Fix variant switch on engine load
[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];
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 = 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         return;
7172     }
7173
7174     if(addToBookFlag) { // adding moves to book
7175         char buf[MSG_SIZ], move[MSG_SIZ];
7176         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7177         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7178                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7179         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7180         AddBookMove(buf);
7181         addToBookFlag = FALSE;
7182         ClearHighlights();
7183         return;
7184     }
7185
7186     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7187 }
7188
7189 /* Common tail of UserMoveEvent and DropMenuEvent */
7190 int
7191 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7192 {
7193     char *bookHit = 0;
7194
7195     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7196         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7197         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7198         if(WhiteOnMove(currentMove)) {
7199             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7200         } else {
7201             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7202         }
7203     }
7204
7205     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7206        move type in caller when we know the move is a legal promotion */
7207     if(moveType == NormalMove && promoChar)
7208         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7209
7210     /* [HGM] <popupFix> The following if has been moved here from
7211        UserMoveEvent(). Because it seemed to belong here (why not allow
7212        piece drops in training games?), and because it can only be
7213        performed after it is known to what we promote. */
7214     if (gameMode == Training) {
7215       /* compare the move played on the board to the next move in the
7216        * game. If they match, display the move and the opponent's response.
7217        * If they don't match, display an error message.
7218        */
7219       int saveAnimate;
7220       Board testBoard;
7221       CopyBoard(testBoard, boards[currentMove]);
7222       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7223
7224       if (CompareBoards(testBoard, boards[currentMove+1])) {
7225         ForwardInner(currentMove+1);
7226
7227         /* Autoplay the opponent's response.
7228          * if appData.animate was TRUE when Training mode was entered,
7229          * the response will be animated.
7230          */
7231         saveAnimate = appData.animate;
7232         appData.animate = animateTraining;
7233         ForwardInner(currentMove+1);
7234         appData.animate = saveAnimate;
7235
7236         /* check for the end of the game */
7237         if (currentMove >= forwardMostMove) {
7238           gameMode = PlayFromGameFile;
7239           ModeHighlight();
7240           SetTrainingModeOff();
7241           DisplayInformation(_("End of game"));
7242         }
7243       } else {
7244         DisplayError(_("Incorrect move"), 0);
7245       }
7246       return 1;
7247     }
7248
7249   /* Ok, now we know that the move is good, so we can kill
7250      the previous line in Analysis Mode */
7251   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7252                                 && currentMove < forwardMostMove) {
7253     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7254     else forwardMostMove = currentMove;
7255   }
7256
7257   ClearMap();
7258
7259   /* If we need the chess program but it's dead, restart it */
7260   ResurrectChessProgram();
7261
7262   /* A user move restarts a paused game*/
7263   if (pausing)
7264     PauseEvent();
7265
7266   thinkOutput[0] = NULLCHAR;
7267
7268   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7269
7270   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7271     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7272     return 1;
7273   }
7274
7275   if (gameMode == BeginningOfGame) {
7276     if (appData.noChessProgram) {
7277       gameMode = EditGame;
7278       SetGameInfo();
7279     } else {
7280       char buf[MSG_SIZ];
7281       gameMode = MachinePlaysBlack;
7282       StartClocks();
7283       SetGameInfo();
7284       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7285       DisplayTitle(buf);
7286       if (first.sendName) {
7287         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7288         SendToProgram(buf, &first);
7289       }
7290       StartClocks();
7291     }
7292     ModeHighlight();
7293   }
7294
7295   /* Relay move to ICS or chess engine */
7296   if (appData.icsActive) {
7297     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7298         gameMode == IcsExamining) {
7299       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7300         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7301         SendToICS("draw ");
7302         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7303       }
7304       // also send plain move, in case ICS does not understand atomic claims
7305       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7306       ics_user_moved = 1;
7307     }
7308   } else {
7309     if (first.sendTime && (gameMode == BeginningOfGame ||
7310                            gameMode == MachinePlaysWhite ||
7311                            gameMode == MachinePlaysBlack)) {
7312       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7313     }
7314     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7315          // [HGM] book: if program might be playing, let it use book
7316         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7317         first.maybeThinking = TRUE;
7318     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7319         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7320         SendBoard(&first, currentMove+1);
7321         if(second.analyzing) {
7322             if(!second.useSetboard) SendToProgram("undo\n", &second);
7323             SendBoard(&second, currentMove+1);
7324         }
7325     } else {
7326         SendMoveToProgram(forwardMostMove-1, &first);
7327         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7328     }
7329     if (currentMove == cmailOldMove + 1) {
7330       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7331     }
7332   }
7333
7334   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7335
7336   switch (gameMode) {
7337   case EditGame:
7338     if(appData.testLegality)
7339     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7340     case MT_NONE:
7341     case MT_CHECK:
7342       break;
7343     case MT_CHECKMATE:
7344     case MT_STAINMATE:
7345       if (WhiteOnMove(currentMove)) {
7346         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7347       } else {
7348         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7349       }
7350       break;
7351     case MT_STALEMATE:
7352       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7353       break;
7354     }
7355     break;
7356
7357   case MachinePlaysBlack:
7358   case MachinePlaysWhite:
7359     /* disable certain menu options while machine is thinking */
7360     SetMachineThinkingEnables();
7361     break;
7362
7363   default:
7364     break;
7365   }
7366
7367   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7368   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7369
7370   if(bookHit) { // [HGM] book: simulate book reply
7371         static char bookMove[MSG_SIZ]; // a bit generous?
7372
7373         programStats.nodes = programStats.depth = programStats.time =
7374         programStats.score = programStats.got_only_move = 0;
7375         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7376
7377         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7378         strcat(bookMove, bookHit);
7379         HandleMachineMove(bookMove, &first);
7380   }
7381   return 1;
7382 }
7383
7384 void
7385 MarkByFEN(char *fen)
7386 {
7387         int r, f;
7388         if(!appData.markers || !appData.highlightDragging) return;
7389         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7390         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7391         while(*fen) {
7392             int s = 0;
7393             marker[r][f] = 0;
7394             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7395             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7396             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7397             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7398             if(*fen == 'T') marker[r][f++] = 0; else
7399             if(*fen == 'Y') marker[r][f++] = 1; else
7400             if(*fen == 'G') marker[r][f++] = 3; else
7401             if(*fen == 'B') marker[r][f++] = 4; else
7402             if(*fen == 'C') marker[r][f++] = 5; else
7403             if(*fen == 'M') marker[r][f++] = 6; else
7404             if(*fen == 'W') marker[r][f++] = 7; else
7405             if(*fen == 'D') marker[r][f++] = 8; else
7406             if(*fen == 'R') marker[r][f++] = 2; else {
7407                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7408               f += s; fen -= s>0;
7409             }
7410             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7411             if(r < 0) break;
7412             fen++;
7413         }
7414         DrawPosition(TRUE, NULL);
7415 }
7416
7417 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7418
7419 void
7420 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7421 {
7422     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7423     Markers *m = (Markers *) closure;
7424     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7425                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7426         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7427                          || kind == WhiteCapturesEnPassant
7428                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7429     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7430 }
7431
7432 static int hoverSavedValid;
7433
7434 void
7435 MarkTargetSquares (int clear)
7436 {
7437   int x, y, sum=0;
7438   if(clear) { // no reason to ever suppress clearing
7439     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7440     hoverSavedValid = 0;
7441     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7442   } else {
7443     int capt = 0;
7444     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7445        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7446     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7447     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7448       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7449       if(capt)
7450       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7451     }
7452   }
7453   DrawPosition(FALSE, NULL);
7454 }
7455
7456 int
7457 Explode (Board board, int fromX, int fromY, int toX, int toY)
7458 {
7459     if(gameInfo.variant == VariantAtomic &&
7460        (board[toY][toX] != EmptySquare ||                     // capture?
7461         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7462                          board[fromY][fromX] == BlackPawn   )
7463       )) {
7464         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7465         return TRUE;
7466     }
7467     return FALSE;
7468 }
7469
7470 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7471
7472 int
7473 CanPromote (ChessSquare piece, int y)
7474 {
7475         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7476         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7477         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7478         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7479            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7480           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7481            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7482         return (piece == BlackPawn && y <= zone ||
7483                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7484                 piece == BlackLance && y <= zone ||
7485                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7486 }
7487
7488 void
7489 HoverEvent (int xPix, int yPix, int x, int y)
7490 {
7491         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7492         int r, f;
7493         if(!first.highlight) return;
7494         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7495         if(x == oldX && y == oldY) return; // only do something if we enter new square
7496         oldFromX = fromX; oldFromY = fromY;
7497         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7498           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7499             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7500           hoverSavedValid = 1;
7501         } else if(oldX != x || oldY != y) {
7502           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7503           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7504           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7505             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7506           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7507             char buf[MSG_SIZ];
7508             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7509             SendToProgram(buf, &first);
7510           }
7511           oldX = x; oldY = y;
7512 //        SetHighlights(fromX, fromY, x, y);
7513         }
7514 }
7515
7516 void ReportClick(char *action, int x, int y)
7517 {
7518         char buf[MSG_SIZ]; // Inform engine of what user does
7519         int r, f;
7520         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7521           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7522             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7523         if(!first.highlight || gameMode == EditPosition) return;
7524         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7525         SendToProgram(buf, &first);
7526 }
7527
7528 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7529
7530 void
7531 LeftClick (ClickType clickType, int xPix, int yPix)
7532 {
7533     int x, y;
7534     Boolean saveAnimate;
7535     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7536     char promoChoice = NULLCHAR;
7537     ChessSquare piece;
7538     static TimeMark lastClickTime, prevClickTime;
7539
7540     if(flashing) return;
7541
7542     x = EventToSquare(xPix, BOARD_WIDTH);
7543     y = EventToSquare(yPix, BOARD_HEIGHT);
7544     if (!flipView && y >= 0) {
7545         y = BOARD_HEIGHT - 1 - y;
7546     }
7547     if (flipView && x >= 0) {
7548         x = BOARD_WIDTH - 1 - x;
7549     }
7550
7551     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7552         static int dummy;
7553         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7554         right = TRUE;
7555         return;
7556     }
7557
7558     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7559
7560     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7561
7562     if (clickType == Press) ErrorPopDown();
7563     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7564
7565     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7566         defaultPromoChoice = promoSweep;
7567         promoSweep = EmptySquare;   // terminate sweep
7568         promoDefaultAltered = TRUE;
7569         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7570     }
7571
7572     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7573         if(clickType == Release) return; // ignore upclick of click-click destination
7574         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7575         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7576         if(gameInfo.holdingsWidth &&
7577                 (WhiteOnMove(currentMove)
7578                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7579                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7580             // click in right holdings, for determining promotion piece
7581             ChessSquare p = boards[currentMove][y][x];
7582             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7583             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7584             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7585                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7586                 fromX = fromY = -1;
7587                 return;
7588             }
7589         }
7590         DrawPosition(FALSE, boards[currentMove]);
7591         return;
7592     }
7593
7594     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7595     if(clickType == Press
7596             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7597               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7598               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7599         return;
7600
7601     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7602         // could be static click on premove from-square: abort premove
7603         gotPremove = 0;
7604         ClearPremoveHighlights();
7605     }
7606
7607     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7608         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7609
7610     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7611         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7612                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7613         defaultPromoChoice = DefaultPromoChoice(side);
7614     }
7615
7616     autoQueen = appData.alwaysPromoteToQueen;
7617
7618     if (fromX == -1) {
7619       int originalY = y;
7620       gatingPiece = EmptySquare;
7621       if (clickType != Press) {
7622         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7623             DragPieceEnd(xPix, yPix); dragging = 0;
7624             DrawPosition(FALSE, NULL);
7625         }
7626         return;
7627       }
7628       doubleClick = FALSE;
7629       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7630         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7631       }
7632       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7633       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7634          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7635          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7636             /* First square */
7637             if (OKToStartUserMove(fromX, fromY)) {
7638                 second = 0;
7639                 ReportClick("lift", x, y);
7640                 MarkTargetSquares(0);
7641                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7642                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7643                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7644                     promoSweep = defaultPromoChoice;
7645                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7646                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7647                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7648                 }
7649                 if (appData.highlightDragging) {
7650                     SetHighlights(fromX, fromY, -1, -1);
7651                 } else {
7652                     ClearHighlights();
7653                 }
7654             } else fromX = fromY = -1;
7655             return;
7656         }
7657     }
7658
7659     /* fromX != -1 */
7660     if (clickType == Press && gameMode != EditPosition) {
7661         ChessSquare fromP;
7662         ChessSquare toP;
7663         int frc;
7664
7665         // ignore off-board to clicks
7666         if(y < 0 || x < 0) return;
7667
7668         /* Check if clicking again on the same color piece */
7669         fromP = boards[currentMove][fromY][fromX];
7670         toP = boards[currentMove][y][x];
7671         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7672         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7673             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7674            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7675              WhitePawn <= toP && toP <= WhiteKing &&
7676              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7677              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7678             (BlackPawn <= fromP && fromP <= BlackKing &&
7679              BlackPawn <= toP && toP <= BlackKing &&
7680              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7681              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7682             /* Clicked again on same color piece -- changed his mind */
7683             second = (x == fromX && y == fromY);
7684             killX = killY = kill2X = kill2Y = -1;
7685             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7686                 second = FALSE; // first double-click rather than scond click
7687                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7688             }
7689             promoDefaultAltered = FALSE;
7690            if(!second) MarkTargetSquares(1);
7691            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7692             if (appData.highlightDragging) {
7693                 SetHighlights(x, y, -1, -1);
7694             } else {
7695                 ClearHighlights();
7696             }
7697             if (OKToStartUserMove(x, y)) {
7698                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7699                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7700                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7701                  gatingPiece = boards[currentMove][fromY][fromX];
7702                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7703                 fromX = x;
7704                 fromY = y; dragging = 1;
7705                 if(!second) ReportClick("lift", x, y);
7706                 MarkTargetSquares(0);
7707                 DragPieceBegin(xPix, yPix, FALSE);
7708                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7709                     promoSweep = defaultPromoChoice;
7710                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7711                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7712                 }
7713             }
7714            }
7715            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7716            second = FALSE;
7717         }
7718         // ignore clicks on holdings
7719         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7720     }
7721
7722     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7723         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7724         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7725         return;
7726     }
7727
7728     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7729         DragPieceEnd(xPix, yPix); dragging = 0;
7730         if(clearFlag) {
7731             // a deferred attempt to click-click move an empty square on top of a piece
7732             boards[currentMove][y][x] = EmptySquare;
7733             ClearHighlights();
7734             DrawPosition(FALSE, boards[currentMove]);
7735             fromX = fromY = -1; clearFlag = 0;
7736             return;
7737         }
7738         if (appData.animateDragging) {
7739             /* Undo animation damage if any */
7740             DrawPosition(FALSE, NULL);
7741         }
7742         if (second) {
7743             /* Second up/down in same square; just abort move */
7744             second = 0;
7745             fromX = fromY = -1;
7746             gatingPiece = EmptySquare;
7747             ClearHighlights();
7748             gotPremove = 0;
7749             ClearPremoveHighlights();
7750             MarkTargetSquares(-1);
7751             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7752         } else {
7753             /* First upclick in same square; start click-click mode */
7754             SetHighlights(x, y, -1, -1);
7755         }
7756         return;
7757     }
7758
7759     clearFlag = 0;
7760
7761     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7762        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7763         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7764         DisplayMessage(_("only marked squares are legal"),"");
7765         DrawPosition(TRUE, NULL);
7766         return; // ignore to-click
7767     }
7768
7769     /* we now have a different from- and (possibly off-board) to-square */
7770     /* Completed move */
7771     if(!sweepSelecting) {
7772         toX = x;
7773         toY = y;
7774     }
7775
7776     piece = boards[currentMove][fromY][fromX];
7777
7778     saveAnimate = appData.animate;
7779     if (clickType == Press) {
7780         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7781         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7782             // must be Edit Position mode with empty-square selected
7783             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7784             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7785             return;
7786         }
7787         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7788             return;
7789         }
7790         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7791             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7792         } else
7793         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7794         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7795           if(appData.sweepSelect) {
7796             promoSweep = defaultPromoChoice;
7797             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7798             selectFlag = 0; lastX = xPix; lastY = yPix;
7799             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7800             saveFlash = appData.flashCount; appData.flashCount = 0;
7801             Sweep(0); // Pawn that is going to promote: preview promotion piece
7802             sweepSelecting = 1;
7803             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7804             MarkTargetSquares(1);
7805           }
7806           return; // promo popup appears on up-click
7807         }
7808         /* Finish clickclick move */
7809         if (appData.animate || appData.highlightLastMove) {
7810             SetHighlights(fromX, fromY, toX, toY);
7811         } else {
7812             ClearHighlights();
7813         }
7814         MarkTargetSquares(1);
7815     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7816         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7817         *promoRestrict = 0; appData.flashCount = saveFlash;
7818         if (appData.animate || appData.highlightLastMove) {
7819             SetHighlights(fromX, fromY, toX, toY);
7820         } else {
7821             ClearHighlights();
7822         }
7823         MarkTargetSquares(1);
7824     } else {
7825 #if 0
7826 // [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
7827         /* Finish drag move */
7828         if (appData.highlightLastMove) {
7829             SetHighlights(fromX, fromY, toX, toY);
7830         } else {
7831             ClearHighlights();
7832         }
7833 #endif
7834         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7835           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7836         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7837         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7838           dragging *= 2;            // flag button-less dragging if we are dragging
7839           MarkTargetSquares(1);
7840           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7841           else {
7842             kill2X = killX; kill2Y = killY;
7843             killX = x; killY = y;     // remember this square as intermediate
7844             ReportClick("put", x, y); // and inform engine
7845             ReportClick("lift", x, y);
7846             MarkTargetSquares(0);
7847             return;
7848           }
7849         }
7850         DragPieceEnd(xPix, yPix); dragging = 0;
7851         /* Don't animate move and drag both */
7852         appData.animate = FALSE;
7853         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7854     }
7855
7856     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7857     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7858         ChessSquare piece = boards[currentMove][fromY][fromX];
7859         if(gameMode == EditPosition && piece != EmptySquare &&
7860            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7861             int n;
7862
7863             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7864                 n = PieceToNumber(piece - (int)BlackPawn);
7865                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7866                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7867                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7868             } else
7869             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7870                 n = PieceToNumber(piece);
7871                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7872                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7873                 boards[currentMove][n][BOARD_WIDTH-2]++;
7874             }
7875             boards[currentMove][fromY][fromX] = EmptySquare;
7876         }
7877         ClearHighlights();
7878         fromX = fromY = -1;
7879         MarkTargetSquares(1);
7880         DrawPosition(TRUE, boards[currentMove]);
7881         return;
7882     }
7883
7884     // off-board moves should not be highlighted
7885     if(x < 0 || y < 0) ClearHighlights();
7886     else ReportClick("put", x, y);
7887
7888     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7889
7890     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7891
7892     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7893         SetHighlights(fromX, fromY, toX, toY);
7894         MarkTargetSquares(1);
7895         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7896             // [HGM] super: promotion to captured piece selected from holdings
7897             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7898             promotionChoice = TRUE;
7899             // kludge follows to temporarily execute move on display, without promoting yet
7900             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7901             boards[currentMove][toY][toX] = p;
7902             DrawPosition(FALSE, boards[currentMove]);
7903             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7904             boards[currentMove][toY][toX] = q;
7905             DisplayMessage("Click in holdings to choose piece", "");
7906             return;
7907         }
7908         PromotionPopUp(promoChoice);
7909     } else {
7910         int oldMove = currentMove;
7911         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7912         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7913         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7914         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7915         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7916            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7917             DrawPosition(TRUE, boards[currentMove]);
7918         fromX = fromY = -1;
7919         flashing = 0;
7920     }
7921     appData.animate = saveAnimate;
7922     if (appData.animate || appData.animateDragging) {
7923         /* Undo animation damage if needed */
7924 //      DrawPosition(FALSE, NULL);
7925     }
7926 }
7927
7928 int
7929 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7930 {   // front-end-free part taken out of PieceMenuPopup
7931     int whichMenu; int xSqr, ySqr;
7932
7933     if(seekGraphUp) { // [HGM] seekgraph
7934         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7935         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7936         return -2;
7937     }
7938
7939     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7940          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7941         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7942         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7943         if(action == Press)   {
7944             originalFlip = flipView;
7945             flipView = !flipView; // temporarily flip board to see game from partners perspective
7946             DrawPosition(TRUE, partnerBoard);
7947             DisplayMessage(partnerStatus, "");
7948             partnerUp = TRUE;
7949         } else if(action == Release) {
7950             flipView = originalFlip;
7951             DrawPosition(TRUE, boards[currentMove]);
7952             partnerUp = FALSE;
7953         }
7954         return -2;
7955     }
7956
7957     xSqr = EventToSquare(x, BOARD_WIDTH);
7958     ySqr = EventToSquare(y, BOARD_HEIGHT);
7959     if (action == Release) {
7960         if(pieceSweep != EmptySquare) {
7961             EditPositionMenuEvent(pieceSweep, toX, toY);
7962             pieceSweep = EmptySquare;
7963         } else UnLoadPV(); // [HGM] pv
7964     }
7965     if (action != Press) return -2; // return code to be ignored
7966     switch (gameMode) {
7967       case IcsExamining:
7968         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7969       case EditPosition:
7970         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7971         if (xSqr < 0 || ySqr < 0) return -1;
7972         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7973         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7974         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7975         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7976         NextPiece(0);
7977         return 2; // grab
7978       case IcsObserving:
7979         if(!appData.icsEngineAnalyze) return -1;
7980       case IcsPlayingWhite:
7981       case IcsPlayingBlack:
7982         if(!appData.zippyPlay) goto noZip;
7983       case AnalyzeMode:
7984       case AnalyzeFile:
7985       case MachinePlaysWhite:
7986       case MachinePlaysBlack:
7987       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7988         if (!appData.dropMenu) {
7989           LoadPV(x, y);
7990           return 2; // flag front-end to grab mouse events
7991         }
7992         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7993            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7994       case EditGame:
7995       noZip:
7996         if (xSqr < 0 || ySqr < 0) return -1;
7997         if (!appData.dropMenu || appData.testLegality &&
7998             gameInfo.variant != VariantBughouse &&
7999             gameInfo.variant != VariantCrazyhouse) return -1;
8000         whichMenu = 1; // drop menu
8001         break;
8002       default:
8003         return -1;
8004     }
8005
8006     if (((*fromX = xSqr) < 0) ||
8007         ((*fromY = ySqr) < 0)) {
8008         *fromX = *fromY = -1;
8009         return -1;
8010     }
8011     if (flipView)
8012       *fromX = BOARD_WIDTH - 1 - *fromX;
8013     else
8014       *fromY = BOARD_HEIGHT - 1 - *fromY;
8015
8016     return whichMenu;
8017 }
8018
8019 void
8020 Wheel (int dir, int x, int y)
8021 {
8022     if(gameMode == EditPosition) {
8023         int xSqr = EventToSquare(x, BOARD_WIDTH);
8024         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8025         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8026         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8027         do {
8028             boards[currentMove][ySqr][xSqr] += dir;
8029             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8030             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8031         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8032         DrawPosition(FALSE, boards[currentMove]);
8033     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8034 }
8035
8036 void
8037 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8038 {
8039 //    char * hint = lastHint;
8040     FrontEndProgramStats stats;
8041
8042     stats.which = cps == &first ? 0 : 1;
8043     stats.depth = cpstats->depth;
8044     stats.nodes = cpstats->nodes;
8045     stats.score = cpstats->score;
8046     stats.time = cpstats->time;
8047     stats.pv = cpstats->movelist;
8048     stats.hint = lastHint;
8049     stats.an_move_index = 0;
8050     stats.an_move_count = 0;
8051
8052     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8053         stats.hint = cpstats->move_name;
8054         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8055         stats.an_move_count = cpstats->nr_moves;
8056     }
8057
8058     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
8059
8060     SetProgramStats( &stats );
8061 }
8062
8063 void
8064 ClearEngineOutputPane (int which)
8065 {
8066     static FrontEndProgramStats dummyStats;
8067     dummyStats.which = which;
8068     dummyStats.pv = "#";
8069     SetProgramStats( &dummyStats );
8070 }
8071
8072 #define MAXPLAYERS 500
8073
8074 char *
8075 TourneyStandings (int display)
8076 {
8077     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8078     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8079     char result, *p, *names[MAXPLAYERS];
8080
8081     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8082         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8083     names[0] = p = strdup(appData.participants);
8084     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8085
8086     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8087
8088     while(result = appData.results[nr]) {
8089         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8090         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8091         wScore = bScore = 0;
8092         switch(result) {
8093           case '+': wScore = 2; break;
8094           case '-': bScore = 2; break;
8095           case '=': wScore = bScore = 1; break;
8096           case ' ':
8097           case '*': return strdup("busy"); // tourney not finished
8098         }
8099         score[w] += wScore;
8100         score[b] += bScore;
8101         games[w]++;
8102         games[b]++;
8103         nr++;
8104     }
8105     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8106     for(w=0; w<nPlayers; w++) {
8107         bScore = -1;
8108         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8109         ranking[w] = b; points[w] = bScore; score[b] = -2;
8110     }
8111     p = malloc(nPlayers*34+1);
8112     for(w=0; w<nPlayers && w<display; w++)
8113         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8114     free(names[0]);
8115     return p;
8116 }
8117
8118 void
8119 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8120 {       // count all piece types
8121         int p, f, r;
8122         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8123         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8124         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8125                 p = board[r][f];
8126                 pCnt[p]++;
8127                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8128                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8129                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8130                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8131                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8132                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8133         }
8134 }
8135
8136 int
8137 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8138 {
8139         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8140         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8141
8142         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8143         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8144         if(myPawns == 2 && nMine == 3) // KPP
8145             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8146         if(myPawns == 1 && nMine == 2) // KP
8147             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8148         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8149             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8150         if(myPawns) return FALSE;
8151         if(pCnt[WhiteRook+side])
8152             return pCnt[BlackRook-side] ||
8153                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8154                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8155                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8156         if(pCnt[WhiteCannon+side]) {
8157             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8158             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8159         }
8160         if(pCnt[WhiteKnight+side])
8161             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8162         return FALSE;
8163 }
8164
8165 int
8166 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8167 {
8168         VariantClass v = gameInfo.variant;
8169
8170         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8171         if(v == VariantShatranj) return TRUE; // always winnable through baring
8172         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8173         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8174
8175         if(v == VariantXiangqi) {
8176                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8177
8178                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8179                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8180                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8181                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8182                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8183                 if(stale) // we have at least one last-rank P plus perhaps C
8184                     return majors // KPKX
8185                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8186                 else // KCA*E*
8187                     return pCnt[WhiteFerz+side] // KCAK
8188                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8189                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8190                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8191
8192         } else if(v == VariantKnightmate) {
8193                 if(nMine == 1) return FALSE;
8194                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8195         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8196                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8197
8198                 if(nMine == 1) return FALSE; // bare King
8199                 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
8200                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8201                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8202                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8203                 if(pCnt[WhiteKnight+side])
8204                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8205                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8206                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8207                 if(nBishops)
8208                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8209                 if(pCnt[WhiteAlfil+side])
8210                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8211                 if(pCnt[WhiteWazir+side])
8212                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8213         }
8214
8215         return TRUE;
8216 }
8217
8218 int
8219 CompareWithRights (Board b1, Board b2)
8220 {
8221     int rights = 0;
8222     if(!CompareBoards(b1, b2)) return FALSE;
8223     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8224     /* compare castling rights */
8225     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8226            rights++; /* King lost rights, while rook still had them */
8227     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8228         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8229            rights++; /* but at least one rook lost them */
8230     }
8231     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8232            rights++;
8233     if( b1[CASTLING][5] != NoRights ) {
8234         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8235            rights++;
8236     }
8237     return rights == 0;
8238 }
8239
8240 int
8241 Adjudicate (ChessProgramState *cps)
8242 {       // [HGM] some adjudications useful with buggy engines
8243         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8244         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8245         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8246         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8247         int k, drop, count = 0; static int bare = 1;
8248         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8249         Boolean canAdjudicate = !appData.icsActive;
8250
8251         // most tests only when we understand the game, i.e. legality-checking on
8252             if( appData.testLegality )
8253             {   /* [HGM] Some more adjudications for obstinate engines */
8254                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8255                 static int moveCount = 6;
8256                 ChessMove result;
8257                 char *reason = NULL;
8258
8259                 /* Count what is on board. */
8260                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8261
8262                 /* Some material-based adjudications that have to be made before stalemate test */
8263                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8264                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8265                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8266                      if(canAdjudicate && appData.checkMates) {
8267                          if(engineOpponent)
8268                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8269                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8270                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8271                          return 1;
8272                      }
8273                 }
8274
8275                 /* Bare King in Shatranj (loses) or Losers (wins) */
8276                 if( nrW == 1 || nrB == 1) {
8277                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8278                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8279                      if(canAdjudicate && appData.checkMates) {
8280                          if(engineOpponent)
8281                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8282                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8283                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8284                          return 1;
8285                      }
8286                   } else
8287                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8288                   {    /* bare King */
8289                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8290                         if(canAdjudicate && appData.checkMates) {
8291                             /* but only adjudicate if adjudication enabled */
8292                             if(engineOpponent)
8293                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8294                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8295                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8296                             return 1;
8297                         }
8298                   }
8299                 } else bare = 1;
8300
8301
8302             // don't wait for engine to announce game end if we can judge ourselves
8303             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8304               case MT_CHECK:
8305                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8306                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8307                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8308                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8309                             checkCnt++;
8310                         if(checkCnt >= 2) {
8311                             reason = "Xboard adjudication: 3rd check";
8312                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8313                             break;
8314                         }
8315                     }
8316                 }
8317               case MT_NONE:
8318               default:
8319                 break;
8320               case MT_STEALMATE:
8321               case MT_STALEMATE:
8322               case MT_STAINMATE:
8323                 reason = "Xboard adjudication: Stalemate";
8324                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8325                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8326                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8327                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8328                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8329                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8330                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8331                                                                         EP_CHECKMATE : EP_WINS);
8332                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8333                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8334                 }
8335                 break;
8336               case MT_CHECKMATE:
8337                 reason = "Xboard adjudication: Checkmate";
8338                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8339                 if(gameInfo.variant == VariantShogi) {
8340                     if(forwardMostMove > backwardMostMove
8341                        && moveList[forwardMostMove-1][1] == '@'
8342                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8343                         reason = "XBoard adjudication: pawn-drop mate";
8344                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8345                     }
8346                 }
8347                 break;
8348             }
8349
8350                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8351                     case EP_STALEMATE:
8352                         result = GameIsDrawn; break;
8353                     case EP_CHECKMATE:
8354                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8355                     case EP_WINS:
8356                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8357                     default:
8358                         result = EndOfFile;
8359                 }
8360                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8361                     if(engineOpponent)
8362                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8363                     GameEnds( result, reason, GE_XBOARD );
8364                     return 1;
8365                 }
8366
8367                 /* Next absolutely insufficient mating material. */
8368                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8369                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8370                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8371
8372                      /* always flag draws, for judging claims */
8373                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8374
8375                      if(canAdjudicate && appData.materialDraws) {
8376                          /* but only adjudicate them if adjudication enabled */
8377                          if(engineOpponent) {
8378                            SendToProgram("force\n", engineOpponent); // suppress reply
8379                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8380                          }
8381                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8382                          return 1;
8383                      }
8384                 }
8385
8386                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8387                 if(gameInfo.variant == VariantXiangqi ?
8388                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8389                  : nrW + nrB == 4 &&
8390                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8391                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8392                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8393                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8394                    ) ) {
8395                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8396                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8397                           if(engineOpponent) {
8398                             SendToProgram("force\n", engineOpponent); // suppress reply
8399                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8400                           }
8401                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8402                           return 1;
8403                      }
8404                 } else moveCount = 6;
8405             }
8406
8407         // Repetition draws and 50-move rule can be applied independently of legality testing
8408
8409                 /* Check for rep-draws */
8410                 count = 0;
8411                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8412                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8413                 for(k = forwardMostMove-2;
8414                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8415                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8416                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8417                     k-=2)
8418                 {   int rights=0;
8419                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8420                         /* compare castling rights */
8421                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8422                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8423                                 rights++; /* King lost rights, while rook still had them */
8424                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8425                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8426                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8427                                    rights++; /* but at least one rook lost them */
8428                         }
8429                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8430                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8431                                 rights++;
8432                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8433                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8434                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8435                                    rights++;
8436                         }
8437                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8438                             && appData.drawRepeats > 1) {
8439                              /* adjudicate after user-specified nr of repeats */
8440                              int result = GameIsDrawn;
8441                              char *details = "XBoard adjudication: repetition draw";
8442                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8443                                 // [HGM] xiangqi: check for forbidden perpetuals
8444                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8445                                 for(m=forwardMostMove; m>k; m-=2) {
8446                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8447                                         ourPerpetual = 0; // the current mover did not always check
8448                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8449                                         hisPerpetual = 0; // the opponent did not always check
8450                                 }
8451                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8452                                                                         ourPerpetual, hisPerpetual);
8453                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8454                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8455                                     details = "Xboard adjudication: perpetual checking";
8456                                 } else
8457                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8458                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8459                                 } else
8460                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8461                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8462                                         result = BlackWins;
8463                                         details = "Xboard adjudication: repetition";
8464                                     }
8465                                 } else // it must be XQ
8466                                 // Now check for perpetual chases
8467                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8468                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8469                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8470                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8471                                         static char resdet[MSG_SIZ];
8472                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8473                                         details = resdet;
8474                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8475                                     } else
8476                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8477                                         break; // Abort repetition-checking loop.
8478                                 }
8479                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8480                              }
8481                              if(engineOpponent) {
8482                                SendToProgram("force\n", engineOpponent); // suppress reply
8483                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8484                              }
8485                              GameEnds( result, details, GE_XBOARD );
8486                              return 1;
8487                         }
8488                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8489                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8490                     }
8491                 }
8492
8493                 /* Now we test for 50-move draws. Determine ply count */
8494                 count = forwardMostMove;
8495                 /* look for last irreversble move */
8496                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8497                     count--;
8498                 /* if we hit starting position, add initial plies */
8499                 if( count == backwardMostMove )
8500                     count -= initialRulePlies;
8501                 count = forwardMostMove - count;
8502                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8503                         // adjust reversible move counter for checks in Xiangqi
8504                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8505                         if(i < backwardMostMove) i = backwardMostMove;
8506                         while(i <= forwardMostMove) {
8507                                 lastCheck = inCheck; // check evasion does not count
8508                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8509                                 if(inCheck || lastCheck) count--; // check does not count
8510                                 i++;
8511                         }
8512                 }
8513                 if( count >= 100)
8514                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8515                          /* this is used to judge if draw claims are legal */
8516                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8517                          if(engineOpponent) {
8518                            SendToProgram("force\n", engineOpponent); // suppress reply
8519                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8520                          }
8521                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8522                          return 1;
8523                 }
8524
8525                 /* if draw offer is pending, treat it as a draw claim
8526                  * when draw condition present, to allow engines a way to
8527                  * claim draws before making their move to avoid a race
8528                  * condition occurring after their move
8529                  */
8530                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8531                          char *p = NULL;
8532                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8533                              p = "Draw claim: 50-move rule";
8534                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8535                              p = "Draw claim: 3-fold repetition";
8536                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8537                              p = "Draw claim: insufficient mating material";
8538                          if( p != NULL && canAdjudicate) {
8539                              if(engineOpponent) {
8540                                SendToProgram("force\n", engineOpponent); // suppress reply
8541                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8542                              }
8543                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8544                              return 1;
8545                          }
8546                 }
8547
8548                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8549                     if(engineOpponent) {
8550                       SendToProgram("force\n", engineOpponent); // suppress reply
8551                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8552                     }
8553                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8554                     return 1;
8555                 }
8556         return 0;
8557 }
8558
8559 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8560 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8561 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8562
8563 static int
8564 BitbaseProbe ()
8565 {
8566     int pieces[10], squares[10], cnt=0, r, f, res;
8567     static int loaded;
8568     static PPROBE_EGBB probeBB;
8569     if(!appData.testLegality) return 10;
8570     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8571     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8572     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8573     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8574         ChessSquare piece = boards[forwardMostMove][r][f];
8575         int black = (piece >= BlackPawn);
8576         int type = piece - black*BlackPawn;
8577         if(piece == EmptySquare) continue;
8578         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8579         if(type == WhiteKing) type = WhiteQueen + 1;
8580         type = egbbCode[type];
8581         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8582         pieces[cnt] = type + black*6;
8583         if(++cnt > 5) return 11;
8584     }
8585     pieces[cnt] = squares[cnt] = 0;
8586     // probe EGBB
8587     if(loaded == 2) return 13; // loading failed before
8588     if(loaded == 0) {
8589         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8590         HMODULE lib;
8591         PLOAD_EGBB loadBB;
8592         loaded = 2; // prepare for failure
8593         if(!path) return 13; // no egbb installed
8594         strncpy(buf, path + 8, MSG_SIZ);
8595         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8596         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8597         lib = LoadLibrary(buf);
8598         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8599         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8600         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8601         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8602         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8603         loaded = 1; // success!
8604     }
8605     res = probeBB(forwardMostMove & 1, pieces, squares);
8606     return res > 0 ? 1 : res < 0 ? -1 : 0;
8607 }
8608
8609 char *
8610 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8611 {   // [HGM] book: this routine intercepts moves to simulate book replies
8612     char *bookHit = NULL;
8613
8614     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8615         char buf[MSG_SIZ];
8616         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8617         SendToProgram(buf, cps);
8618     }
8619     //first determine if the incoming move brings opponent into his book
8620     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8621         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8622     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8623     if(bookHit != NULL && !cps->bookSuspend) {
8624         // make sure opponent is not going to reply after receiving move to book position
8625         SendToProgram("force\n", cps);
8626         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8627     }
8628     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8629     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8630     // now arrange restart after book miss
8631     if(bookHit) {
8632         // after a book hit we never send 'go', and the code after the call to this routine
8633         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8634         char buf[MSG_SIZ], *move = bookHit;
8635         if(cps->useSAN) {
8636             int fromX, fromY, toX, toY;
8637             char promoChar;
8638             ChessMove moveType;
8639             move = buf + 30;
8640             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8641                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8642                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8643                                     PosFlags(forwardMostMove),
8644                                     fromY, fromX, toY, toX, promoChar, move);
8645             } else {
8646                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8647                 bookHit = NULL;
8648             }
8649         }
8650         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8651         SendToProgram(buf, cps);
8652         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8653     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8654         SendToProgram("go\n", cps);
8655         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8656     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8657         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8658             SendToProgram("go\n", cps);
8659         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8660     }
8661     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8662 }
8663
8664 int
8665 LoadError (char *errmess, ChessProgramState *cps)
8666 {   // unloads engine and switches back to -ncp mode if it was first
8667     if(cps->initDone) return FALSE;
8668     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8669     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8670     cps->pr = NoProc;
8671     if(cps == &first) {
8672         appData.noChessProgram = TRUE;
8673         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8674         gameMode = BeginningOfGame; ModeHighlight();
8675         SetNCPMode();
8676     }
8677     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8678     DisplayMessage("", ""); // erase waiting message
8679     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8680     return TRUE;
8681 }
8682
8683 char *savedMessage;
8684 ChessProgramState *savedState;
8685 void
8686 DeferredBookMove (void)
8687 {
8688         if(savedState->lastPing != savedState->lastPong)
8689                     ScheduleDelayedEvent(DeferredBookMove, 10);
8690         else
8691         HandleMachineMove(savedMessage, savedState);
8692 }
8693
8694 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8695 static ChessProgramState *stalledEngine;
8696 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8697
8698 void
8699 HandleMachineMove (char *message, ChessProgramState *cps)
8700 {
8701     static char firstLeg[20], legs;
8702     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8703     char realname[MSG_SIZ];
8704     int fromX, fromY, toX, toY;
8705     ChessMove moveType;
8706     char promoChar, roar;
8707     char *p, *pv=buf1;
8708     int oldError;
8709     char *bookHit;
8710
8711     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8712         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8713         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8714             DisplayError(_("Invalid pairing from pairing engine"), 0);
8715             return;
8716         }
8717         pairingReceived = 1;
8718         NextMatchGame();
8719         return; // Skim the pairing messages here.
8720     }
8721
8722     oldError = cps->userError; cps->userError = 0;
8723
8724 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8725     /*
8726      * Kludge to ignore BEL characters
8727      */
8728     while (*message == '\007') message++;
8729
8730     /*
8731      * [HGM] engine debug message: ignore lines starting with '#' character
8732      */
8733     if(cps->debug && *message == '#') return;
8734
8735     /*
8736      * Look for book output
8737      */
8738     if (cps == &first && bookRequested) {
8739         if (message[0] == '\t' || message[0] == ' ') {
8740             /* Part of the book output is here; append it */
8741             strcat(bookOutput, message);
8742             strcat(bookOutput, "  \n");
8743             return;
8744         } else if (bookOutput[0] != NULLCHAR) {
8745             /* All of book output has arrived; display it */
8746             char *p = bookOutput;
8747             while (*p != NULLCHAR) {
8748                 if (*p == '\t') *p = ' ';
8749                 p++;
8750             }
8751             DisplayInformation(bookOutput);
8752             bookRequested = FALSE;
8753             /* Fall through to parse the current output */
8754         }
8755     }
8756
8757     /*
8758      * Look for machine move.
8759      */
8760     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8761         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8762     {
8763         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8764             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8765             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8766             stalledEngine = cps;
8767             if(appData.ponderNextMove) { // bring opponent out of ponder
8768                 if(gameMode == TwoMachinesPlay) {
8769                     if(cps->other->pause)
8770                         PauseEngine(cps->other);
8771                     else
8772                         SendToProgram("easy\n", cps->other);
8773                 }
8774             }
8775             StopClocks();
8776             return;
8777         }
8778
8779       if(cps->usePing) {
8780
8781         /* This method is only useful on engines that support ping */
8782         if(abortEngineThink) {
8783             if (appData.debugMode) {
8784                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8785             }
8786             SendToProgram("undo\n", cps);
8787             return;
8788         }
8789
8790         if (cps->lastPing != cps->lastPong) {
8791             /* Extra move from before last new; ignore */
8792             if (appData.debugMode) {
8793                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8794             }
8795           return;
8796         }
8797
8798       } else {
8799
8800         int machineWhite = FALSE;
8801
8802         switch (gameMode) {
8803           case BeginningOfGame:
8804             /* Extra move from before last reset; ignore */
8805             if (appData.debugMode) {
8806                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8807             }
8808             return;
8809
8810           case EndOfGame:
8811           case IcsIdle:
8812           default:
8813             /* Extra move after we tried to stop.  The mode test is
8814                not a reliable way of detecting this problem, but it's
8815                the best we can do on engines that don't support ping.
8816             */
8817             if (appData.debugMode) {
8818                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8819                         cps->which, gameMode);
8820             }
8821             SendToProgram("undo\n", cps);
8822             return;
8823
8824           case MachinePlaysWhite:
8825           case IcsPlayingWhite:
8826             machineWhite = TRUE;
8827             break;
8828
8829           case MachinePlaysBlack:
8830           case IcsPlayingBlack:
8831             machineWhite = FALSE;
8832             break;
8833
8834           case TwoMachinesPlay:
8835             machineWhite = (cps->twoMachinesColor[0] == 'w');
8836             break;
8837         }
8838         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8839             if (appData.debugMode) {
8840                 fprintf(debugFP,
8841                         "Ignoring move out of turn by %s, gameMode %d"
8842                         ", forwardMost %d\n",
8843                         cps->which, gameMode, forwardMostMove);
8844             }
8845             return;
8846         }
8847       }
8848
8849         if(cps->alphaRank) AlphaRank(machineMove, 4);
8850
8851         // [HGM] lion: (some very limited) support for Alien protocol
8852         killX = killY = kill2X = kill2Y = -1;
8853         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8854             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8855             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8856             return;
8857         }
8858         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8859             char *q = strchr(p+1, ',');            // second comma?
8860             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8861             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8862             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8863         }
8864         if(firstLeg[0]) { // there was a previous leg;
8865             // only support case where same piece makes two step
8866             char buf[20], *p = machineMove+1, *q = buf+1, f;
8867             safeStrCpy(buf, machineMove, 20);
8868             while(isdigit(*q)) q++; // find start of to-square
8869             safeStrCpy(machineMove, firstLeg, 20);
8870             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8871             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
8872             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)
8873             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8874             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8875             firstLeg[0] = NULLCHAR; legs = 0;
8876         }
8877
8878         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8879                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8880             /* Machine move could not be parsed; ignore it. */
8881           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8882                     machineMove, _(cps->which));
8883             DisplayMoveError(buf1);
8884             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8885                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8886             if (gameMode == TwoMachinesPlay) {
8887               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8888                        buf1, GE_XBOARD);
8889             }
8890             return;
8891         }
8892
8893         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8894         /* So we have to redo legality test with true e.p. status here,  */
8895         /* to make sure an illegal e.p. capture does not slip through,   */
8896         /* to cause a forfeit on a justified illegal-move complaint      */
8897         /* of the opponent.                                              */
8898         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8899            ChessMove moveType;
8900            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8901                              fromY, fromX, toY, toX, promoChar);
8902             if(moveType == IllegalMove) {
8903               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8904                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8905                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8906                            buf1, GE_XBOARD);
8907                 return;
8908            } else if(!appData.fischerCastling)
8909            /* [HGM] Kludge to handle engines that send FRC-style castling
8910               when they shouldn't (like TSCP-Gothic) */
8911            switch(moveType) {
8912              case WhiteASideCastleFR:
8913              case BlackASideCastleFR:
8914                toX+=2;
8915                currentMoveString[2]++;
8916                break;
8917              case WhiteHSideCastleFR:
8918              case BlackHSideCastleFR:
8919                toX--;
8920                currentMoveString[2]--;
8921                break;
8922              default: ; // nothing to do, but suppresses warning of pedantic compilers
8923            }
8924         }
8925         hintRequested = FALSE;
8926         lastHint[0] = NULLCHAR;
8927         bookRequested = FALSE;
8928         /* Program may be pondering now */
8929         cps->maybeThinking = TRUE;
8930         if (cps->sendTime == 2) cps->sendTime = 1;
8931         if (cps->offeredDraw) cps->offeredDraw--;
8932
8933         /* [AS] Save move info*/
8934         pvInfoList[ forwardMostMove ].score = programStats.score;
8935         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8936         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8937
8938         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8939
8940         /* Test suites abort the 'game' after one move */
8941         if(*appData.finger) {
8942            static FILE *f;
8943            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8944            if(!f) f = fopen(appData.finger, "w");
8945            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8946            else { DisplayFatalError("Bad output file", errno, 0); return; }
8947            free(fen);
8948            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8949         }
8950         if(appData.epd) {
8951            if(solvingTime >= 0) {
8952               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8953               totalTime += solvingTime; first.matchWins++;
8954            } else {
8955               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8956               second.matchWins++;
8957            }
8958            OutputKibitz(2, buf1);
8959            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8960         }
8961
8962         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8963         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8964             int count = 0;
8965
8966             while( count < adjudicateLossPlies ) {
8967                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8968
8969                 if( count & 1 ) {
8970                     score = -score; /* Flip score for winning side */
8971                 }
8972
8973                 if( score > appData.adjudicateLossThreshold ) {
8974                     break;
8975                 }
8976
8977                 count++;
8978             }
8979
8980             if( count >= adjudicateLossPlies ) {
8981                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8982
8983                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8984                     "Xboard adjudication",
8985                     GE_XBOARD );
8986
8987                 return;
8988             }
8989         }
8990
8991         if(Adjudicate(cps)) {
8992             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8993             return; // [HGM] adjudicate: for all automatic game ends
8994         }
8995
8996 #if ZIPPY
8997         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8998             first.initDone) {
8999           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9000                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9001                 SendToICS("draw ");
9002                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9003           }
9004           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9005           ics_user_moved = 1;
9006           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9007                 char buf[3*MSG_SIZ];
9008
9009                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9010                         programStats.score / 100.,
9011                         programStats.depth,
9012                         programStats.time / 100.,
9013                         (unsigned int)programStats.nodes,
9014                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9015                         programStats.movelist);
9016                 SendToICS(buf);
9017           }
9018         }
9019 #endif
9020
9021         /* [AS] Clear stats for next move */
9022         ClearProgramStats();
9023         thinkOutput[0] = NULLCHAR;
9024         hiddenThinkOutputState = 0;
9025
9026         bookHit = NULL;
9027         if (gameMode == TwoMachinesPlay) {
9028             /* [HGM] relaying draw offers moved to after reception of move */
9029             /* and interpreting offer as claim if it brings draw condition */
9030             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9031                 SendToProgram("draw\n", cps->other);
9032             }
9033             if (cps->other->sendTime) {
9034                 SendTimeRemaining(cps->other,
9035                                   cps->other->twoMachinesColor[0] == 'w');
9036             }
9037             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9038             if (firstMove && !bookHit) {
9039                 firstMove = FALSE;
9040                 if (cps->other->useColors) {
9041                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9042                 }
9043                 SendToProgram("go\n", cps->other);
9044             }
9045             cps->other->maybeThinking = TRUE;
9046         }
9047
9048         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9049
9050         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9051
9052         if (!pausing && appData.ringBellAfterMoves) {
9053             if(!roar) RingBell();
9054         }
9055
9056         /*
9057          * Reenable menu items that were disabled while
9058          * machine was thinking
9059          */
9060         if (gameMode != TwoMachinesPlay)
9061             SetUserThinkingEnables();
9062
9063         // [HGM] book: after book hit opponent has received move and is now in force mode
9064         // force the book reply into it, and then fake that it outputted this move by jumping
9065         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9066         if(bookHit) {
9067                 static char bookMove[MSG_SIZ]; // a bit generous?
9068
9069                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9070                 strcat(bookMove, bookHit);
9071                 message = bookMove;
9072                 cps = cps->other;
9073                 programStats.nodes = programStats.depth = programStats.time =
9074                 programStats.score = programStats.got_only_move = 0;
9075                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9076
9077                 if(cps->lastPing != cps->lastPong) {
9078                     savedMessage = message; // args for deferred call
9079                     savedState = cps;
9080                     ScheduleDelayedEvent(DeferredBookMove, 10);
9081                     return;
9082                 }
9083                 goto FakeBookMove;
9084         }
9085
9086         return;
9087     }
9088
9089     /* Set special modes for chess engines.  Later something general
9090      *  could be added here; for now there is just one kludge feature,
9091      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9092      *  when "xboard" is given as an interactive command.
9093      */
9094     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9095         cps->useSigint = FALSE;
9096         cps->useSigterm = FALSE;
9097     }
9098     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9099       ParseFeatures(message+8, cps);
9100       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9101     }
9102
9103     if (!strncmp(message, "setup ", 6) && 
9104         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9105           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9106                                         ) { // [HGM] allow first engine to define opening position
9107       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9108       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9109       *buf = NULLCHAR;
9110       if(sscanf(message, "setup (%s", buf) == 1) {
9111         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9112         ASSIGN(appData.pieceToCharTable, buf);
9113       }
9114       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9115       if(dummy >= 3) {
9116         while(message[s] && message[s++] != ' ');
9117         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9118            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9119             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9120             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9121           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9122           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9123           startedFromSetupPosition = FALSE;
9124         }
9125       }
9126       if(startedFromSetupPosition) return;
9127       ParseFEN(boards[0], &dummy, message+s, FALSE);
9128       DrawPosition(TRUE, boards[0]);
9129       CopyBoard(initialPosition, boards[0]);
9130       startedFromSetupPosition = TRUE;
9131       return;
9132     }
9133     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9134       ChessSquare piece = WhitePawn;
9135       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9136       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9137       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9138       piece += CharToPiece(ID & 255) - WhitePawn;
9139       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9140       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9141       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9142       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9143       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9144       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9145                                                && gameInfo.variant != VariantGreat
9146                                                && gameInfo.variant != VariantFairy    ) return;
9147       if(piece < EmptySquare) {
9148         pieceDefs = TRUE;
9149         ASSIGN(pieceDesc[piece], buf1);
9150         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9151       }
9152       return;
9153     }
9154     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9155       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9156       Sweep(0);
9157       return;
9158     }
9159     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9160      * want this, I was asked to put it in, and obliged.
9161      */
9162     if (!strncmp(message, "setboard ", 9)) {
9163         Board initial_position;
9164
9165         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9166
9167         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9168             DisplayError(_("Bad FEN received from engine"), 0);
9169             return ;
9170         } else {
9171            Reset(TRUE, FALSE);
9172            CopyBoard(boards[0], initial_position);
9173            initialRulePlies = FENrulePlies;
9174            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9175            else gameMode = MachinePlaysBlack;
9176            DrawPosition(FALSE, boards[currentMove]);
9177         }
9178         return;
9179     }
9180
9181     /*
9182      * Look for communication commands
9183      */
9184     if (!strncmp(message, "telluser ", 9)) {
9185         if(message[9] == '\\' && message[10] == '\\')
9186             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9187         PlayTellSound();
9188         DisplayNote(message + 9);
9189         return;
9190     }
9191     if (!strncmp(message, "tellusererror ", 14)) {
9192         cps->userError = 1;
9193         if(message[14] == '\\' && message[15] == '\\')
9194             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9195         PlayTellSound();
9196         DisplayError(message + 14, 0);
9197         return;
9198     }
9199     if (!strncmp(message, "tellopponent ", 13)) {
9200       if (appData.icsActive) {
9201         if (loggedOn) {
9202           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9203           SendToICS(buf1);
9204         }
9205       } else {
9206         DisplayNote(message + 13);
9207       }
9208       return;
9209     }
9210     if (!strncmp(message, "tellothers ", 11)) {
9211       if (appData.icsActive) {
9212         if (loggedOn) {
9213           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9214           SendToICS(buf1);
9215         }
9216       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9217       return;
9218     }
9219     if (!strncmp(message, "tellall ", 8)) {
9220       if (appData.icsActive) {
9221         if (loggedOn) {
9222           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9223           SendToICS(buf1);
9224         }
9225       } else {
9226         DisplayNote(message + 8);
9227       }
9228       return;
9229     }
9230     if (strncmp(message, "warning", 7) == 0) {
9231         /* Undocumented feature, use tellusererror in new code */
9232         DisplayError(message, 0);
9233         return;
9234     }
9235     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9236         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9237         strcat(realname, " query");
9238         AskQuestion(realname, buf2, buf1, cps->pr);
9239         return;
9240     }
9241     /* Commands from the engine directly to ICS.  We don't allow these to be
9242      *  sent until we are logged on. Crafty kibitzes have been known to
9243      *  interfere with the login process.
9244      */
9245     if (loggedOn) {
9246         if (!strncmp(message, "tellics ", 8)) {
9247             SendToICS(message + 8);
9248             SendToICS("\n");
9249             return;
9250         }
9251         if (!strncmp(message, "tellicsnoalias ", 15)) {
9252             SendToICS(ics_prefix);
9253             SendToICS(message + 15);
9254             SendToICS("\n");
9255             return;
9256         }
9257         /* The following are for backward compatibility only */
9258         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9259             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9260             SendToICS(ics_prefix);
9261             SendToICS(message);
9262             SendToICS("\n");
9263             return;
9264         }
9265     }
9266     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9267         if(initPing == cps->lastPong) {
9268             if(gameInfo.variant == VariantUnknown) {
9269                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9270                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9271                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9272             }
9273             initPing = -1;
9274         }
9275         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9276             abortEngineThink = FALSE;
9277             DisplayMessage("", "");
9278             ThawUI();
9279         }
9280         return;
9281     }
9282     if(!strncmp(message, "highlight ", 10)) {
9283         if(appData.testLegality && !*engineVariant && appData.markers) return;
9284         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9285         return;
9286     }
9287     if(!strncmp(message, "click ", 6)) {
9288         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9289         if(appData.testLegality || !appData.oneClick) return;
9290         sscanf(message+6, "%c%d%c", &f, &y, &c);
9291         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9292         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9293         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9294         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9295         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9296         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9297             LeftClick(Release, lastLeftX, lastLeftY);
9298         controlKey  = (c == ',');
9299         LeftClick(Press, x, y);
9300         LeftClick(Release, x, y);
9301         first.highlight = f;
9302         return;
9303     }
9304     /*
9305      * If the move is illegal, cancel it and redraw the board.
9306      * Also deal with other error cases.  Matching is rather loose
9307      * here to accommodate engines written before the spec.
9308      */
9309     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9310         strncmp(message, "Error", 5) == 0) {
9311         if (StrStr(message, "name") ||
9312             StrStr(message, "rating") || StrStr(message, "?") ||
9313             StrStr(message, "result") || StrStr(message, "board") ||
9314             StrStr(message, "bk") || StrStr(message, "computer") ||
9315             StrStr(message, "variant") || StrStr(message, "hint") ||
9316             StrStr(message, "random") || StrStr(message, "depth") ||
9317             StrStr(message, "accepted")) {
9318             return;
9319         }
9320         if (StrStr(message, "protover")) {
9321           /* Program is responding to input, so it's apparently done
9322              initializing, and this error message indicates it is
9323              protocol version 1.  So we don't need to wait any longer
9324              for it to initialize and send feature commands. */
9325           FeatureDone(cps, 1);
9326           cps->protocolVersion = 1;
9327           return;
9328         }
9329         cps->maybeThinking = FALSE;
9330
9331         if (StrStr(message, "draw")) {
9332             /* Program doesn't have "draw" command */
9333             cps->sendDrawOffers = 0;
9334             return;
9335         }
9336         if (cps->sendTime != 1 &&
9337             (StrStr(message, "time") || StrStr(message, "otim"))) {
9338           /* Program apparently doesn't have "time" or "otim" command */
9339           cps->sendTime = 0;
9340           return;
9341         }
9342         if (StrStr(message, "analyze")) {
9343             cps->analysisSupport = FALSE;
9344             cps->analyzing = FALSE;
9345 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9346             EditGameEvent(); // [HGM] try to preserve loaded game
9347             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9348             DisplayError(buf2, 0);
9349             return;
9350         }
9351         if (StrStr(message, "(no matching move)st")) {
9352           /* Special kludge for GNU Chess 4 only */
9353           cps->stKludge = TRUE;
9354           SendTimeControl(cps, movesPerSession, timeControl,
9355                           timeIncrement, appData.searchDepth,
9356                           searchTime);
9357           return;
9358         }
9359         if (StrStr(message, "(no matching move)sd")) {
9360           /* Special kludge for GNU Chess 4 only */
9361           cps->sdKludge = TRUE;
9362           SendTimeControl(cps, movesPerSession, timeControl,
9363                           timeIncrement, appData.searchDepth,
9364                           searchTime);
9365           return;
9366         }
9367         if (!StrStr(message, "llegal")) {
9368             return;
9369         }
9370         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9371             gameMode == IcsIdle) return;
9372         if (forwardMostMove <= backwardMostMove) return;
9373         if (pausing) PauseEvent();
9374       if(appData.forceIllegal) {
9375             // [HGM] illegal: machine refused move; force position after move into it
9376           SendToProgram("force\n", cps);
9377           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9378                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9379                 // when black is to move, while there might be nothing on a2 or black
9380                 // might already have the move. So send the board as if white has the move.
9381                 // But first we must change the stm of the engine, as it refused the last move
9382                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9383                 if(WhiteOnMove(forwardMostMove)) {
9384                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9385                     SendBoard(cps, forwardMostMove); // kludgeless board
9386                 } else {
9387                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9388                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9389                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9390                 }
9391           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9392             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9393                  gameMode == TwoMachinesPlay)
9394               SendToProgram("go\n", cps);
9395             return;
9396       } else
9397         if (gameMode == PlayFromGameFile) {
9398             /* Stop reading this game file */
9399             gameMode = EditGame;
9400             ModeHighlight();
9401         }
9402         /* [HGM] illegal-move claim should forfeit game when Xboard */
9403         /* only passes fully legal moves                            */
9404         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9405             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9406                                 "False illegal-move claim", GE_XBOARD );
9407             return; // do not take back move we tested as valid
9408         }
9409         currentMove = forwardMostMove-1;
9410         DisplayMove(currentMove-1); /* before DisplayMoveError */
9411         SwitchClocks(forwardMostMove-1); // [HGM] race
9412         DisplayBothClocks();
9413         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9414                 parseList[currentMove], _(cps->which));
9415         DisplayMoveError(buf1);
9416         DrawPosition(FALSE, boards[currentMove]);
9417
9418         SetUserThinkingEnables();
9419         return;
9420     }
9421     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9422         /* Program has a broken "time" command that
9423            outputs a string not ending in newline.
9424            Don't use it. */
9425         cps->sendTime = 0;
9426     }
9427     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9428         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9429             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9430     }
9431
9432     /*
9433      * If chess program startup fails, exit with an error message.
9434      * Attempts to recover here are futile. [HGM] Well, we try anyway
9435      */
9436     if ((StrStr(message, "unknown host") != NULL)
9437         || (StrStr(message, "No remote directory") != NULL)
9438         || (StrStr(message, "not found") != NULL)
9439         || (StrStr(message, "No such file") != NULL)
9440         || (StrStr(message, "can't alloc") != NULL)
9441         || (StrStr(message, "Permission denied") != NULL)) {
9442
9443         cps->maybeThinking = FALSE;
9444         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9445                 _(cps->which), cps->program, cps->host, message);
9446         RemoveInputSource(cps->isr);
9447         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9448             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9449             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9450         }
9451         return;
9452     }
9453
9454     /*
9455      * Look for hint output
9456      */
9457     if (sscanf(message, "Hint: %s", buf1) == 1) {
9458         if (cps == &first && hintRequested) {
9459             hintRequested = FALSE;
9460             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9461                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9462                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9463                                     PosFlags(forwardMostMove),
9464                                     fromY, fromX, toY, toX, promoChar, buf1);
9465                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9466                 DisplayInformation(buf2);
9467             } else {
9468                 /* Hint move could not be parsed!? */
9469               snprintf(buf2, sizeof(buf2),
9470                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9471                         buf1, _(cps->which));
9472                 DisplayError(buf2, 0);
9473             }
9474         } else {
9475           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9476         }
9477         return;
9478     }
9479
9480     /*
9481      * Ignore other messages if game is not in progress
9482      */
9483     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9484         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9485
9486     /*
9487      * look for win, lose, draw, or draw offer
9488      */
9489     if (strncmp(message, "1-0", 3) == 0) {
9490         char *p, *q, *r = "";
9491         p = strchr(message, '{');
9492         if (p) {
9493             q = strchr(p, '}');
9494             if (q) {
9495                 *q = NULLCHAR;
9496                 r = p + 1;
9497             }
9498         }
9499         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9500         return;
9501     } else if (strncmp(message, "0-1", 3) == 0) {
9502         char *p, *q, *r = "";
9503         p = strchr(message, '{');
9504         if (p) {
9505             q = strchr(p, '}');
9506             if (q) {
9507                 *q = NULLCHAR;
9508                 r = p + 1;
9509             }
9510         }
9511         /* Kludge for Arasan 4.1 bug */
9512         if (strcmp(r, "Black resigns") == 0) {
9513             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9514             return;
9515         }
9516         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9517         return;
9518     } else if (strncmp(message, "1/2", 3) == 0) {
9519         char *p, *q, *r = "";
9520         p = strchr(message, '{');
9521         if (p) {
9522             q = strchr(p, '}');
9523             if (q) {
9524                 *q = NULLCHAR;
9525                 r = p + 1;
9526             }
9527         }
9528
9529         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9530         return;
9531
9532     } else if (strncmp(message, "White resign", 12) == 0) {
9533         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9534         return;
9535     } else if (strncmp(message, "Black resign", 12) == 0) {
9536         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9537         return;
9538     } else if (strncmp(message, "White matches", 13) == 0 ||
9539                strncmp(message, "Black matches", 13) == 0   ) {
9540         /* [HGM] ignore GNUShogi noises */
9541         return;
9542     } else if (strncmp(message, "White", 5) == 0 &&
9543                message[5] != '(' &&
9544                StrStr(message, "Black") == NULL) {
9545         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9546         return;
9547     } else if (strncmp(message, "Black", 5) == 0 &&
9548                message[5] != '(') {
9549         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9550         return;
9551     } else if (strcmp(message, "resign") == 0 ||
9552                strcmp(message, "computer resigns") == 0) {
9553         switch (gameMode) {
9554           case MachinePlaysBlack:
9555           case IcsPlayingBlack:
9556             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9557             break;
9558           case MachinePlaysWhite:
9559           case IcsPlayingWhite:
9560             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9561             break;
9562           case TwoMachinesPlay:
9563             if (cps->twoMachinesColor[0] == 'w')
9564               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9565             else
9566               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9567             break;
9568           default:
9569             /* can't happen */
9570             break;
9571         }
9572         return;
9573     } else if (strncmp(message, "opponent mates", 14) == 0) {
9574         switch (gameMode) {
9575           case MachinePlaysBlack:
9576           case IcsPlayingBlack:
9577             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9578             break;
9579           case MachinePlaysWhite:
9580           case IcsPlayingWhite:
9581             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9582             break;
9583           case TwoMachinesPlay:
9584             if (cps->twoMachinesColor[0] == 'w')
9585               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9586             else
9587               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9588             break;
9589           default:
9590             /* can't happen */
9591             break;
9592         }
9593         return;
9594     } else if (strncmp(message, "computer mates", 14) == 0) {
9595         switch (gameMode) {
9596           case MachinePlaysBlack:
9597           case IcsPlayingBlack:
9598             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9599             break;
9600           case MachinePlaysWhite:
9601           case IcsPlayingWhite:
9602             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9603             break;
9604           case TwoMachinesPlay:
9605             if (cps->twoMachinesColor[0] == 'w')
9606               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9607             else
9608               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9609             break;
9610           default:
9611             /* can't happen */
9612             break;
9613         }
9614         return;
9615     } else if (strncmp(message, "checkmate", 9) == 0) {
9616         if (WhiteOnMove(forwardMostMove)) {
9617             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9618         } else {
9619             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9620         }
9621         return;
9622     } else if (strstr(message, "Draw") != NULL ||
9623                strstr(message, "game is a draw") != NULL) {
9624         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9625         return;
9626     } else if (strstr(message, "offer") != NULL &&
9627                strstr(message, "draw") != NULL) {
9628 #if ZIPPY
9629         if (appData.zippyPlay && first.initDone) {
9630             /* Relay offer to ICS */
9631             SendToICS(ics_prefix);
9632             SendToICS("draw\n");
9633         }
9634 #endif
9635         cps->offeredDraw = 2; /* valid until this engine moves twice */
9636         if (gameMode == TwoMachinesPlay) {
9637             if (cps->other->offeredDraw) {
9638                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9639             /* [HGM] in two-machine mode we delay relaying draw offer      */
9640             /* until after we also have move, to see if it is really claim */
9641             }
9642         } else if (gameMode == MachinePlaysWhite ||
9643                    gameMode == MachinePlaysBlack) {
9644           if (userOfferedDraw) {
9645             DisplayInformation(_("Machine accepts your draw offer"));
9646             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9647           } else {
9648             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9649           }
9650         }
9651     }
9652
9653
9654     /*
9655      * Look for thinking output
9656      */
9657     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9658           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9659                                 ) {
9660         int plylev, mvleft, mvtot, curscore, time;
9661         char mvname[MOVE_LEN];
9662         u64 nodes; // [DM]
9663         char plyext;
9664         int ignore = FALSE;
9665         int prefixHint = FALSE;
9666         mvname[0] = NULLCHAR;
9667
9668         switch (gameMode) {
9669           case MachinePlaysBlack:
9670           case IcsPlayingBlack:
9671             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9672             break;
9673           case MachinePlaysWhite:
9674           case IcsPlayingWhite:
9675             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9676             break;
9677           case AnalyzeMode:
9678           case AnalyzeFile:
9679             break;
9680           case IcsObserving: /* [DM] icsEngineAnalyze */
9681             if (!appData.icsEngineAnalyze) ignore = TRUE;
9682             break;
9683           case TwoMachinesPlay:
9684             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9685                 ignore = TRUE;
9686             }
9687             break;
9688           default:
9689             ignore = TRUE;
9690             break;
9691         }
9692
9693         if (!ignore) {
9694             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9695             buf1[0] = NULLCHAR;
9696             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9697                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9698                 char score_buf[MSG_SIZ];
9699
9700                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9701                     nodes += u64Const(0x100000000);
9702
9703                 if (plyext != ' ' && plyext != '\t') {
9704                     time *= 100;
9705                 }
9706
9707                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9708                 if( cps->scoreIsAbsolute &&
9709                     ( gameMode == MachinePlaysBlack ||
9710                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9711                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9712                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9713                      !WhiteOnMove(currentMove)
9714                     ) )
9715                 {
9716                     curscore = -curscore;
9717                 }
9718
9719                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9720
9721                 if(*bestMove) { // rememer time best EPD move was first found
9722                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9723                     ChessMove mt;
9724                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9725                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9726                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9727                 }
9728
9729                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9730                         char buf[MSG_SIZ];
9731                         FILE *f;
9732                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9733                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9734                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9735                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9736                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9737                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9738                                 fclose(f);
9739                         }
9740                         else
9741                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9742                           DisplayError(_("failed writing PV"), 0);
9743                 }
9744
9745                 tempStats.depth = plylev;
9746                 tempStats.nodes = nodes;
9747                 tempStats.time = time;
9748                 tempStats.score = curscore;
9749                 tempStats.got_only_move = 0;
9750
9751                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9752                         int ticklen;
9753
9754                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9755                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9756                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9757                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9758                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9759                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9760                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9761                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9762                 }
9763
9764                 /* Buffer overflow protection */
9765                 if (pv[0] != NULLCHAR) {
9766                     if (strlen(pv) >= sizeof(tempStats.movelist)
9767                         && appData.debugMode) {
9768                         fprintf(debugFP,
9769                                 "PV is too long; using the first %u bytes.\n",
9770                                 (unsigned) sizeof(tempStats.movelist) - 1);
9771                     }
9772
9773                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9774                 } else {
9775                     sprintf(tempStats.movelist, " no PV\n");
9776                 }
9777
9778                 if (tempStats.seen_stat) {
9779                     tempStats.ok_to_send = 1;
9780                 }
9781
9782                 if (strchr(tempStats.movelist, '(') != NULL) {
9783                     tempStats.line_is_book = 1;
9784                     tempStats.nr_moves = 0;
9785                     tempStats.moves_left = 0;
9786                 } else {
9787                     tempStats.line_is_book = 0;
9788                 }
9789
9790                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9791                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9792
9793                 SendProgramStatsToFrontend( cps, &tempStats );
9794
9795                 /*
9796                     [AS] Protect the thinkOutput buffer from overflow... this
9797                     is only useful if buf1 hasn't overflowed first!
9798                 */
9799                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9800                 if(curscore >= MATE_SCORE) 
9801                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9802                 else if(curscore <= -MATE_SCORE) 
9803                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9804                 else
9805                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9806                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9807                          plylev,
9808                          (gameMode == TwoMachinesPlay ?
9809                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9810                          score_buf,
9811                          prefixHint ? lastHint : "",
9812                          prefixHint ? " " : "" );
9813
9814                 if( buf1[0] != NULLCHAR ) {
9815                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9816
9817                     if( strlen(pv) > max_len ) {
9818                         if( appData.debugMode) {
9819                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9820                         }
9821                         pv[max_len+1] = '\0';
9822                     }
9823
9824                     strcat( thinkOutput, pv);
9825                 }
9826
9827                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9828                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9829                     DisplayMove(currentMove - 1);
9830                 }
9831                 return;
9832
9833             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9834                 /* crafty (9.25+) says "(only move) <move>"
9835                  * if there is only 1 legal move
9836                  */
9837                 sscanf(p, "(only move) %s", buf1);
9838                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9839                 sprintf(programStats.movelist, "%s (only move)", buf1);
9840                 programStats.depth = 1;
9841                 programStats.nr_moves = 1;
9842                 programStats.moves_left = 1;
9843                 programStats.nodes = 1;
9844                 programStats.time = 1;
9845                 programStats.got_only_move = 1;
9846
9847                 /* Not really, but we also use this member to
9848                    mean "line isn't going to change" (Crafty
9849                    isn't searching, so stats won't change) */
9850                 programStats.line_is_book = 1;
9851
9852                 SendProgramStatsToFrontend( cps, &programStats );
9853
9854                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9855                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9856                     DisplayMove(currentMove - 1);
9857                 }
9858                 return;
9859             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9860                               &time, &nodes, &plylev, &mvleft,
9861                               &mvtot, mvname) >= 5) {
9862                 /* The stat01: line is from Crafty (9.29+) in response
9863                    to the "." command */
9864                 programStats.seen_stat = 1;
9865                 cps->maybeThinking = TRUE;
9866
9867                 if (programStats.got_only_move || !appData.periodicUpdates)
9868                   return;
9869
9870                 programStats.depth = plylev;
9871                 programStats.time = time;
9872                 programStats.nodes = nodes;
9873                 programStats.moves_left = mvleft;
9874                 programStats.nr_moves = mvtot;
9875                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9876                 programStats.ok_to_send = 1;
9877                 programStats.movelist[0] = '\0';
9878
9879                 SendProgramStatsToFrontend( cps, &programStats );
9880
9881                 return;
9882
9883             } else if (strncmp(message,"++",2) == 0) {
9884                 /* Crafty 9.29+ outputs this */
9885                 programStats.got_fail = 2;
9886                 return;
9887
9888             } else if (strncmp(message,"--",2) == 0) {
9889                 /* Crafty 9.29+ outputs this */
9890                 programStats.got_fail = 1;
9891                 return;
9892
9893             } else if (thinkOutput[0] != NULLCHAR &&
9894                        strncmp(message, "    ", 4) == 0) {
9895                 unsigned message_len;
9896
9897                 p = message;
9898                 while (*p && *p == ' ') p++;
9899
9900                 message_len = strlen( p );
9901
9902                 /* [AS] Avoid buffer overflow */
9903                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9904                     strcat(thinkOutput, " ");
9905                     strcat(thinkOutput, p);
9906                 }
9907
9908                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9909                     strcat(programStats.movelist, " ");
9910                     strcat(programStats.movelist, p);
9911                 }
9912
9913                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9914                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9915                     DisplayMove(currentMove - 1);
9916                 }
9917                 return;
9918             }
9919         }
9920         else {
9921             buf1[0] = NULLCHAR;
9922
9923             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9924                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9925             {
9926                 ChessProgramStats cpstats;
9927
9928                 if (plyext != ' ' && plyext != '\t') {
9929                     time *= 100;
9930                 }
9931
9932                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9933                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9934                     curscore = -curscore;
9935                 }
9936
9937                 cpstats.depth = plylev;
9938                 cpstats.nodes = nodes;
9939                 cpstats.time = time;
9940                 cpstats.score = curscore;
9941                 cpstats.got_only_move = 0;
9942                 cpstats.movelist[0] = '\0';
9943
9944                 if (buf1[0] != NULLCHAR) {
9945                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9946                 }
9947
9948                 cpstats.ok_to_send = 0;
9949                 cpstats.line_is_book = 0;
9950                 cpstats.nr_moves = 0;
9951                 cpstats.moves_left = 0;
9952
9953                 SendProgramStatsToFrontend( cps, &cpstats );
9954             }
9955         }
9956     }
9957 }
9958
9959
9960 /* Parse a game score from the character string "game", and
9961    record it as the history of the current game.  The game
9962    score is NOT assumed to start from the standard position.
9963    The display is not updated in any way.
9964    */
9965 void
9966 ParseGameHistory (char *game)
9967 {
9968     ChessMove moveType;
9969     int fromX, fromY, toX, toY, boardIndex;
9970     char promoChar;
9971     char *p, *q;
9972     char buf[MSG_SIZ];
9973
9974     if (appData.debugMode)
9975       fprintf(debugFP, "Parsing game history: %s\n", game);
9976
9977     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9978     gameInfo.site = StrSave(appData.icsHost);
9979     gameInfo.date = PGNDate();
9980     gameInfo.round = StrSave("-");
9981
9982     /* Parse out names of players */
9983     while (*game == ' ') game++;
9984     p = buf;
9985     while (*game != ' ') *p++ = *game++;
9986     *p = NULLCHAR;
9987     gameInfo.white = StrSave(buf);
9988     while (*game == ' ') game++;
9989     p = buf;
9990     while (*game != ' ' && *game != '\n') *p++ = *game++;
9991     *p = NULLCHAR;
9992     gameInfo.black = StrSave(buf);
9993
9994     /* Parse moves */
9995     boardIndex = blackPlaysFirst ? 1 : 0;
9996     yynewstr(game);
9997     for (;;) {
9998         yyboardindex = boardIndex;
9999         moveType = (ChessMove) Myylex();
10000         switch (moveType) {
10001           case IllegalMove:             /* maybe suicide chess, etc. */
10002   if (appData.debugMode) {
10003     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10004     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10005     setbuf(debugFP, NULL);
10006   }
10007           case WhitePromotion:
10008           case BlackPromotion:
10009           case WhiteNonPromotion:
10010           case BlackNonPromotion:
10011           case NormalMove:
10012           case FirstLeg:
10013           case WhiteCapturesEnPassant:
10014           case BlackCapturesEnPassant:
10015           case WhiteKingSideCastle:
10016           case WhiteQueenSideCastle:
10017           case BlackKingSideCastle:
10018           case BlackQueenSideCastle:
10019           case WhiteKingSideCastleWild:
10020           case WhiteQueenSideCastleWild:
10021           case BlackKingSideCastleWild:
10022           case BlackQueenSideCastleWild:
10023           /* PUSH Fabien */
10024           case WhiteHSideCastleFR:
10025           case WhiteASideCastleFR:
10026           case BlackHSideCastleFR:
10027           case BlackASideCastleFR:
10028           /* POP Fabien */
10029             fromX = currentMoveString[0] - AAA;
10030             fromY = currentMoveString[1] - ONE;
10031             toX = currentMoveString[2] - AAA;
10032             toY = currentMoveString[3] - ONE;
10033             promoChar = currentMoveString[4];
10034             break;
10035           case WhiteDrop:
10036           case BlackDrop:
10037             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10038             fromX = moveType == WhiteDrop ?
10039               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10040             (int) CharToPiece(ToLower(currentMoveString[0]));
10041             fromY = DROP_RANK;
10042             toX = currentMoveString[2] - AAA;
10043             toY = currentMoveString[3] - ONE;
10044             promoChar = NULLCHAR;
10045             break;
10046           case AmbiguousMove:
10047             /* bug? */
10048             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10049   if (appData.debugMode) {
10050     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10051     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10052     setbuf(debugFP, NULL);
10053   }
10054             DisplayError(buf, 0);
10055             return;
10056           case ImpossibleMove:
10057             /* bug? */
10058             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10059   if (appData.debugMode) {
10060     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10061     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10062     setbuf(debugFP, NULL);
10063   }
10064             DisplayError(buf, 0);
10065             return;
10066           case EndOfFile:
10067             if (boardIndex < backwardMostMove) {
10068                 /* Oops, gap.  How did that happen? */
10069                 DisplayError(_("Gap in move list"), 0);
10070                 return;
10071             }
10072             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10073             if (boardIndex > forwardMostMove) {
10074                 forwardMostMove = boardIndex;
10075             }
10076             return;
10077           case ElapsedTime:
10078             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10079                 strcat(parseList[boardIndex-1], " ");
10080                 strcat(parseList[boardIndex-1], yy_text);
10081             }
10082             continue;
10083           case Comment:
10084           case PGNTag:
10085           case NAG:
10086           default:
10087             /* ignore */
10088             continue;
10089           case WhiteWins:
10090           case BlackWins:
10091           case GameIsDrawn:
10092           case GameUnfinished:
10093             if (gameMode == IcsExamining) {
10094                 if (boardIndex < backwardMostMove) {
10095                     /* Oops, gap.  How did that happen? */
10096                     return;
10097                 }
10098                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10099                 return;
10100             }
10101             gameInfo.result = moveType;
10102             p = strchr(yy_text, '{');
10103             if (p == NULL) p = strchr(yy_text, '(');
10104             if (p == NULL) {
10105                 p = yy_text;
10106                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10107             } else {
10108                 q = strchr(p, *p == '{' ? '}' : ')');
10109                 if (q != NULL) *q = NULLCHAR;
10110                 p++;
10111             }
10112             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10113             gameInfo.resultDetails = StrSave(p);
10114             continue;
10115         }
10116         if (boardIndex >= forwardMostMove &&
10117             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10118             backwardMostMove = blackPlaysFirst ? 1 : 0;
10119             return;
10120         }
10121         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10122                                  fromY, fromX, toY, toX, promoChar,
10123                                  parseList[boardIndex]);
10124         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10125         /* currentMoveString is set as a side-effect of yylex */
10126         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10127         strcat(moveList[boardIndex], "\n");
10128         boardIndex++;
10129         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10130         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10131           case MT_NONE:
10132           case MT_STALEMATE:
10133           default:
10134             break;
10135           case MT_CHECK:
10136             if(!IS_SHOGI(gameInfo.variant))
10137                 strcat(parseList[boardIndex - 1], "+");
10138             break;
10139           case MT_CHECKMATE:
10140           case MT_STAINMATE:
10141             strcat(parseList[boardIndex - 1], "#");
10142             break;
10143         }
10144     }
10145 }
10146
10147
10148 /* Apply a move to the given board  */
10149 void
10150 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10151 {
10152   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10153   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10154
10155     /* [HGM] compute & store e.p. status and castling rights for new position */
10156     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10157
10158       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10159       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10160       board[EP_STATUS] = EP_NONE;
10161       board[EP_FILE] = board[EP_RANK] = 100;
10162
10163   if (fromY == DROP_RANK) {
10164         /* must be first */
10165         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10166             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10167             return;
10168         }
10169         piece = board[toY][toX] = (ChessSquare) fromX;
10170   } else {
10171 //      ChessSquare victim;
10172       int i;
10173
10174       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10175 //           victim = board[killY][killX],
10176            killed = board[killY][killX],
10177            board[killY][killX] = EmptySquare,
10178            board[EP_STATUS] = EP_CAPTURE;
10179            if( kill2X >= 0 && kill2Y >= 0)
10180              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10181       }
10182
10183       if( board[toY][toX] != EmptySquare ) {
10184            board[EP_STATUS] = EP_CAPTURE;
10185            if( (fromX != toX || fromY != toY) && // not igui!
10186                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10187                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10188                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10189            }
10190       }
10191
10192       pawn = board[fromY][fromX];
10193       if( pawn == WhiteLance || pawn == BlackLance ) {
10194            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10195                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10196                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10197            }
10198       }
10199       if( pawn == WhitePawn ) {
10200            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10201                board[EP_STATUS] = EP_PAWN_MOVE;
10202            if( toY-fromY>=2) {
10203                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10204                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10205                         gameInfo.variant != VariantBerolina || toX < fromX)
10206                       board[EP_STATUS] = toX | berolina;
10207                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10208                         gameInfo.variant != VariantBerolina || toX > fromX)
10209                       board[EP_STATUS] = toX;
10210            }
10211       } else
10212       if( pawn == BlackPawn ) {
10213            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10214                board[EP_STATUS] = EP_PAWN_MOVE;
10215            if( toY-fromY<= -2) {
10216                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10217                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10218                         gameInfo.variant != VariantBerolina || toX < fromX)
10219                       board[EP_STATUS] = toX | berolina;
10220                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10221                         gameInfo.variant != VariantBerolina || toX > fromX)
10222                       board[EP_STATUS] = toX;
10223            }
10224        }
10225
10226        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10227        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10228        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10229        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10230
10231        for(i=0; i<nrCastlingRights; i++) {
10232            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10233               board[CASTLING][i] == toX   && castlingRank[i] == toY
10234              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10235        }
10236
10237        if(gameInfo.variant == VariantSChess) { // update virginity
10238            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10239            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10240            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10241            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10242        }
10243
10244      if (fromX == toX && fromY == toY && killX < 0) return;
10245
10246      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10247      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10248      if(gameInfo.variant == VariantKnightmate)
10249          king += (int) WhiteUnicorn - (int) WhiteKing;
10250
10251     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10252        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10253         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10254         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10255         board[EP_STATUS] = EP_NONE; // capture was fake!
10256     } else
10257     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10258         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10259         board[toY][toX] = piece;
10260         board[EP_STATUS] = EP_NONE; // capture was fake!
10261     } else
10262     /* Code added by Tord: */
10263     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10264     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10265         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10266       board[EP_STATUS] = EP_NONE; // capture was fake!
10267       board[fromY][fromX] = EmptySquare;
10268       board[toY][toX] = EmptySquare;
10269       if((toX > fromX) != (piece == WhiteRook)) {
10270         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10271       } else {
10272         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10273       }
10274     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10275                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10276       board[EP_STATUS] = EP_NONE;
10277       board[fromY][fromX] = EmptySquare;
10278       board[toY][toX] = EmptySquare;
10279       if((toX > fromX) != (piece == BlackRook)) {
10280         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10281       } else {
10282         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10283       }
10284     /* End of code added by Tord */
10285
10286     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10287         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10288         board[toY][toX] = piece;
10289     } else if (board[fromY][fromX] == king
10290         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10291         && toY == fromY && toX > fromX+1) {
10292         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10293         board[fromY][toX-1] = board[fromY][rookX];
10294         board[fromY][rookX] = EmptySquare;
10295         board[fromY][fromX] = EmptySquare;
10296         board[toY][toX] = king;
10297     } else if (board[fromY][fromX] == king
10298         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10299                && toY == fromY && toX < fromX-1) {
10300         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10301         board[fromY][toX+1] = board[fromY][rookX];
10302         board[fromY][rookX] = EmptySquare;
10303         board[fromY][fromX] = EmptySquare;
10304         board[toY][toX] = king;
10305     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10306                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10307                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10308                ) {
10309         /* white pawn promotion */
10310         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10311         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10312             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10313         board[fromY][fromX] = EmptySquare;
10314     } else if ((fromY >= BOARD_HEIGHT>>1)
10315                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10316                && (toX != fromX)
10317                && gameInfo.variant != VariantXiangqi
10318                && gameInfo.variant != VariantBerolina
10319                && (pawn == WhitePawn)
10320                && (board[toY][toX] == EmptySquare)) {
10321         board[fromY][fromX] = EmptySquare;
10322         board[toY][toX] = piece;
10323         if(toY == epRank - 128 + 1)
10324             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10325         else
10326             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10327     } else if ((fromY == BOARD_HEIGHT-4)
10328                && (toX == fromX)
10329                && gameInfo.variant == VariantBerolina
10330                && (board[fromY][fromX] == WhitePawn)
10331                && (board[toY][toX] == EmptySquare)) {
10332         board[fromY][fromX] = EmptySquare;
10333         board[toY][toX] = WhitePawn;
10334         if(oldEP & EP_BEROLIN_A) {
10335                 captured = board[fromY][fromX-1];
10336                 board[fromY][fromX-1] = EmptySquare;
10337         }else{  captured = board[fromY][fromX+1];
10338                 board[fromY][fromX+1] = EmptySquare;
10339         }
10340     } else if (board[fromY][fromX] == king
10341         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10342                && toY == fromY && toX > fromX+1) {
10343         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10344         board[fromY][toX-1] = board[fromY][rookX];
10345         board[fromY][rookX] = EmptySquare;
10346         board[fromY][fromX] = EmptySquare;
10347         board[toY][toX] = king;
10348     } else if (board[fromY][fromX] == king
10349         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10350                && toY == fromY && toX < fromX-1) {
10351         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10352         board[fromY][toX+1] = board[fromY][rookX];
10353         board[fromY][rookX] = EmptySquare;
10354         board[fromY][fromX] = EmptySquare;
10355         board[toY][toX] = king;
10356     } else if (fromY == 7 && fromX == 3
10357                && board[fromY][fromX] == BlackKing
10358                && toY == 7 && toX == 5) {
10359         board[fromY][fromX] = EmptySquare;
10360         board[toY][toX] = BlackKing;
10361         board[fromY][7] = EmptySquare;
10362         board[toY][4] = BlackRook;
10363     } else if (fromY == 7 && fromX == 3
10364                && board[fromY][fromX] == BlackKing
10365                && toY == 7 && toX == 1) {
10366         board[fromY][fromX] = EmptySquare;
10367         board[toY][toX] = BlackKing;
10368         board[fromY][0] = EmptySquare;
10369         board[toY][2] = BlackRook;
10370     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10371                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10372                && toY < promoRank && promoChar
10373                ) {
10374         /* black pawn promotion */
10375         board[toY][toX] = CharToPiece(ToLower(promoChar));
10376         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10377             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10378         board[fromY][fromX] = EmptySquare;
10379     } else if ((fromY < BOARD_HEIGHT>>1)
10380                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10381                && (toX != fromX)
10382                && gameInfo.variant != VariantXiangqi
10383                && gameInfo.variant != VariantBerolina
10384                && (pawn == BlackPawn)
10385                && (board[toY][toX] == EmptySquare)) {
10386         board[fromY][fromX] = EmptySquare;
10387         board[toY][toX] = piece;
10388         if(toY == epRank - 128 - 1)
10389             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10390         else
10391             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10392     } else if ((fromY == 3)
10393                && (toX == fromX)
10394                && gameInfo.variant == VariantBerolina
10395                && (board[fromY][fromX] == BlackPawn)
10396                && (board[toY][toX] == EmptySquare)) {
10397         board[fromY][fromX] = EmptySquare;
10398         board[toY][toX] = BlackPawn;
10399         if(oldEP & EP_BEROLIN_A) {
10400                 captured = board[fromY][fromX-1];
10401                 board[fromY][fromX-1] = EmptySquare;
10402         }else{  captured = board[fromY][fromX+1];
10403                 board[fromY][fromX+1] = EmptySquare;
10404         }
10405     } else {
10406         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10407         board[fromY][fromX] = EmptySquare;
10408         board[toY][toX] = piece;
10409     }
10410   }
10411
10412     if (gameInfo.holdingsWidth != 0) {
10413
10414       /* !!A lot more code needs to be written to support holdings  */
10415       /* [HGM] OK, so I have written it. Holdings are stored in the */
10416       /* penultimate board files, so they are automaticlly stored   */
10417       /* in the game history.                                       */
10418       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10419                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10420         /* Delete from holdings, by decreasing count */
10421         /* and erasing image if necessary            */
10422         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10423         if(p < (int) BlackPawn) { /* white drop */
10424              p -= (int)WhitePawn;
10425                  p = PieceToNumber((ChessSquare)p);
10426              if(p >= gameInfo.holdingsSize) p = 0;
10427              if(--board[p][BOARD_WIDTH-2] <= 0)
10428                   board[p][BOARD_WIDTH-1] = EmptySquare;
10429              if((int)board[p][BOARD_WIDTH-2] < 0)
10430                         board[p][BOARD_WIDTH-2] = 0;
10431         } else {                  /* black drop */
10432              p -= (int)BlackPawn;
10433                  p = PieceToNumber((ChessSquare)p);
10434              if(p >= gameInfo.holdingsSize) p = 0;
10435              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10436                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10437              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10438                         board[BOARD_HEIGHT-1-p][1] = 0;
10439         }
10440       }
10441       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10442           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10443         /* [HGM] holdings: Add to holdings, if holdings exist */
10444         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10445                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10446                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10447         }
10448         p = (int) captured;
10449         if (p >= (int) BlackPawn) {
10450           p -= (int)BlackPawn;
10451           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10452                   /* Restore shogi-promoted piece to its original  first */
10453                   captured = (ChessSquare) (DEMOTED(captured));
10454                   p = DEMOTED(p);
10455           }
10456           p = PieceToNumber((ChessSquare)p);
10457           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10458           board[p][BOARD_WIDTH-2]++;
10459           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10460         } else {
10461           p -= (int)WhitePawn;
10462           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10463                   captured = (ChessSquare) (DEMOTED(captured));
10464                   p = DEMOTED(p);
10465           }
10466           p = PieceToNumber((ChessSquare)p);
10467           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10468           board[BOARD_HEIGHT-1-p][1]++;
10469           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10470         }
10471       }
10472     } else if (gameInfo.variant == VariantAtomic) {
10473       if (captured != EmptySquare) {
10474         int y, x;
10475         for (y = toY-1; y <= toY+1; y++) {
10476           for (x = toX-1; x <= toX+1; x++) {
10477             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10478                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10479               board[y][x] = EmptySquare;
10480             }
10481           }
10482         }
10483         board[toY][toX] = EmptySquare;
10484       }
10485     }
10486
10487     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10488         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10489     } else
10490     if(promoChar == '+') {
10491         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10492         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10493         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10494           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10495     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10496         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10497         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10498            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10499         board[toY][toX] = newPiece;
10500     }
10501     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10502                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10503         // [HGM] superchess: take promotion piece out of holdings
10504         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10505         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10506             if(!--board[k][BOARD_WIDTH-2])
10507                 board[k][BOARD_WIDTH-1] = EmptySquare;
10508         } else {
10509             if(!--board[BOARD_HEIGHT-1-k][1])
10510                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10511         }
10512     }
10513 }
10514
10515 /* Updates forwardMostMove */
10516 void
10517 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10518 {
10519     int x = toX, y = toY;
10520     char *s = parseList[forwardMostMove];
10521     ChessSquare p = boards[forwardMostMove][toY][toX];
10522 //    forwardMostMove++; // [HGM] bare: moved downstream
10523
10524     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10525     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10526     (void) CoordsToAlgebraic(boards[forwardMostMove],
10527                              PosFlags(forwardMostMove),
10528                              fromY, fromX, y, x, (killX < 0)*promoChar,
10529                              s);
10530     if(kill2X >= 0 && kill2Y >= 0)
10531         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10532     if(killX >= 0 && killY >= 0)
10533         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10534                                            toX + AAA, toY + ONE - '0', promoChar);
10535
10536     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10537         int timeLeft; static int lastLoadFlag=0; int king, piece;
10538         piece = boards[forwardMostMove][fromY][fromX];
10539         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10540         if(gameInfo.variant == VariantKnightmate)
10541             king += (int) WhiteUnicorn - (int) WhiteKing;
10542         if(forwardMostMove == 0) {
10543             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10544                 fprintf(serverMoves, "%s;", UserName());
10545             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10546                 fprintf(serverMoves, "%s;", second.tidy);
10547             fprintf(serverMoves, "%s;", first.tidy);
10548             if(gameMode == MachinePlaysWhite)
10549                 fprintf(serverMoves, "%s;", UserName());
10550             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10551                 fprintf(serverMoves, "%s;", second.tidy);
10552         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10553         lastLoadFlag = loadFlag;
10554         // print base move
10555         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10556         // print castling suffix
10557         if( toY == fromY && piece == king ) {
10558             if(toX-fromX > 1)
10559                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10560             if(fromX-toX >1)
10561                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10562         }
10563         // e.p. suffix
10564         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10565              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10566              boards[forwardMostMove][toY][toX] == EmptySquare
10567              && fromX != toX && fromY != toY)
10568                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10569         // promotion suffix
10570         if(promoChar != NULLCHAR) {
10571             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10572                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10573                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10574             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10575         }
10576         if(!loadFlag) {
10577                 char buf[MOVE_LEN*2], *p; int len;
10578             fprintf(serverMoves, "/%d/%d",
10579                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10580             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10581             else                      timeLeft = blackTimeRemaining/1000;
10582             fprintf(serverMoves, "/%d", timeLeft);
10583                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10584                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10585                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10586                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10587             fprintf(serverMoves, "/%s", buf);
10588         }
10589         fflush(serverMoves);
10590     }
10591
10592     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10593         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10594       return;
10595     }
10596     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10597     if (commentList[forwardMostMove+1] != NULL) {
10598         free(commentList[forwardMostMove+1]);
10599         commentList[forwardMostMove+1] = NULL;
10600     }
10601     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10602     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10603     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10604     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10605     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10606     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10607     adjustedClock = FALSE;
10608     gameInfo.result = GameUnfinished;
10609     if (gameInfo.resultDetails != NULL) {
10610         free(gameInfo.resultDetails);
10611         gameInfo.resultDetails = NULL;
10612     }
10613     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10614                               moveList[forwardMostMove - 1]);
10615     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10616       case MT_NONE:
10617       case MT_STALEMATE:
10618       default:
10619         break;
10620       case MT_CHECK:
10621         if(!IS_SHOGI(gameInfo.variant))
10622             strcat(parseList[forwardMostMove - 1], "+");
10623         break;
10624       case MT_CHECKMATE:
10625       case MT_STAINMATE:
10626         strcat(parseList[forwardMostMove - 1], "#");
10627         break;
10628     }
10629 }
10630
10631 /* Updates currentMove if not pausing */
10632 void
10633 ShowMove (int fromX, int fromY, int toX, int toY)
10634 {
10635     int instant = (gameMode == PlayFromGameFile) ?
10636         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10637     if(appData.noGUI) return;
10638     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10639         if (!instant) {
10640             if (forwardMostMove == currentMove + 1) {
10641                 AnimateMove(boards[forwardMostMove - 1],
10642                             fromX, fromY, toX, toY);
10643             }
10644         }
10645         currentMove = forwardMostMove;
10646     }
10647
10648     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10649
10650     if (instant) return;
10651
10652     DisplayMove(currentMove - 1);
10653     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10654             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10655                 SetHighlights(fromX, fromY, toX, toY);
10656             }
10657     }
10658     DrawPosition(FALSE, boards[currentMove]);
10659     DisplayBothClocks();
10660     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10661 }
10662
10663 void
10664 SendEgtPath (ChessProgramState *cps)
10665 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10666         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10667
10668         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10669
10670         while(*p) {
10671             char c, *q = name+1, *r, *s;
10672
10673             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10674             while(*p && *p != ',') *q++ = *p++;
10675             *q++ = ':'; *q = 0;
10676             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10677                 strcmp(name, ",nalimov:") == 0 ) {
10678                 // take nalimov path from the menu-changeable option first, if it is defined
10679               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10680                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10681             } else
10682             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10683                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10684                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10685                 s = r = StrStr(s, ":") + 1; // beginning of path info
10686                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10687                 c = *r; *r = 0;             // temporarily null-terminate path info
10688                     *--q = 0;               // strip of trailig ':' from name
10689                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10690                 *r = c;
10691                 SendToProgram(buf,cps);     // send egtbpath command for this format
10692             }
10693             if(*p == ',') p++; // read away comma to position for next format name
10694         }
10695 }
10696
10697 static int
10698 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10699 {
10700       int width = 8, height = 8, holdings = 0;             // most common sizes
10701       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10702       // correct the deviations default for each variant
10703       if( v == VariantXiangqi ) width = 9,  height = 10;
10704       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10705       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10706       if( v == VariantCapablanca || v == VariantCapaRandom ||
10707           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10708                                 width = 10;
10709       if( v == VariantCourier ) width = 12;
10710       if( v == VariantSuper )                            holdings = 8;
10711       if( v == VariantGreat )   width = 10,              holdings = 8;
10712       if( v == VariantSChess )                           holdings = 7;
10713       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10714       if( v == VariantChuChess) width = 10, height = 10;
10715       if( v == VariantChu )     width = 12, height = 12;
10716       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10717              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10718              holdingsSize >= 0 && holdingsSize != holdings;
10719 }
10720
10721 char variantError[MSG_SIZ];
10722
10723 char *
10724 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10725 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10726       char *p, *variant = VariantName(v);
10727       static char b[MSG_SIZ];
10728       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10729            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10730                                                holdingsSize, variant); // cook up sized variant name
10731            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10732            if(StrStr(list, b) == NULL) {
10733                // specific sized variant not known, check if general sizing allowed
10734                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10735                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10736                             boardWidth, boardHeight, holdingsSize, engine);
10737                    return NULL;
10738                }
10739                /* [HGM] here we really should compare with the maximum supported board size */
10740            }
10741       } else snprintf(b, MSG_SIZ,"%s", variant);
10742       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10743       p = StrStr(list, b);
10744       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10745       if(p == NULL) {
10746           // occurs not at all in list, or only as sub-string
10747           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10748           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10749               int l = strlen(variantError);
10750               char *q;
10751               while(p != list && p[-1] != ',') p--;
10752               q = strchr(p, ',');
10753               if(q) *q = NULLCHAR;
10754               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10755               if(q) *q= ',';
10756           }
10757           return NULL;
10758       }
10759       return b;
10760 }
10761
10762 void
10763 InitChessProgram (ChessProgramState *cps, int setup)
10764 /* setup needed to setup FRC opening position */
10765 {
10766     char buf[MSG_SIZ], *b;
10767     if (appData.noChessProgram) return;
10768     hintRequested = FALSE;
10769     bookRequested = FALSE;
10770
10771     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10772     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10773     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10774     if(cps->memSize) { /* [HGM] memory */
10775       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10776         SendToProgram(buf, cps);
10777     }
10778     SendEgtPath(cps); /* [HGM] EGT */
10779     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10780       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10781         SendToProgram(buf, cps);
10782     }
10783
10784     setboardSpoiledMachineBlack = FALSE;
10785     SendToProgram(cps->initString, cps);
10786     if (gameInfo.variant != VariantNormal &&
10787         gameInfo.variant != VariantLoadable
10788         /* [HGM] also send variant if board size non-standard */
10789         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10790
10791       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10792                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10793
10794       if (b == NULL) {
10795         VariantClass v;
10796         char c, *q = cps->variants, *p = strchr(q, ',');
10797         if(p) *p = NULLCHAR;
10798         v = StringToVariant(q);
10799         DisplayError(variantError, 0);
10800         if(v != VariantUnknown && cps == &first) {
10801             int w, h, s;
10802             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10803                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10804             ASSIGN(appData.variant, q);
10805             Reset(TRUE, FALSE);
10806         }
10807         if(p) *p = ',';
10808         return;
10809       }
10810
10811       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10812       SendToProgram(buf, cps);
10813     }
10814     currentlyInitializedVariant = gameInfo.variant;
10815
10816     /* [HGM] send opening position in FRC to first engine */
10817     if(setup) {
10818           SendToProgram("force\n", cps);
10819           SendBoard(cps, 0);
10820           /* engine is now in force mode! Set flag to wake it up after first move. */
10821           setboardSpoiledMachineBlack = 1;
10822     }
10823
10824     if (cps->sendICS) {
10825       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10826       SendToProgram(buf, cps);
10827     }
10828     cps->maybeThinking = FALSE;
10829     cps->offeredDraw = 0;
10830     if (!appData.icsActive) {
10831         SendTimeControl(cps, movesPerSession, timeControl,
10832                         timeIncrement, appData.searchDepth,
10833                         searchTime);
10834     }
10835     if (appData.showThinking
10836         // [HGM] thinking: four options require thinking output to be sent
10837         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10838                                 ) {
10839         SendToProgram("post\n", cps);
10840     }
10841     SendToProgram("hard\n", cps);
10842     if (!appData.ponderNextMove) {
10843         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10844            it without being sure what state we are in first.  "hard"
10845            is not a toggle, so that one is OK.
10846          */
10847         SendToProgram("easy\n", cps);
10848     }
10849     if (cps->usePing) {
10850       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10851       SendToProgram(buf, cps);
10852     }
10853     cps->initDone = TRUE;
10854     ClearEngineOutputPane(cps == &second);
10855 }
10856
10857
10858 void
10859 ResendOptions (ChessProgramState *cps)
10860 { // send the stored value of the options
10861   int i;
10862   char buf[MSG_SIZ];
10863   Option *opt = cps->option;
10864   for(i=0; i<cps->nrOptions; i++, opt++) {
10865       switch(opt->type) {
10866         case Spin:
10867         case Slider:
10868         case CheckBox:
10869             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10870           break;
10871         case ComboBox:
10872           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10873           break;
10874         default:
10875             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10876           break;
10877         case Button:
10878         case SaveButton:
10879           continue;
10880       }
10881       SendToProgram(buf, cps);
10882   }
10883 }
10884
10885 void
10886 StartChessProgram (ChessProgramState *cps)
10887 {
10888     char buf[MSG_SIZ];
10889     int err;
10890
10891     if (appData.noChessProgram) return;
10892     cps->initDone = FALSE;
10893
10894     if (strcmp(cps->host, "localhost") == 0) {
10895         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10896     } else if (*appData.remoteShell == NULLCHAR) {
10897         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10898     } else {
10899         if (*appData.remoteUser == NULLCHAR) {
10900           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10901                     cps->program);
10902         } else {
10903           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10904                     cps->host, appData.remoteUser, cps->program);
10905         }
10906         err = StartChildProcess(buf, "", &cps->pr);
10907     }
10908
10909     if (err != 0) {
10910       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10911         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10912         if(cps != &first) return;
10913         appData.noChessProgram = TRUE;
10914         ThawUI();
10915         SetNCPMode();
10916 //      DisplayFatalError(buf, err, 1);
10917 //      cps->pr = NoProc;
10918 //      cps->isr = NULL;
10919         return;
10920     }
10921
10922     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10923     if (cps->protocolVersion > 1) {
10924       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10925       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10926         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10927         cps->comboCnt = 0;  //                and values of combo boxes
10928       }
10929       SendToProgram(buf, cps);
10930       if(cps->reload) ResendOptions(cps);
10931     } else {
10932       SendToProgram("xboard\n", cps);
10933     }
10934 }
10935
10936 void
10937 TwoMachinesEventIfReady P((void))
10938 {
10939   static int curMess = 0;
10940   if (first.lastPing != first.lastPong) {
10941     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10942     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10943     return;
10944   }
10945   if (second.lastPing != second.lastPong) {
10946     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10947     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10948     return;
10949   }
10950   DisplayMessage("", ""); curMess = 0;
10951   TwoMachinesEvent();
10952 }
10953
10954 char *
10955 MakeName (char *template)
10956 {
10957     time_t clock;
10958     struct tm *tm;
10959     static char buf[MSG_SIZ];
10960     char *p = buf;
10961     int i;
10962
10963     clock = time((time_t *)NULL);
10964     tm = localtime(&clock);
10965
10966     while(*p++ = *template++) if(p[-1] == '%') {
10967         switch(*template++) {
10968           case 0:   *p = 0; return buf;
10969           case 'Y': i = tm->tm_year+1900; break;
10970           case 'y': i = tm->tm_year-100; break;
10971           case 'M': i = tm->tm_mon+1; break;
10972           case 'd': i = tm->tm_mday; break;
10973           case 'h': i = tm->tm_hour; break;
10974           case 'm': i = tm->tm_min; break;
10975           case 's': i = tm->tm_sec; break;
10976           default:  i = 0;
10977         }
10978         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10979     }
10980     return buf;
10981 }
10982
10983 int
10984 CountPlayers (char *p)
10985 {
10986     int n = 0;
10987     while(p = strchr(p, '\n')) p++, n++; // count participants
10988     return n;
10989 }
10990
10991 FILE *
10992 WriteTourneyFile (char *results, FILE *f)
10993 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10994     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10995     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10996         // create a file with tournament description
10997         fprintf(f, "-participants {%s}\n", appData.participants);
10998         fprintf(f, "-seedBase %d\n", appData.seedBase);
10999         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11000         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11001         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11002         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11003         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11004         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11005         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11006         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11007         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11008         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11009         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11010         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11011         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11012         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11013         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11014         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11015         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11016         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11017         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11018         fprintf(f, "-smpCores %d\n", appData.smpCores);
11019         if(searchTime > 0)
11020                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11021         else {
11022                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11023                 fprintf(f, "-tc %s\n", appData.timeControl);
11024                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11025         }
11026         fprintf(f, "-results \"%s\"\n", results);
11027     }
11028     return f;
11029 }
11030
11031 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11032
11033 void
11034 Substitute (char *participants, int expunge)
11035 {
11036     int i, changed, changes=0, nPlayers=0;
11037     char *p, *q, *r, buf[MSG_SIZ];
11038     if(participants == NULL) return;
11039     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11040     r = p = participants; q = appData.participants;
11041     while(*p && *p == *q) {
11042         if(*p == '\n') r = p+1, nPlayers++;
11043         p++; q++;
11044     }
11045     if(*p) { // difference
11046         while(*p && *p++ != '\n');
11047         while(*q && *q++ != '\n');
11048       changed = nPlayers;
11049         changes = 1 + (strcmp(p, q) != 0);
11050     }
11051     if(changes == 1) { // a single engine mnemonic was changed
11052         q = r; while(*q) nPlayers += (*q++ == '\n');
11053         p = buf; while(*r && (*p = *r++) != '\n') p++;
11054         *p = NULLCHAR;
11055         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11056         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11057         if(mnemonic[i]) { // The substitute is valid
11058             FILE *f;
11059             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11060                 flock(fileno(f), LOCK_EX);
11061                 ParseArgsFromFile(f);
11062                 fseek(f, 0, SEEK_SET);
11063                 FREE(appData.participants); appData.participants = participants;
11064                 if(expunge) { // erase results of replaced engine
11065                     int len = strlen(appData.results), w, b, dummy;
11066                     for(i=0; i<len; i++) {
11067                         Pairing(i, nPlayers, &w, &b, &dummy);
11068                         if((w == changed || b == changed) && appData.results[i] == '*') {
11069                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11070                             fclose(f);
11071                             return;
11072                         }
11073                     }
11074                     for(i=0; i<len; i++) {
11075                         Pairing(i, nPlayers, &w, &b, &dummy);
11076                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11077                     }
11078                 }
11079                 WriteTourneyFile(appData.results, f);
11080                 fclose(f); // release lock
11081                 return;
11082             }
11083         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11084     }
11085     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11086     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11087     free(participants);
11088     return;
11089 }
11090
11091 int
11092 CheckPlayers (char *participants)
11093 {
11094         int i;
11095         char buf[MSG_SIZ], *p;
11096         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11097         while(p = strchr(participants, '\n')) {
11098             *p = NULLCHAR;
11099             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11100             if(!mnemonic[i]) {
11101                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11102                 *p = '\n';
11103                 DisplayError(buf, 0);
11104                 return 1;
11105             }
11106             *p = '\n';
11107             participants = p + 1;
11108         }
11109         return 0;
11110 }
11111
11112 int
11113 CreateTourney (char *name)
11114 {
11115         FILE *f;
11116         if(matchMode && strcmp(name, appData.tourneyFile)) {
11117              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11118         }
11119         if(name[0] == NULLCHAR) {
11120             if(appData.participants[0])
11121                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11122             return 0;
11123         }
11124         f = fopen(name, "r");
11125         if(f) { // file exists
11126             ASSIGN(appData.tourneyFile, name);
11127             ParseArgsFromFile(f); // parse it
11128         } else {
11129             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11130             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11131                 DisplayError(_("Not enough participants"), 0);
11132                 return 0;
11133             }
11134             if(CheckPlayers(appData.participants)) return 0;
11135             ASSIGN(appData.tourneyFile, name);
11136             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11137             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11138         }
11139         fclose(f);
11140         appData.noChessProgram = FALSE;
11141         appData.clockMode = TRUE;
11142         SetGNUMode();
11143         return 1;
11144 }
11145
11146 int
11147 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11148 {
11149     char buf[MSG_SIZ], *p, *q;
11150     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11151     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11152     skip = !all && group[0]; // if group requested, we start in skip mode
11153     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11154         p = names; q = buf; header = 0;
11155         while(*p && *p != '\n') *q++ = *p++;
11156         *q = 0;
11157         if(*p == '\n') p++;
11158         if(buf[0] == '#') {
11159             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11160             depth++; // we must be entering a new group
11161             if(all) continue; // suppress printing group headers when complete list requested
11162             header = 1;
11163             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11164         }
11165         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11166         if(engineList[i]) free(engineList[i]);
11167         engineList[i] = strdup(buf);
11168         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11169         if(engineMnemonic[i]) free(engineMnemonic[i]);
11170         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11171             strcat(buf, " (");
11172             sscanf(q + 8, "%s", buf + strlen(buf));
11173             strcat(buf, ")");
11174         }
11175         engineMnemonic[i] = strdup(buf);
11176         i++;
11177     }
11178     engineList[i] = engineMnemonic[i] = NULL;
11179     return i;
11180 }
11181
11182 // following implemented as macro to avoid type limitations
11183 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11184
11185 void
11186 SwapEngines (int n)
11187 {   // swap settings for first engine and other engine (so far only some selected options)
11188     int h;
11189     char *p;
11190     if(n == 0) return;
11191     SWAP(directory, p)
11192     SWAP(chessProgram, p)
11193     SWAP(isUCI, h)
11194     SWAP(hasOwnBookUCI, h)
11195     SWAP(protocolVersion, h)
11196     SWAP(reuse, h)
11197     SWAP(scoreIsAbsolute, h)
11198     SWAP(timeOdds, h)
11199     SWAP(logo, p)
11200     SWAP(pgnName, p)
11201     SWAP(pvSAN, h)
11202     SWAP(engOptions, p)
11203     SWAP(engInitString, p)
11204     SWAP(computerString, p)
11205     SWAP(features, p)
11206     SWAP(fenOverride, p)
11207     SWAP(NPS, h)
11208     SWAP(accumulateTC, h)
11209     SWAP(drawDepth, h)
11210     SWAP(host, p)
11211     SWAP(pseudo, h)
11212 }
11213
11214 int
11215 GetEngineLine (char *s, int n)
11216 {
11217     int i;
11218     char buf[MSG_SIZ];
11219     extern char *icsNames;
11220     if(!s || !*s) return 0;
11221     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11222     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11223     if(!mnemonic[i]) return 0;
11224     if(n == 11) return 1; // just testing if there was a match
11225     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11226     if(n == 1) SwapEngines(n);
11227     ParseArgsFromString(buf);
11228     if(n == 1) SwapEngines(n);
11229     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11230         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11231         ParseArgsFromString(buf);
11232     }
11233     return 1;
11234 }
11235
11236 int
11237 SetPlayer (int player, char *p)
11238 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11239     int i;
11240     char buf[MSG_SIZ], *engineName;
11241     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11242     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11243     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11244     if(mnemonic[i]) {
11245         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11246         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11247         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11248         ParseArgsFromString(buf);
11249     } else { // no engine with this nickname is installed!
11250         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11251         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11252         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11253         ModeHighlight();
11254         DisplayError(buf, 0);
11255         return 0;
11256     }
11257     free(engineName);
11258     return i;
11259 }
11260
11261 char *recentEngines;
11262
11263 void
11264 RecentEngineEvent (int nr)
11265 {
11266     int n;
11267 //    SwapEngines(1); // bump first to second
11268 //    ReplaceEngine(&second, 1); // and load it there
11269     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11270     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11271     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11272         ReplaceEngine(&first, 0);
11273         FloatToFront(&appData.recentEngineList, command[n]);
11274     }
11275 }
11276
11277 int
11278 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11279 {   // determine players from game number
11280     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11281
11282     if(appData.tourneyType == 0) {
11283         roundsPerCycle = (nPlayers - 1) | 1;
11284         pairingsPerRound = nPlayers / 2;
11285     } else if(appData.tourneyType > 0) {
11286         roundsPerCycle = nPlayers - appData.tourneyType;
11287         pairingsPerRound = appData.tourneyType;
11288     }
11289     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11290     gamesPerCycle = gamesPerRound * roundsPerCycle;
11291     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11292     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11293     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11294     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11295     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11296     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11297
11298     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11299     if(appData.roundSync) *syncInterval = gamesPerRound;
11300
11301     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11302
11303     if(appData.tourneyType == 0) {
11304         if(curPairing == (nPlayers-1)/2 ) {
11305             *whitePlayer = curRound;
11306             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11307         } else {
11308             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11309             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11310             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11311             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11312         }
11313     } else if(appData.tourneyType > 1) {
11314         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11315         *whitePlayer = curRound + appData.tourneyType;
11316     } else if(appData.tourneyType > 0) {
11317         *whitePlayer = curPairing;
11318         *blackPlayer = curRound + appData.tourneyType;
11319     }
11320
11321     // take care of white/black alternation per round.
11322     // For cycles and games this is already taken care of by default, derived from matchGame!
11323     return curRound & 1;
11324 }
11325
11326 int
11327 NextTourneyGame (int nr, int *swapColors)
11328 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11329     char *p, *q;
11330     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11331     FILE *tf;
11332     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11333     tf = fopen(appData.tourneyFile, "r");
11334     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11335     ParseArgsFromFile(tf); fclose(tf);
11336     InitTimeControls(); // TC might be altered from tourney file
11337
11338     nPlayers = CountPlayers(appData.participants); // count participants
11339     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11340     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11341
11342     if(syncInterval) {
11343         p = q = appData.results;
11344         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11345         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11346             DisplayMessage(_("Waiting for other game(s)"),"");
11347             waitingForGame = TRUE;
11348             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11349             return 0;
11350         }
11351         waitingForGame = FALSE;
11352     }
11353
11354     if(appData.tourneyType < 0) {
11355         if(nr>=0 && !pairingReceived) {
11356             char buf[1<<16];
11357             if(pairing.pr == NoProc) {
11358                 if(!appData.pairingEngine[0]) {
11359                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11360                     return 0;
11361                 }
11362                 StartChessProgram(&pairing); // starts the pairing engine
11363             }
11364             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11365             SendToProgram(buf, &pairing);
11366             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11367             SendToProgram(buf, &pairing);
11368             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11369         }
11370         pairingReceived = 0;                              // ... so we continue here
11371         *swapColors = 0;
11372         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11373         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11374         matchGame = 1; roundNr = nr / syncInterval + 1;
11375     }
11376
11377     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11378
11379     // redefine engines, engine dir, etc.
11380     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11381     if(first.pr == NoProc) {
11382       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11383       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11384     }
11385     if(second.pr == NoProc) {
11386       SwapEngines(1);
11387       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11388       SwapEngines(1);         // and make that valid for second engine by swapping
11389       InitEngine(&second, 1);
11390     }
11391     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11392     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11393     return OK;
11394 }
11395
11396 void
11397 NextMatchGame ()
11398 {   // performs game initialization that does not invoke engines, and then tries to start the game
11399     int res, firstWhite, swapColors = 0;
11400     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11401     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
11402         char buf[MSG_SIZ];
11403         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11404         if(strcmp(buf, currentDebugFile)) { // name has changed
11405             FILE *f = fopen(buf, "w");
11406             if(f) { // if opening the new file failed, just keep using the old one
11407                 ASSIGN(currentDebugFile, buf);
11408                 fclose(debugFP);
11409                 debugFP = f;
11410             }
11411             if(appData.serverFileName) {
11412                 if(serverFP) fclose(serverFP);
11413                 serverFP = fopen(appData.serverFileName, "w");
11414                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11415                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11416             }
11417         }
11418     }
11419     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11420     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11421     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11422     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11423     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11424     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11425     Reset(FALSE, first.pr != NoProc);
11426     res = LoadGameOrPosition(matchGame); // setup game
11427     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11428     if(!res) return; // abort when bad game/pos file
11429     TwoMachinesEvent();
11430 }
11431
11432 void
11433 UserAdjudicationEvent (int result)
11434 {
11435     ChessMove gameResult = GameIsDrawn;
11436
11437     if( result > 0 ) {
11438         gameResult = WhiteWins;
11439     }
11440     else if( result < 0 ) {
11441         gameResult = BlackWins;
11442     }
11443
11444     if( gameMode == TwoMachinesPlay ) {
11445         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11446     }
11447 }
11448
11449
11450 // [HGM] save: calculate checksum of game to make games easily identifiable
11451 int
11452 StringCheckSum (char *s)
11453 {
11454         int i = 0;
11455         if(s==NULL) return 0;
11456         while(*s) i = i*259 + *s++;
11457         return i;
11458 }
11459
11460 int
11461 GameCheckSum ()
11462 {
11463         int i, sum=0;
11464         for(i=backwardMostMove; i<forwardMostMove; i++) {
11465                 sum += pvInfoList[i].depth;
11466                 sum += StringCheckSum(parseList[i]);
11467                 sum += StringCheckSum(commentList[i]);
11468                 sum *= 261;
11469         }
11470         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11471         return sum + StringCheckSum(commentList[i]);
11472 } // end of save patch
11473
11474 void
11475 GameEnds (ChessMove result, char *resultDetails, int whosays)
11476 {
11477     GameMode nextGameMode;
11478     int isIcsGame;
11479     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11480
11481     if(endingGame) return; /* [HGM] crash: forbid recursion */
11482     endingGame = 1;
11483     if(twoBoards) { // [HGM] dual: switch back to one board
11484         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11485         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11486     }
11487     if (appData.debugMode) {
11488       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11489               result, resultDetails ? resultDetails : "(null)", whosays);
11490     }
11491
11492     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11493
11494     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11495
11496     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11497         /* If we are playing on ICS, the server decides when the
11498            game is over, but the engine can offer to draw, claim
11499            a draw, or resign.
11500          */
11501 #if ZIPPY
11502         if (appData.zippyPlay && first.initDone) {
11503             if (result == GameIsDrawn) {
11504                 /* In case draw still needs to be claimed */
11505                 SendToICS(ics_prefix);
11506                 SendToICS("draw\n");
11507             } else if (StrCaseStr(resultDetails, "resign")) {
11508                 SendToICS(ics_prefix);
11509                 SendToICS("resign\n");
11510             }
11511         }
11512 #endif
11513         endingGame = 0; /* [HGM] crash */
11514         return;
11515     }
11516
11517     /* If we're loading the game from a file, stop */
11518     if (whosays == GE_FILE) {
11519       (void) StopLoadGameTimer();
11520       gameFileFP = NULL;
11521     }
11522
11523     /* Cancel draw offers */
11524     first.offeredDraw = second.offeredDraw = 0;
11525
11526     /* If this is an ICS game, only ICS can really say it's done;
11527        if not, anyone can. */
11528     isIcsGame = (gameMode == IcsPlayingWhite ||
11529                  gameMode == IcsPlayingBlack ||
11530                  gameMode == IcsObserving    ||
11531                  gameMode == IcsExamining);
11532
11533     if (!isIcsGame || whosays == GE_ICS) {
11534         /* OK -- not an ICS game, or ICS said it was done */
11535         StopClocks();
11536         if (!isIcsGame && !appData.noChessProgram)
11537           SetUserThinkingEnables();
11538
11539         /* [HGM] if a machine claims the game end we verify this claim */
11540         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11541             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11542                 char claimer;
11543                 ChessMove trueResult = (ChessMove) -1;
11544
11545                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11546                                             first.twoMachinesColor[0] :
11547                                             second.twoMachinesColor[0] ;
11548
11549                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11550                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11551                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11552                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11553                 } else
11554                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11555                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11556                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11557                 } else
11558                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11559                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11560                 }
11561
11562                 // now verify win claims, but not in drop games, as we don't understand those yet
11563                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11564                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11565                     (result == WhiteWins && claimer == 'w' ||
11566                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11567                       if (appData.debugMode) {
11568                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11569                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11570                       }
11571                       if(result != trueResult) {
11572                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11573                               result = claimer == 'w' ? BlackWins : WhiteWins;
11574                               resultDetails = buf;
11575                       }
11576                 } else
11577                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11578                     && (forwardMostMove <= backwardMostMove ||
11579                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11580                         (claimer=='b')==(forwardMostMove&1))
11581                                                                                   ) {
11582                       /* [HGM] verify: draws that were not flagged are false claims */
11583                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11584                       result = claimer == 'w' ? BlackWins : WhiteWins;
11585                       resultDetails = buf;
11586                 }
11587                 /* (Claiming a loss is accepted no questions asked!) */
11588             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11589                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11590                 result = GameUnfinished;
11591                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11592             }
11593             /* [HGM] bare: don't allow bare King to win */
11594             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11595                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11596                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11597                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11598                && result != GameIsDrawn)
11599             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11600                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11601                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11602                         if(p >= 0 && p <= (int)WhiteKing) k++;
11603                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11604                 }
11605                 if (appData.debugMode) {
11606                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11607                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11608                 }
11609                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11610                         result = GameIsDrawn;
11611                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11612                         resultDetails = buf;
11613                 }
11614             }
11615         }
11616
11617
11618         if(serverMoves != NULL && !loadFlag) { char c = '=';
11619             if(result==WhiteWins) c = '+';
11620             if(result==BlackWins) c = '-';
11621             if(resultDetails != NULL)
11622                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11623         }
11624         if (resultDetails != NULL) {
11625             gameInfo.result = result;
11626             gameInfo.resultDetails = StrSave(resultDetails);
11627
11628             /* display last move only if game was not loaded from file */
11629             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11630                 DisplayMove(currentMove - 1);
11631
11632             if (forwardMostMove != 0) {
11633                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11634                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11635                                                                 ) {
11636                     if (*appData.saveGameFile != NULLCHAR) {
11637                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11638                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11639                         else
11640                         SaveGameToFile(appData.saveGameFile, TRUE);
11641                     } else if (appData.autoSaveGames) {
11642                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11643                     }
11644                     if (*appData.savePositionFile != NULLCHAR) {
11645                         SavePositionToFile(appData.savePositionFile);
11646                     }
11647                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11648                 }
11649             }
11650
11651             /* Tell program how game ended in case it is learning */
11652             /* [HGM] Moved this to after saving the PGN, just in case */
11653             /* engine died and we got here through time loss. In that */
11654             /* case we will get a fatal error writing the pipe, which */
11655             /* would otherwise lose us the PGN.                       */
11656             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11657             /* output during GameEnds should never be fatal anymore   */
11658             if (gameMode == MachinePlaysWhite ||
11659                 gameMode == MachinePlaysBlack ||
11660                 gameMode == TwoMachinesPlay ||
11661                 gameMode == IcsPlayingWhite ||
11662                 gameMode == IcsPlayingBlack ||
11663                 gameMode == BeginningOfGame) {
11664                 char buf[MSG_SIZ];
11665                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11666                         resultDetails);
11667                 if (first.pr != NoProc) {
11668                     SendToProgram(buf, &first);
11669                 }
11670                 if (second.pr != NoProc &&
11671                     gameMode == TwoMachinesPlay) {
11672                     SendToProgram(buf, &second);
11673                 }
11674             }
11675         }
11676
11677         if (appData.icsActive) {
11678             if (appData.quietPlay &&
11679                 (gameMode == IcsPlayingWhite ||
11680                  gameMode == IcsPlayingBlack)) {
11681                 SendToICS(ics_prefix);
11682                 SendToICS("set shout 1\n");
11683             }
11684             nextGameMode = IcsIdle;
11685             ics_user_moved = FALSE;
11686             /* clean up premove.  It's ugly when the game has ended and the
11687              * premove highlights are still on the board.
11688              */
11689             if (gotPremove) {
11690               gotPremove = FALSE;
11691               ClearPremoveHighlights();
11692               DrawPosition(FALSE, boards[currentMove]);
11693             }
11694             if (whosays == GE_ICS) {
11695                 switch (result) {
11696                 case WhiteWins:
11697                     if (gameMode == IcsPlayingWhite)
11698                         PlayIcsWinSound();
11699                     else if(gameMode == IcsPlayingBlack)
11700                         PlayIcsLossSound();
11701                     break;
11702                 case BlackWins:
11703                     if (gameMode == IcsPlayingBlack)
11704                         PlayIcsWinSound();
11705                     else if(gameMode == IcsPlayingWhite)
11706                         PlayIcsLossSound();
11707                     break;
11708                 case GameIsDrawn:
11709                     PlayIcsDrawSound();
11710                     break;
11711                 default:
11712                     PlayIcsUnfinishedSound();
11713                 }
11714             }
11715             if(appData.quitNext) { ExitEvent(0); return; }
11716         } else if (gameMode == EditGame ||
11717                    gameMode == PlayFromGameFile ||
11718                    gameMode == AnalyzeMode ||
11719                    gameMode == AnalyzeFile) {
11720             nextGameMode = gameMode;
11721         } else {
11722             nextGameMode = EndOfGame;
11723         }
11724         pausing = FALSE;
11725         ModeHighlight();
11726     } else {
11727         nextGameMode = gameMode;
11728     }
11729
11730     if (appData.noChessProgram) {
11731         gameMode = nextGameMode;
11732         ModeHighlight();
11733         endingGame = 0; /* [HGM] crash */
11734         return;
11735     }
11736
11737     if (first.reuse) {
11738         /* Put first chess program into idle state */
11739         if (first.pr != NoProc &&
11740             (gameMode == MachinePlaysWhite ||
11741              gameMode == MachinePlaysBlack ||
11742              gameMode == TwoMachinesPlay ||
11743              gameMode == IcsPlayingWhite ||
11744              gameMode == IcsPlayingBlack ||
11745              gameMode == BeginningOfGame)) {
11746             SendToProgram("force\n", &first);
11747             if (first.usePing) {
11748               char buf[MSG_SIZ];
11749               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11750               SendToProgram(buf, &first);
11751             }
11752         }
11753     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11754         /* Kill off first chess program */
11755         if (first.isr != NULL)
11756           RemoveInputSource(first.isr);
11757         first.isr = NULL;
11758
11759         if (first.pr != NoProc) {
11760             ExitAnalyzeMode();
11761             DoSleep( appData.delayBeforeQuit );
11762             SendToProgram("quit\n", &first);
11763             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11764             first.reload = TRUE;
11765         }
11766         first.pr = NoProc;
11767     }
11768     if (second.reuse) {
11769         /* Put second chess program into idle state */
11770         if (second.pr != NoProc &&
11771             gameMode == TwoMachinesPlay) {
11772             SendToProgram("force\n", &second);
11773             if (second.usePing) {
11774               char buf[MSG_SIZ];
11775               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11776               SendToProgram(buf, &second);
11777             }
11778         }
11779     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11780         /* Kill off second chess program */
11781         if (second.isr != NULL)
11782           RemoveInputSource(second.isr);
11783         second.isr = NULL;
11784
11785         if (second.pr != NoProc) {
11786             DoSleep( appData.delayBeforeQuit );
11787             SendToProgram("quit\n", &second);
11788             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11789             second.reload = TRUE;
11790         }
11791         second.pr = NoProc;
11792     }
11793
11794     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11795         char resChar = '=';
11796         switch (result) {
11797         case WhiteWins:
11798           resChar = '+';
11799           if (first.twoMachinesColor[0] == 'w') {
11800             first.matchWins++;
11801           } else {
11802             second.matchWins++;
11803           }
11804           break;
11805         case BlackWins:
11806           resChar = '-';
11807           if (first.twoMachinesColor[0] == 'b') {
11808             first.matchWins++;
11809           } else {
11810             second.matchWins++;
11811           }
11812           break;
11813         case GameUnfinished:
11814           resChar = ' ';
11815         default:
11816           break;
11817         }
11818
11819         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11820         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11821             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11822             ReserveGame(nextGame, resChar); // sets nextGame
11823             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11824             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11825         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11826
11827         if (nextGame <= appData.matchGames && !abortMatch) {
11828             gameMode = nextGameMode;
11829             matchGame = nextGame; // this will be overruled in tourney mode!
11830             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11831             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11832             endingGame = 0; /* [HGM] crash */
11833             return;
11834         } else {
11835             gameMode = nextGameMode;
11836             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11837                      first.tidy, second.tidy,
11838                      first.matchWins, second.matchWins,
11839                      appData.matchGames - (first.matchWins + second.matchWins));
11840             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11841             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11842             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11843             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11844                 first.twoMachinesColor = "black\n";
11845                 second.twoMachinesColor = "white\n";
11846             } else {
11847                 first.twoMachinesColor = "white\n";
11848                 second.twoMachinesColor = "black\n";
11849             }
11850         }
11851     }
11852     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11853         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11854       ExitAnalyzeMode();
11855     gameMode = nextGameMode;
11856     ModeHighlight();
11857     endingGame = 0;  /* [HGM] crash */
11858     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11859         if(matchMode == TRUE) { // match through command line: exit with or without popup
11860             if(ranking) {
11861                 ToNrEvent(forwardMostMove);
11862                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11863                 else ExitEvent(0);
11864             } else DisplayFatalError(buf, 0, 0);
11865         } else { // match through menu; just stop, with or without popup
11866             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11867             ModeHighlight();
11868             if(ranking){
11869                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11870             } else DisplayNote(buf);
11871       }
11872       if(ranking) free(ranking);
11873     }
11874 }
11875
11876 /* Assumes program was just initialized (initString sent).
11877    Leaves program in force mode. */
11878 void
11879 FeedMovesToProgram (ChessProgramState *cps, int upto)
11880 {
11881     int i;
11882
11883     if (appData.debugMode)
11884       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11885               startedFromSetupPosition ? "position and " : "",
11886               backwardMostMove, upto, cps->which);
11887     if(currentlyInitializedVariant != gameInfo.variant) {
11888       char buf[MSG_SIZ];
11889         // [HGM] variantswitch: make engine aware of new variant
11890         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11891                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11892                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11893         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11894         SendToProgram(buf, cps);
11895         currentlyInitializedVariant = gameInfo.variant;
11896     }
11897     SendToProgram("force\n", cps);
11898     if (startedFromSetupPosition) {
11899         SendBoard(cps, backwardMostMove);
11900     if (appData.debugMode) {
11901         fprintf(debugFP, "feedMoves\n");
11902     }
11903     }
11904     for (i = backwardMostMove; i < upto; i++) {
11905         SendMoveToProgram(i, cps);
11906     }
11907 }
11908
11909
11910 int
11911 ResurrectChessProgram ()
11912 {
11913      /* The chess program may have exited.
11914         If so, restart it and feed it all the moves made so far. */
11915     static int doInit = 0;
11916
11917     if (appData.noChessProgram) return 1;
11918
11919     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11920         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11921         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11922         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11923     } else {
11924         if (first.pr != NoProc) return 1;
11925         StartChessProgram(&first);
11926     }
11927     InitChessProgram(&first, FALSE);
11928     FeedMovesToProgram(&first, currentMove);
11929
11930     if (!first.sendTime) {
11931         /* can't tell gnuchess what its clock should read,
11932            so we bow to its notion. */
11933         ResetClocks();
11934         timeRemaining[0][currentMove] = whiteTimeRemaining;
11935         timeRemaining[1][currentMove] = blackTimeRemaining;
11936     }
11937
11938     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11939                 appData.icsEngineAnalyze) && first.analysisSupport) {
11940       SendToProgram("analyze\n", &first);
11941       first.analyzing = TRUE;
11942     }
11943     return 1;
11944 }
11945
11946 /*
11947  * Button procedures
11948  */
11949 void
11950 Reset (int redraw, int init)
11951 {
11952     int i;
11953
11954     if (appData.debugMode) {
11955         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11956                 redraw, init, gameMode);
11957     }
11958     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11959     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11960     CleanupTail(); // [HGM] vari: delete any stored variations
11961     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11962     pausing = pauseExamInvalid = FALSE;
11963     startedFromSetupPosition = blackPlaysFirst = FALSE;
11964     firstMove = TRUE;
11965     whiteFlag = blackFlag = FALSE;
11966     userOfferedDraw = FALSE;
11967     hintRequested = bookRequested = FALSE;
11968     first.maybeThinking = FALSE;
11969     second.maybeThinking = FALSE;
11970     first.bookSuspend = FALSE; // [HGM] book
11971     second.bookSuspend = FALSE;
11972     thinkOutput[0] = NULLCHAR;
11973     lastHint[0] = NULLCHAR;
11974     ClearGameInfo(&gameInfo);
11975     gameInfo.variant = StringToVariant(appData.variant);
11976     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
11977         gameInfo.variant = VariantUnknown;
11978         strncpy(engineVariant, appData.variant, MSG_SIZ);
11979     }
11980     ics_user_moved = ics_clock_paused = FALSE;
11981     ics_getting_history = H_FALSE;
11982     ics_gamenum = -1;
11983     white_holding[0] = black_holding[0] = NULLCHAR;
11984     ClearProgramStats();
11985     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11986
11987     ResetFrontEnd();
11988     ClearHighlights();
11989     flipView = appData.flipView;
11990     ClearPremoveHighlights();
11991     gotPremove = FALSE;
11992     alarmSounded = FALSE;
11993     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
11994
11995     GameEnds(EndOfFile, NULL, GE_PLAYER);
11996     if(appData.serverMovesName != NULL) {
11997         /* [HGM] prepare to make moves file for broadcasting */
11998         clock_t t = clock();
11999         if(serverMoves != NULL) fclose(serverMoves);
12000         serverMoves = fopen(appData.serverMovesName, "r");
12001         if(serverMoves != NULL) {
12002             fclose(serverMoves);
12003             /* delay 15 sec before overwriting, so all clients can see end */
12004             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12005         }
12006         serverMoves = fopen(appData.serverMovesName, "w");
12007     }
12008
12009     ExitAnalyzeMode();
12010     gameMode = BeginningOfGame;
12011     ModeHighlight();
12012     if(appData.icsActive) gameInfo.variant = VariantNormal;
12013     currentMove = forwardMostMove = backwardMostMove = 0;
12014     MarkTargetSquares(1);
12015     InitPosition(redraw);
12016     for (i = 0; i < MAX_MOVES; i++) {
12017         if (commentList[i] != NULL) {
12018             free(commentList[i]);
12019             commentList[i] = NULL;
12020         }
12021     }
12022     ResetClocks();
12023     timeRemaining[0][0] = whiteTimeRemaining;
12024     timeRemaining[1][0] = blackTimeRemaining;
12025
12026     if (first.pr == NoProc) {
12027         StartChessProgram(&first);
12028     }
12029     if (init) {
12030             InitChessProgram(&first, startedFromSetupPosition);
12031     }
12032     DisplayTitle("");
12033     DisplayMessage("", "");
12034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12035     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12036     ClearMap();        // [HGM] exclude: invalidate map
12037 }
12038
12039 void
12040 AutoPlayGameLoop ()
12041 {
12042     for (;;) {
12043         if (!AutoPlayOneMove())
12044           return;
12045         if (matchMode || appData.timeDelay == 0)
12046           continue;
12047         if (appData.timeDelay < 0)
12048           return;
12049         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12050         break;
12051     }
12052 }
12053
12054 void
12055 AnalyzeNextGame()
12056 {
12057     ReloadGame(1); // next game
12058 }
12059
12060 int
12061 AutoPlayOneMove ()
12062 {
12063     int fromX, fromY, toX, toY;
12064
12065     if (appData.debugMode) {
12066       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12067     }
12068
12069     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12070       return FALSE;
12071
12072     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12073       pvInfoList[currentMove].depth = programStats.depth;
12074       pvInfoList[currentMove].score = programStats.score;
12075       pvInfoList[currentMove].time  = 0;
12076       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12077       else { // append analysis of final position as comment
12078         char buf[MSG_SIZ];
12079         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12080         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12081       }
12082       programStats.depth = 0;
12083     }
12084
12085     if (currentMove >= forwardMostMove) {
12086       if(gameMode == AnalyzeFile) {
12087           if(appData.loadGameIndex == -1) {
12088             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12089           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12090           } else {
12091           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12092         }
12093       }
12094 //      gameMode = EndOfGame;
12095 //      ModeHighlight();
12096
12097       /* [AS] Clear current move marker at the end of a game */
12098       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12099
12100       return FALSE;
12101     }
12102
12103     toX = moveList[currentMove][2] - AAA;
12104     toY = moveList[currentMove][3] - ONE;
12105
12106     if (moveList[currentMove][1] == '@') {
12107         if (appData.highlightLastMove) {
12108             SetHighlights(-1, -1, toX, toY);
12109         }
12110     } else {
12111         fromX = moveList[currentMove][0] - AAA;
12112         fromY = moveList[currentMove][1] - ONE;
12113
12114         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12115
12116         if(moveList[currentMove][4] == ';') { // multi-leg
12117             killX = moveList[currentMove][5] - AAA;
12118             killY = moveList[currentMove][6] - ONE;
12119         }
12120         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12121         killX = killY = -1;
12122
12123         if (appData.highlightLastMove) {
12124             SetHighlights(fromX, fromY, toX, toY);
12125         }
12126     }
12127     DisplayMove(currentMove);
12128     SendMoveToProgram(currentMove++, &first);
12129     DisplayBothClocks();
12130     DrawPosition(FALSE, boards[currentMove]);
12131     // [HGM] PV info: always display, routine tests if empty
12132     DisplayComment(currentMove - 1, commentList[currentMove]);
12133     return TRUE;
12134 }
12135
12136
12137 int
12138 LoadGameOneMove (ChessMove readAhead)
12139 {
12140     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12141     char promoChar = NULLCHAR;
12142     ChessMove moveType;
12143     char move[MSG_SIZ];
12144     char *p, *q;
12145
12146     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12147         gameMode != AnalyzeMode && gameMode != Training) {
12148         gameFileFP = NULL;
12149         return FALSE;
12150     }
12151
12152     yyboardindex = forwardMostMove;
12153     if (readAhead != EndOfFile) {
12154       moveType = readAhead;
12155     } else {
12156       if (gameFileFP == NULL)
12157           return FALSE;
12158       moveType = (ChessMove) Myylex();
12159     }
12160
12161     done = FALSE;
12162     switch (moveType) {
12163       case Comment:
12164         if (appData.debugMode)
12165           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12166         p = yy_text;
12167
12168         /* append the comment but don't display it */
12169         AppendComment(currentMove, p, FALSE);
12170         return TRUE;
12171
12172       case WhiteCapturesEnPassant:
12173       case BlackCapturesEnPassant:
12174       case WhitePromotion:
12175       case BlackPromotion:
12176       case WhiteNonPromotion:
12177       case BlackNonPromotion:
12178       case NormalMove:
12179       case FirstLeg:
12180       case WhiteKingSideCastle:
12181       case WhiteQueenSideCastle:
12182       case BlackKingSideCastle:
12183       case BlackQueenSideCastle:
12184       case WhiteKingSideCastleWild:
12185       case WhiteQueenSideCastleWild:
12186       case BlackKingSideCastleWild:
12187       case BlackQueenSideCastleWild:
12188       /* PUSH Fabien */
12189       case WhiteHSideCastleFR:
12190       case WhiteASideCastleFR:
12191       case BlackHSideCastleFR:
12192       case BlackASideCastleFR:
12193       /* POP Fabien */
12194         if (appData.debugMode)
12195           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12196         fromX = currentMoveString[0] - AAA;
12197         fromY = currentMoveString[1] - ONE;
12198         toX = currentMoveString[2] - AAA;
12199         toY = currentMoveString[3] - ONE;
12200         promoChar = currentMoveString[4];
12201         if(promoChar == ';') promoChar = currentMoveString[7];
12202         break;
12203
12204       case WhiteDrop:
12205       case BlackDrop:
12206         if (appData.debugMode)
12207           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12208         fromX = moveType == WhiteDrop ?
12209           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12210         (int) CharToPiece(ToLower(currentMoveString[0]));
12211         fromY = DROP_RANK;
12212         toX = currentMoveString[2] - AAA;
12213         toY = currentMoveString[3] - ONE;
12214         break;
12215
12216       case WhiteWins:
12217       case BlackWins:
12218       case GameIsDrawn:
12219       case GameUnfinished:
12220         if (appData.debugMode)
12221           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12222         p = strchr(yy_text, '{');
12223         if (p == NULL) p = strchr(yy_text, '(');
12224         if (p == NULL) {
12225             p = yy_text;
12226             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12227         } else {
12228             q = strchr(p, *p == '{' ? '}' : ')');
12229             if (q != NULL) *q = NULLCHAR;
12230             p++;
12231         }
12232         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12233         GameEnds(moveType, p, GE_FILE);
12234         done = TRUE;
12235         if (cmailMsgLoaded) {
12236             ClearHighlights();
12237             flipView = WhiteOnMove(currentMove);
12238             if (moveType == GameUnfinished) flipView = !flipView;
12239             if (appData.debugMode)
12240               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12241         }
12242         break;
12243
12244       case EndOfFile:
12245         if (appData.debugMode)
12246           fprintf(debugFP, "Parser hit end of file\n");
12247         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12248           case MT_NONE:
12249           case MT_CHECK:
12250             break;
12251           case MT_CHECKMATE:
12252           case MT_STAINMATE:
12253             if (WhiteOnMove(currentMove)) {
12254                 GameEnds(BlackWins, "Black mates", GE_FILE);
12255             } else {
12256                 GameEnds(WhiteWins, "White mates", GE_FILE);
12257             }
12258             break;
12259           case MT_STALEMATE:
12260             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12261             break;
12262         }
12263         done = TRUE;
12264         break;
12265
12266       case MoveNumberOne:
12267         if (lastLoadGameStart == GNUChessGame) {
12268             /* GNUChessGames have numbers, but they aren't move numbers */
12269             if (appData.debugMode)
12270               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12271                       yy_text, (int) moveType);
12272             return LoadGameOneMove(EndOfFile); /* tail recursion */
12273         }
12274         /* else fall thru */
12275
12276       case XBoardGame:
12277       case GNUChessGame:
12278       case PGNTag:
12279         /* Reached start of next game in file */
12280         if (appData.debugMode)
12281           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12282         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12283           case MT_NONE:
12284           case MT_CHECK:
12285             break;
12286           case MT_CHECKMATE:
12287           case MT_STAINMATE:
12288             if (WhiteOnMove(currentMove)) {
12289                 GameEnds(BlackWins, "Black mates", GE_FILE);
12290             } else {
12291                 GameEnds(WhiteWins, "White mates", GE_FILE);
12292             }
12293             break;
12294           case MT_STALEMATE:
12295             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12296             break;
12297         }
12298         done = TRUE;
12299         break;
12300
12301       case PositionDiagram:     /* should not happen; ignore */
12302       case ElapsedTime:         /* ignore */
12303       case NAG:                 /* ignore */
12304         if (appData.debugMode)
12305           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12306                   yy_text, (int) moveType);
12307         return LoadGameOneMove(EndOfFile); /* tail recursion */
12308
12309       case IllegalMove:
12310         if (appData.testLegality) {
12311             if (appData.debugMode)
12312               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12313             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12314                     (forwardMostMove / 2) + 1,
12315                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12316             DisplayError(move, 0);
12317             done = TRUE;
12318         } else {
12319             if (appData.debugMode)
12320               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12321                       yy_text, currentMoveString);
12322             if(currentMoveString[1] == '@') {
12323                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12324                 fromY = DROP_RANK;
12325             } else {
12326                 fromX = currentMoveString[0] - AAA;
12327                 fromY = currentMoveString[1] - ONE;
12328             }
12329             toX = currentMoveString[2] - AAA;
12330             toY = currentMoveString[3] - ONE;
12331             promoChar = currentMoveString[4];
12332         }
12333         break;
12334
12335       case AmbiguousMove:
12336         if (appData.debugMode)
12337           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12338         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12339                 (forwardMostMove / 2) + 1,
12340                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12341         DisplayError(move, 0);
12342         done = TRUE;
12343         break;
12344
12345       default:
12346       case ImpossibleMove:
12347         if (appData.debugMode)
12348           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12349         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12350                 (forwardMostMove / 2) + 1,
12351                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12352         DisplayError(move, 0);
12353         done = TRUE;
12354         break;
12355     }
12356
12357     if (done) {
12358         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12359             DrawPosition(FALSE, boards[currentMove]);
12360             DisplayBothClocks();
12361             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12362               DisplayComment(currentMove - 1, commentList[currentMove]);
12363         }
12364         (void) StopLoadGameTimer();
12365         gameFileFP = NULL;
12366         cmailOldMove = forwardMostMove;
12367         return FALSE;
12368     } else {
12369         /* currentMoveString is set as a side-effect of yylex */
12370
12371         thinkOutput[0] = NULLCHAR;
12372         MakeMove(fromX, fromY, toX, toY, promoChar);
12373         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12374         currentMove = forwardMostMove;
12375         return TRUE;
12376     }
12377 }
12378
12379 /* Load the nth game from the given file */
12380 int
12381 LoadGameFromFile (char *filename, int n, char *title, int useList)
12382 {
12383     FILE *f;
12384     char buf[MSG_SIZ];
12385
12386     if (strcmp(filename, "-") == 0) {
12387         f = stdin;
12388         title = "stdin";
12389     } else {
12390         f = fopen(filename, "rb");
12391         if (f == NULL) {
12392           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12393             DisplayError(buf, errno);
12394             return FALSE;
12395         }
12396     }
12397     if (fseek(f, 0, 0) == -1) {
12398         /* f is not seekable; probably a pipe */
12399         useList = FALSE;
12400     }
12401     if (useList && n == 0) {
12402         int error = GameListBuild(f);
12403         if (error) {
12404             DisplayError(_("Cannot build game list"), error);
12405         } else if (!ListEmpty(&gameList) &&
12406                    ((ListGame *) gameList.tailPred)->number > 1) {
12407             GameListPopUp(f, title);
12408             return TRUE;
12409         }
12410         GameListDestroy();
12411         n = 1;
12412     }
12413     if (n == 0) n = 1;
12414     return LoadGame(f, n, title, FALSE);
12415 }
12416
12417
12418 void
12419 MakeRegisteredMove ()
12420 {
12421     int fromX, fromY, toX, toY;
12422     char promoChar;
12423     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12424         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12425           case CMAIL_MOVE:
12426           case CMAIL_DRAW:
12427             if (appData.debugMode)
12428               fprintf(debugFP, "Restoring %s for game %d\n",
12429                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12430
12431             thinkOutput[0] = NULLCHAR;
12432             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12433             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12434             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12435             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12436             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12437             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12438             MakeMove(fromX, fromY, toX, toY, promoChar);
12439             ShowMove(fromX, fromY, toX, toY);
12440
12441             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12442               case MT_NONE:
12443               case MT_CHECK:
12444                 break;
12445
12446               case MT_CHECKMATE:
12447               case MT_STAINMATE:
12448                 if (WhiteOnMove(currentMove)) {
12449                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12450                 } else {
12451                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12452                 }
12453                 break;
12454
12455               case MT_STALEMATE:
12456                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12457                 break;
12458             }
12459
12460             break;
12461
12462           case CMAIL_RESIGN:
12463             if (WhiteOnMove(currentMove)) {
12464                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12465             } else {
12466                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12467             }
12468             break;
12469
12470           case CMAIL_ACCEPT:
12471             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12472             break;
12473
12474           default:
12475             break;
12476         }
12477     }
12478
12479     return;
12480 }
12481
12482 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12483 int
12484 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12485 {
12486     int retVal;
12487
12488     if (gameNumber > nCmailGames) {
12489         DisplayError(_("No more games in this message"), 0);
12490         return FALSE;
12491     }
12492     if (f == lastLoadGameFP) {
12493         int offset = gameNumber - lastLoadGameNumber;
12494         if (offset == 0) {
12495             cmailMsg[0] = NULLCHAR;
12496             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12497                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12498                 nCmailMovesRegistered--;
12499             }
12500             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12501             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12502                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12503             }
12504         } else {
12505             if (! RegisterMove()) return FALSE;
12506         }
12507     }
12508
12509     retVal = LoadGame(f, gameNumber, title, useList);
12510
12511     /* Make move registered during previous look at this game, if any */
12512     MakeRegisteredMove();
12513
12514     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12515         commentList[currentMove]
12516           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12517         DisplayComment(currentMove - 1, commentList[currentMove]);
12518     }
12519
12520     return retVal;
12521 }
12522
12523 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12524 int
12525 ReloadGame (int offset)
12526 {
12527     int gameNumber = lastLoadGameNumber + offset;
12528     if (lastLoadGameFP == NULL) {
12529         DisplayError(_("No game has been loaded yet"), 0);
12530         return FALSE;
12531     }
12532     if (gameNumber <= 0) {
12533         DisplayError(_("Can't back up any further"), 0);
12534         return FALSE;
12535     }
12536     if (cmailMsgLoaded) {
12537         return CmailLoadGame(lastLoadGameFP, gameNumber,
12538                              lastLoadGameTitle, lastLoadGameUseList);
12539     } else {
12540         return LoadGame(lastLoadGameFP, gameNumber,
12541                         lastLoadGameTitle, lastLoadGameUseList);
12542     }
12543 }
12544
12545 int keys[EmptySquare+1];
12546
12547 int
12548 PositionMatches (Board b1, Board b2)
12549 {
12550     int r, f, sum=0;
12551     switch(appData.searchMode) {
12552         case 1: return CompareWithRights(b1, b2);
12553         case 2:
12554             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12555                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12556             }
12557             return TRUE;
12558         case 3:
12559             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12560               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12561                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12562             }
12563             return sum==0;
12564         case 4:
12565             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12566                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12567             }
12568             return sum==0;
12569     }
12570     return TRUE;
12571 }
12572
12573 #define Q_PROMO  4
12574 #define Q_EP     3
12575 #define Q_BCASTL 2
12576 #define Q_WCASTL 1
12577
12578 int pieceList[256], quickBoard[256];
12579 ChessSquare pieceType[256] = { EmptySquare };
12580 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12581 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12582 int soughtTotal, turn;
12583 Boolean epOK, flipSearch;
12584
12585 typedef struct {
12586     unsigned char piece, to;
12587 } Move;
12588
12589 #define DSIZE (250000)
12590
12591 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12592 Move *moveDatabase = initialSpace;
12593 unsigned int movePtr, dataSize = DSIZE;
12594
12595 int
12596 MakePieceList (Board board, int *counts)
12597 {
12598     int r, f, n=Q_PROMO, total=0;
12599     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12600     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12601         int sq = f + (r<<4);
12602         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12603             quickBoard[sq] = ++n;
12604             pieceList[n] = sq;
12605             pieceType[n] = board[r][f];
12606             counts[board[r][f]]++;
12607             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12608             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12609             total++;
12610         }
12611     }
12612     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12613     return total;
12614 }
12615
12616 void
12617 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12618 {
12619     int sq = fromX + (fromY<<4);
12620     int piece = quickBoard[sq], rook;
12621     quickBoard[sq] = 0;
12622     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12623     if(piece == pieceList[1] && fromY == toY) {
12624       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12625         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12626         moveDatabase[movePtr++].piece = Q_WCASTL;
12627         quickBoard[sq] = piece;
12628         piece = quickBoard[from]; quickBoard[from] = 0;
12629         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12630       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12631         quickBoard[sq] = 0; // remove Rook
12632         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12633         moveDatabase[movePtr++].piece = Q_WCASTL;
12634         quickBoard[sq] = pieceList[1]; // put King
12635         piece = rook;
12636         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12637       }
12638     } else
12639     if(piece == pieceList[2] && fromY == toY) {
12640       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12641         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12642         moveDatabase[movePtr++].piece = Q_BCASTL;
12643         quickBoard[sq] = piece;
12644         piece = quickBoard[from]; quickBoard[from] = 0;
12645         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12646       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12647         quickBoard[sq] = 0; // remove Rook
12648         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12649         moveDatabase[movePtr++].piece = Q_BCASTL;
12650         quickBoard[sq] = pieceList[2]; // put King
12651         piece = rook;
12652         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12653       }
12654     } else
12655     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12656         quickBoard[(fromY<<4)+toX] = 0;
12657         moveDatabase[movePtr].piece = Q_EP;
12658         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12659         moveDatabase[movePtr].to = sq;
12660     } else
12661     if(promoPiece != pieceType[piece]) {
12662         moveDatabase[movePtr++].piece = Q_PROMO;
12663         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12664     }
12665     moveDatabase[movePtr].piece = piece;
12666     quickBoard[sq] = piece;
12667     movePtr++;
12668 }
12669
12670 int
12671 PackGame (Board board)
12672 {
12673     Move *newSpace = NULL;
12674     moveDatabase[movePtr].piece = 0; // terminate previous game
12675     if(movePtr > dataSize) {
12676         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12677         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12678         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12679         if(newSpace) {
12680             int i;
12681             Move *p = moveDatabase, *q = newSpace;
12682             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12683             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12684             moveDatabase = newSpace;
12685         } else { // calloc failed, we must be out of memory. Too bad...
12686             dataSize = 0; // prevent calloc events for all subsequent games
12687             return 0;     // and signal this one isn't cached
12688         }
12689     }
12690     movePtr++;
12691     MakePieceList(board, counts);
12692     return movePtr;
12693 }
12694
12695 int
12696 QuickCompare (Board board, int *minCounts, int *maxCounts)
12697 {   // compare according to search mode
12698     int r, f;
12699     switch(appData.searchMode)
12700     {
12701       case 1: // exact position match
12702         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12703         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12704             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12705         }
12706         break;
12707       case 2: // can have extra material on empty squares
12708         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12709             if(board[r][f] == EmptySquare) continue;
12710             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12711         }
12712         break;
12713       case 3: // material with exact Pawn structure
12714         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12715             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12716             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12717         } // fall through to material comparison
12718       case 4: // exact material
12719         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12720         break;
12721       case 6: // material range with given imbalance
12722         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12723         // fall through to range comparison
12724       case 5: // material range
12725         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12726     }
12727     return TRUE;
12728 }
12729
12730 int
12731 QuickScan (Board board, Move *move)
12732 {   // reconstruct game,and compare all positions in it
12733     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12734     do {
12735         int piece = move->piece;
12736         int to = move->to, from = pieceList[piece];
12737         if(found < 0) { // if already found just scan to game end for final piece count
12738           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12739            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12740            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12741                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12742             ) {
12743             static int lastCounts[EmptySquare+1];
12744             int i;
12745             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12746             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12747           } else stretch = 0;
12748           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12749           if(found >= 0 && !appData.minPieces) return found;
12750         }
12751         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12752           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12753           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12754             piece = (++move)->piece;
12755             from = pieceList[piece];
12756             counts[pieceType[piece]]--;
12757             pieceType[piece] = (ChessSquare) move->to;
12758             counts[move->to]++;
12759           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12760             counts[pieceType[quickBoard[to]]]--;
12761             quickBoard[to] = 0; total--;
12762             move++;
12763             continue;
12764           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12765             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12766             from  = pieceList[piece]; // so this must be King
12767             quickBoard[from] = 0;
12768             pieceList[piece] = to;
12769             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12770             quickBoard[from] = 0; // rook
12771             quickBoard[to] = piece;
12772             to = move->to; piece = move->piece;
12773             goto aftercastle;
12774           }
12775         }
12776         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12777         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12778         quickBoard[from] = 0;
12779       aftercastle:
12780         quickBoard[to] = piece;
12781         pieceList[piece] = to;
12782         cnt++; turn ^= 3;
12783         move++;
12784     } while(1);
12785 }
12786
12787 void
12788 InitSearch ()
12789 {
12790     int r, f;
12791     flipSearch = FALSE;
12792     CopyBoard(soughtBoard, boards[currentMove]);
12793     soughtTotal = MakePieceList(soughtBoard, maxSought);
12794     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12795     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12796     CopyBoard(reverseBoard, boards[currentMove]);
12797     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12798         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12799         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12800         reverseBoard[r][f] = piece;
12801     }
12802     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12803     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12804     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12805                  || (boards[currentMove][CASTLING][2] == NoRights ||
12806                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12807                  && (boards[currentMove][CASTLING][5] == NoRights ||
12808                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12809       ) {
12810         flipSearch = TRUE;
12811         CopyBoard(flipBoard, soughtBoard);
12812         CopyBoard(rotateBoard, reverseBoard);
12813         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12814             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12815             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12816         }
12817     }
12818     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12819     if(appData.searchMode >= 5) {
12820         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12821         MakePieceList(soughtBoard, minSought);
12822         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12823     }
12824     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12825         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12826 }
12827
12828 GameInfo dummyInfo;
12829 static int creatingBook;
12830
12831 int
12832 GameContainsPosition (FILE *f, ListGame *lg)
12833 {
12834     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12835     int fromX, fromY, toX, toY;
12836     char promoChar;
12837     static int initDone=FALSE;
12838
12839     // weed out games based on numerical tag comparison
12840     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12841     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12842     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12843     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12844     if(!initDone) {
12845         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12846         initDone = TRUE;
12847     }
12848     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12849     else CopyBoard(boards[scratch], initialPosition); // default start position
12850     if(lg->moves) {
12851         turn = btm + 1;
12852         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12853         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12854     }
12855     if(btm) plyNr++;
12856     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12857     fseek(f, lg->offset, 0);
12858     yynewfile(f);
12859     while(1) {
12860         yyboardindex = scratch;
12861         quickFlag = plyNr+1;
12862         next = Myylex();
12863         quickFlag = 0;
12864         switch(next) {
12865             case PGNTag:
12866                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12867             default:
12868                 continue;
12869
12870             case XBoardGame:
12871             case GNUChessGame:
12872                 if(plyNr) return -1; // after we have seen moves, this is for new game
12873               continue;
12874
12875             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12876             case ImpossibleMove:
12877             case WhiteWins: // game ends here with these four
12878             case BlackWins:
12879             case GameIsDrawn:
12880             case GameUnfinished:
12881                 return -1;
12882
12883             case IllegalMove:
12884                 if(appData.testLegality) return -1;
12885             case WhiteCapturesEnPassant:
12886             case BlackCapturesEnPassant:
12887             case WhitePromotion:
12888             case BlackPromotion:
12889             case WhiteNonPromotion:
12890             case BlackNonPromotion:
12891             case NormalMove:
12892             case FirstLeg:
12893             case WhiteKingSideCastle:
12894             case WhiteQueenSideCastle:
12895             case BlackKingSideCastle:
12896             case BlackQueenSideCastle:
12897             case WhiteKingSideCastleWild:
12898             case WhiteQueenSideCastleWild:
12899             case BlackKingSideCastleWild:
12900             case BlackQueenSideCastleWild:
12901             case WhiteHSideCastleFR:
12902             case WhiteASideCastleFR:
12903             case BlackHSideCastleFR:
12904             case BlackASideCastleFR:
12905                 fromX = currentMoveString[0] - AAA;
12906                 fromY = currentMoveString[1] - ONE;
12907                 toX = currentMoveString[2] - AAA;
12908                 toY = currentMoveString[3] - ONE;
12909                 promoChar = currentMoveString[4];
12910                 break;
12911             case WhiteDrop:
12912             case BlackDrop:
12913                 fromX = next == WhiteDrop ?
12914                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12915                   (int) CharToPiece(ToLower(currentMoveString[0]));
12916                 fromY = DROP_RANK;
12917                 toX = currentMoveString[2] - AAA;
12918                 toY = currentMoveString[3] - ONE;
12919                 promoChar = 0;
12920                 break;
12921         }
12922         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12923         plyNr++;
12924         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12925         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12926         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12927         if(appData.findMirror) {
12928             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12929             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12930         }
12931     }
12932 }
12933
12934 /* Load the nth game from open file f */
12935 int
12936 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12937 {
12938     ChessMove cm;
12939     char buf[MSG_SIZ];
12940     int gn = gameNumber;
12941     ListGame *lg = NULL;
12942     int numPGNTags = 0, i;
12943     int err, pos = -1;
12944     GameMode oldGameMode;
12945     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12946     char oldName[MSG_SIZ];
12947
12948     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12949
12950     if (appData.debugMode)
12951         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12952
12953     if (gameMode == Training )
12954         SetTrainingModeOff();
12955
12956     oldGameMode = gameMode;
12957     if (gameMode != BeginningOfGame) {
12958       Reset(FALSE, TRUE);
12959     }
12960     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
12961
12962     gameFileFP = f;
12963     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12964         fclose(lastLoadGameFP);
12965     }
12966
12967     if (useList) {
12968         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12969
12970         if (lg) {
12971             fseek(f, lg->offset, 0);
12972             GameListHighlight(gameNumber);
12973             pos = lg->position;
12974             gn = 1;
12975         }
12976         else {
12977             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12978               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12979             else
12980             DisplayError(_("Game number out of range"), 0);
12981             return FALSE;
12982         }
12983     } else {
12984         GameListDestroy();
12985         if (fseek(f, 0, 0) == -1) {
12986             if (f == lastLoadGameFP ?
12987                 gameNumber == lastLoadGameNumber + 1 :
12988                 gameNumber == 1) {
12989                 gn = 1;
12990             } else {
12991                 DisplayError(_("Can't seek on game file"), 0);
12992                 return FALSE;
12993             }
12994         }
12995     }
12996     lastLoadGameFP = f;
12997     lastLoadGameNumber = gameNumber;
12998     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12999     lastLoadGameUseList = useList;
13000
13001     yynewfile(f);
13002
13003     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13004       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13005                 lg->gameInfo.black);
13006             DisplayTitle(buf);
13007     } else if (*title != NULLCHAR) {
13008         if (gameNumber > 1) {
13009           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13010             DisplayTitle(buf);
13011         } else {
13012             DisplayTitle(title);
13013         }
13014     }
13015
13016     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13017         gameMode = PlayFromGameFile;
13018         ModeHighlight();
13019     }
13020
13021     currentMove = forwardMostMove = backwardMostMove = 0;
13022     CopyBoard(boards[0], initialPosition);
13023     StopClocks();
13024
13025     /*
13026      * Skip the first gn-1 games in the file.
13027      * Also skip over anything that precedes an identifiable
13028      * start of game marker, to avoid being confused by
13029      * garbage at the start of the file.  Currently
13030      * recognized start of game markers are the move number "1",
13031      * the pattern "gnuchess .* game", the pattern
13032      * "^[#;%] [^ ]* game file", and a PGN tag block.
13033      * A game that starts with one of the latter two patterns
13034      * will also have a move number 1, possibly
13035      * following a position diagram.
13036      * 5-4-02: Let's try being more lenient and allowing a game to
13037      * start with an unnumbered move.  Does that break anything?
13038      */
13039     cm = lastLoadGameStart = EndOfFile;
13040     while (gn > 0) {
13041         yyboardindex = forwardMostMove;
13042         cm = (ChessMove) Myylex();
13043         switch (cm) {
13044           case EndOfFile:
13045             if (cmailMsgLoaded) {
13046                 nCmailGames = CMAIL_MAX_GAMES - gn;
13047             } else {
13048                 Reset(TRUE, TRUE);
13049                 DisplayError(_("Game not found in file"), 0);
13050             }
13051             return FALSE;
13052
13053           case GNUChessGame:
13054           case XBoardGame:
13055             gn--;
13056             lastLoadGameStart = cm;
13057             break;
13058
13059           case MoveNumberOne:
13060             switch (lastLoadGameStart) {
13061               case GNUChessGame:
13062               case XBoardGame:
13063               case PGNTag:
13064                 break;
13065               case MoveNumberOne:
13066               case EndOfFile:
13067                 gn--;           /* count this game */
13068                 lastLoadGameStart = cm;
13069                 break;
13070               default:
13071                 /* impossible */
13072                 break;
13073             }
13074             break;
13075
13076           case PGNTag:
13077             switch (lastLoadGameStart) {
13078               case GNUChessGame:
13079               case PGNTag:
13080               case MoveNumberOne:
13081               case EndOfFile:
13082                 gn--;           /* count this game */
13083                 lastLoadGameStart = cm;
13084                 break;
13085               case XBoardGame:
13086                 lastLoadGameStart = cm; /* game counted already */
13087                 break;
13088               default:
13089                 /* impossible */
13090                 break;
13091             }
13092             if (gn > 0) {
13093                 do {
13094                     yyboardindex = forwardMostMove;
13095                     cm = (ChessMove) Myylex();
13096                 } while (cm == PGNTag || cm == Comment);
13097             }
13098             break;
13099
13100           case WhiteWins:
13101           case BlackWins:
13102           case GameIsDrawn:
13103             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13104                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13105                     != CMAIL_OLD_RESULT) {
13106                     nCmailResults ++ ;
13107                     cmailResult[  CMAIL_MAX_GAMES
13108                                 - gn - 1] = CMAIL_OLD_RESULT;
13109                 }
13110             }
13111             break;
13112
13113           case NormalMove:
13114           case FirstLeg:
13115             /* Only a NormalMove can be at the start of a game
13116              * without a position diagram. */
13117             if (lastLoadGameStart == EndOfFile ) {
13118               gn--;
13119               lastLoadGameStart = MoveNumberOne;
13120             }
13121             break;
13122
13123           default:
13124             break;
13125         }
13126     }
13127
13128     if (appData.debugMode)
13129       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13130
13131     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13132
13133     if (cm == XBoardGame) {
13134         /* Skip any header junk before position diagram and/or move 1 */
13135         for (;;) {
13136             yyboardindex = forwardMostMove;
13137             cm = (ChessMove) Myylex();
13138
13139             if (cm == EndOfFile ||
13140                 cm == GNUChessGame || cm == XBoardGame) {
13141                 /* Empty game; pretend end-of-file and handle later */
13142                 cm = EndOfFile;
13143                 break;
13144             }
13145
13146             if (cm == MoveNumberOne || cm == PositionDiagram ||
13147                 cm == PGNTag || cm == Comment)
13148               break;
13149         }
13150     } else if (cm == GNUChessGame) {
13151         if (gameInfo.event != NULL) {
13152             free(gameInfo.event);
13153         }
13154         gameInfo.event = StrSave(yy_text);
13155     }
13156
13157     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13158     while (cm == PGNTag) {
13159         if (appData.debugMode)
13160           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13161         err = ParsePGNTag(yy_text, &gameInfo);
13162         if (!err) numPGNTags++;
13163
13164         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13165         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13166             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13167             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13168             InitPosition(TRUE);
13169             oldVariant = gameInfo.variant;
13170             if (appData.debugMode)
13171               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13172         }
13173
13174
13175         if (gameInfo.fen != NULL) {
13176           Board initial_position;
13177           startedFromSetupPosition = TRUE;
13178           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13179             Reset(TRUE, TRUE);
13180             DisplayError(_("Bad FEN position in file"), 0);
13181             return FALSE;
13182           }
13183           CopyBoard(boards[0], initial_position);
13184           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13185             CopyBoard(initialPosition, initial_position);
13186           if (blackPlaysFirst) {
13187             currentMove = forwardMostMove = backwardMostMove = 1;
13188             CopyBoard(boards[1], initial_position);
13189             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13190             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13191             timeRemaining[0][1] = whiteTimeRemaining;
13192             timeRemaining[1][1] = blackTimeRemaining;
13193             if (commentList[0] != NULL) {
13194               commentList[1] = commentList[0];
13195               commentList[0] = NULL;
13196             }
13197           } else {
13198             currentMove = forwardMostMove = backwardMostMove = 0;
13199           }
13200           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13201           {   int i;
13202               initialRulePlies = FENrulePlies;
13203               for( i=0; i< nrCastlingRights; i++ )
13204                   initialRights[i] = initial_position[CASTLING][i];
13205           }
13206           yyboardindex = forwardMostMove;
13207           free(gameInfo.fen);
13208           gameInfo.fen = NULL;
13209         }
13210
13211         yyboardindex = forwardMostMove;
13212         cm = (ChessMove) Myylex();
13213
13214         /* Handle comments interspersed among the tags */
13215         while (cm == Comment) {
13216             char *p;
13217             if (appData.debugMode)
13218               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13219             p = yy_text;
13220             AppendComment(currentMove, p, FALSE);
13221             yyboardindex = forwardMostMove;
13222             cm = (ChessMove) Myylex();
13223         }
13224     }
13225
13226     /* don't rely on existence of Event tag since if game was
13227      * pasted from clipboard the Event tag may not exist
13228      */
13229     if (numPGNTags > 0){
13230         char *tags;
13231         if (gameInfo.variant == VariantNormal) {
13232           VariantClass v = StringToVariant(gameInfo.event);
13233           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13234           if(v < VariantShogi) gameInfo.variant = v;
13235         }
13236         if (!matchMode) {
13237           if( appData.autoDisplayTags ) {
13238             tags = PGNTags(&gameInfo);
13239             TagsPopUp(tags, CmailMsg());
13240             free(tags);
13241           }
13242         }
13243     } else {
13244         /* Make something up, but don't display it now */
13245         SetGameInfo();
13246         TagsPopDown();
13247     }
13248
13249     if (cm == PositionDiagram) {
13250         int i, j;
13251         char *p;
13252         Board initial_position;
13253
13254         if (appData.debugMode)
13255           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13256
13257         if (!startedFromSetupPosition) {
13258             p = yy_text;
13259             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13260               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13261                 switch (*p) {
13262                   case '{':
13263                   case '[':
13264                   case '-':
13265                   case ' ':
13266                   case '\t':
13267                   case '\n':
13268                   case '\r':
13269                     break;
13270                   default:
13271                     initial_position[i][j++] = CharToPiece(*p);
13272                     break;
13273                 }
13274             while (*p == ' ' || *p == '\t' ||
13275                    *p == '\n' || *p == '\r') p++;
13276
13277             if (strncmp(p, "black", strlen("black"))==0)
13278               blackPlaysFirst = TRUE;
13279             else
13280               blackPlaysFirst = FALSE;
13281             startedFromSetupPosition = TRUE;
13282
13283             CopyBoard(boards[0], initial_position);
13284             if (blackPlaysFirst) {
13285                 currentMove = forwardMostMove = backwardMostMove = 1;
13286                 CopyBoard(boards[1], initial_position);
13287                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13288                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13289                 timeRemaining[0][1] = whiteTimeRemaining;
13290                 timeRemaining[1][1] = blackTimeRemaining;
13291                 if (commentList[0] != NULL) {
13292                     commentList[1] = commentList[0];
13293                     commentList[0] = NULL;
13294                 }
13295             } else {
13296                 currentMove = forwardMostMove = backwardMostMove = 0;
13297             }
13298         }
13299         yyboardindex = forwardMostMove;
13300         cm = (ChessMove) Myylex();
13301     }
13302
13303   if(!creatingBook) {
13304     if (first.pr == NoProc) {
13305         StartChessProgram(&first);
13306     }
13307     InitChessProgram(&first, FALSE);
13308     if(gameInfo.variant == VariantUnknown && *oldName) {
13309         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13310         gameInfo.variant = v;
13311     }
13312     SendToProgram("force\n", &first);
13313     if (startedFromSetupPosition) {
13314         SendBoard(&first, forwardMostMove);
13315     if (appData.debugMode) {
13316         fprintf(debugFP, "Load Game\n");
13317     }
13318         DisplayBothClocks();
13319     }
13320   }
13321
13322     /* [HGM] server: flag to write setup moves in broadcast file as one */
13323     loadFlag = appData.suppressLoadMoves;
13324
13325     while (cm == Comment) {
13326         char *p;
13327         if (appData.debugMode)
13328           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13329         p = yy_text;
13330         AppendComment(currentMove, p, FALSE);
13331         yyboardindex = forwardMostMove;
13332         cm = (ChessMove) Myylex();
13333     }
13334
13335     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13336         cm == WhiteWins || cm == BlackWins ||
13337         cm == GameIsDrawn || cm == GameUnfinished) {
13338         DisplayMessage("", _("No moves in game"));
13339         if (cmailMsgLoaded) {
13340             if (appData.debugMode)
13341               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13342             ClearHighlights();
13343             flipView = FALSE;
13344         }
13345         DrawPosition(FALSE, boards[currentMove]);
13346         DisplayBothClocks();
13347         gameMode = EditGame;
13348         ModeHighlight();
13349         gameFileFP = NULL;
13350         cmailOldMove = 0;
13351         return TRUE;
13352     }
13353
13354     // [HGM] PV info: routine tests if comment empty
13355     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13356         DisplayComment(currentMove - 1, commentList[currentMove]);
13357     }
13358     if (!matchMode && appData.timeDelay != 0)
13359       DrawPosition(FALSE, boards[currentMove]);
13360
13361     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13362       programStats.ok_to_send = 1;
13363     }
13364
13365     /* if the first token after the PGN tags is a move
13366      * and not move number 1, retrieve it from the parser
13367      */
13368     if (cm != MoveNumberOne)
13369         LoadGameOneMove(cm);
13370
13371     /* load the remaining moves from the file */
13372     while (LoadGameOneMove(EndOfFile)) {
13373       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13374       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13375     }
13376
13377     /* rewind to the start of the game */
13378     currentMove = backwardMostMove;
13379
13380     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13381
13382     if (oldGameMode == AnalyzeFile) {
13383       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13384       AnalyzeFileEvent();
13385     } else
13386     if (oldGameMode == AnalyzeMode) {
13387       AnalyzeFileEvent();
13388     }
13389
13390     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13391         long int w, b; // [HGM] adjourn: restore saved clock times
13392         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13393         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13394             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13395             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13396         }
13397     }
13398
13399     if(creatingBook) return TRUE;
13400     if (!matchMode && pos > 0) {
13401         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13402     } else
13403     if (matchMode || appData.timeDelay == 0) {
13404       ToEndEvent();
13405     } else if (appData.timeDelay > 0) {
13406       AutoPlayGameLoop();
13407     }
13408
13409     if (appData.debugMode)
13410         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13411
13412     loadFlag = 0; /* [HGM] true game starts */
13413     return TRUE;
13414 }
13415
13416 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13417 int
13418 ReloadPosition (int offset)
13419 {
13420     int positionNumber = lastLoadPositionNumber + offset;
13421     if (lastLoadPositionFP == NULL) {
13422         DisplayError(_("No position has been loaded yet"), 0);
13423         return FALSE;
13424     }
13425     if (positionNumber <= 0) {
13426         DisplayError(_("Can't back up any further"), 0);
13427         return FALSE;
13428     }
13429     return LoadPosition(lastLoadPositionFP, positionNumber,
13430                         lastLoadPositionTitle);
13431 }
13432
13433 /* Load the nth position from the given file */
13434 int
13435 LoadPositionFromFile (char *filename, int n, char *title)
13436 {
13437     FILE *f;
13438     char buf[MSG_SIZ];
13439
13440     if (strcmp(filename, "-") == 0) {
13441         return LoadPosition(stdin, n, "stdin");
13442     } else {
13443         f = fopen(filename, "rb");
13444         if (f == NULL) {
13445             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13446             DisplayError(buf, errno);
13447             return FALSE;
13448         } else {
13449             return LoadPosition(f, n, title);
13450         }
13451     }
13452 }
13453
13454 /* Load the nth position from the given open file, and close it */
13455 int
13456 LoadPosition (FILE *f, int positionNumber, char *title)
13457 {
13458     char *p, line[MSG_SIZ];
13459     Board initial_position;
13460     int i, j, fenMode, pn;
13461
13462     if (gameMode == Training )
13463         SetTrainingModeOff();
13464
13465     if (gameMode != BeginningOfGame) {
13466         Reset(FALSE, TRUE);
13467     }
13468     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13469         fclose(lastLoadPositionFP);
13470     }
13471     if (positionNumber == 0) positionNumber = 1;
13472     lastLoadPositionFP = f;
13473     lastLoadPositionNumber = positionNumber;
13474     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13475     if (first.pr == NoProc && !appData.noChessProgram) {
13476       StartChessProgram(&first);
13477       InitChessProgram(&first, FALSE);
13478     }
13479     pn = positionNumber;
13480     if (positionNumber < 0) {
13481         /* Negative position number means to seek to that byte offset */
13482         if (fseek(f, -positionNumber, 0) == -1) {
13483             DisplayError(_("Can't seek on position file"), 0);
13484             return FALSE;
13485         };
13486         pn = 1;
13487     } else {
13488         if (fseek(f, 0, 0) == -1) {
13489             if (f == lastLoadPositionFP ?
13490                 positionNumber == lastLoadPositionNumber + 1 :
13491                 positionNumber == 1) {
13492                 pn = 1;
13493             } else {
13494                 DisplayError(_("Can't seek on position file"), 0);
13495                 return FALSE;
13496             }
13497         }
13498     }
13499     /* See if this file is FEN or old-style xboard */
13500     if (fgets(line, MSG_SIZ, f) == NULL) {
13501         DisplayError(_("Position not found in file"), 0);
13502         return FALSE;
13503     }
13504     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13505     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13506
13507     if (pn >= 2) {
13508         if (fenMode || line[0] == '#') pn--;
13509         while (pn > 0) {
13510             /* skip positions before number pn */
13511             if (fgets(line, MSG_SIZ, f) == NULL) {
13512                 Reset(TRUE, TRUE);
13513                 DisplayError(_("Position not found in file"), 0);
13514                 return FALSE;
13515             }
13516             if (fenMode || line[0] == '#') pn--;
13517         }
13518     }
13519
13520     if (fenMode) {
13521         char *p;
13522         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13523             DisplayError(_("Bad FEN position in file"), 0);
13524             return FALSE;
13525         }
13526         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13527             sscanf(p+3, "%s", bestMove);
13528         } else *bestMove = NULLCHAR;
13529     } else {
13530         (void) fgets(line, MSG_SIZ, f);
13531         (void) fgets(line, MSG_SIZ, f);
13532
13533         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13534             (void) fgets(line, MSG_SIZ, f);
13535             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13536                 if (*p == ' ')
13537                   continue;
13538                 initial_position[i][j++] = CharToPiece(*p);
13539             }
13540         }
13541
13542         blackPlaysFirst = FALSE;
13543         if (!feof(f)) {
13544             (void) fgets(line, MSG_SIZ, f);
13545             if (strncmp(line, "black", strlen("black"))==0)
13546               blackPlaysFirst = TRUE;
13547         }
13548     }
13549     startedFromSetupPosition = TRUE;
13550
13551     CopyBoard(boards[0], initial_position);
13552     if (blackPlaysFirst) {
13553         currentMove = forwardMostMove = backwardMostMove = 1;
13554         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13555         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13556         CopyBoard(boards[1], initial_position);
13557         DisplayMessage("", _("Black to play"));
13558     } else {
13559         currentMove = forwardMostMove = backwardMostMove = 0;
13560         DisplayMessage("", _("White to play"));
13561     }
13562     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13563     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13564         SendToProgram("force\n", &first);
13565         SendBoard(&first, forwardMostMove);
13566     }
13567     if (appData.debugMode) {
13568 int i, j;
13569   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13570   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13571         fprintf(debugFP, "Load Position\n");
13572     }
13573
13574     if (positionNumber > 1) {
13575       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13576         DisplayTitle(line);
13577     } else {
13578         DisplayTitle(title);
13579     }
13580     gameMode = EditGame;
13581     ModeHighlight();
13582     ResetClocks();
13583     timeRemaining[0][1] = whiteTimeRemaining;
13584     timeRemaining[1][1] = blackTimeRemaining;
13585     DrawPosition(FALSE, boards[currentMove]);
13586
13587     return TRUE;
13588 }
13589
13590
13591 void
13592 CopyPlayerNameIntoFileName (char **dest, char *src)
13593 {
13594     while (*src != NULLCHAR && *src != ',') {
13595         if (*src == ' ') {
13596             *(*dest)++ = '_';
13597             src++;
13598         } else {
13599             *(*dest)++ = *src++;
13600         }
13601     }
13602 }
13603
13604 char *
13605 DefaultFileName (char *ext)
13606 {
13607     static char def[MSG_SIZ];
13608     char *p;
13609
13610     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13611         p = def;
13612         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13613         *p++ = '-';
13614         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13615         *p++ = '.';
13616         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13617     } else {
13618         def[0] = NULLCHAR;
13619     }
13620     return def;
13621 }
13622
13623 /* Save the current game to the given file */
13624 int
13625 SaveGameToFile (char *filename, int append)
13626 {
13627     FILE *f;
13628     char buf[MSG_SIZ];
13629     int result, i, t,tot=0;
13630
13631     if (strcmp(filename, "-") == 0) {
13632         return SaveGame(stdout, 0, NULL);
13633     } else {
13634         for(i=0; i<10; i++) { // upto 10 tries
13635              f = fopen(filename, append ? "a" : "w");
13636              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13637              if(f || errno != 13) break;
13638              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13639              tot += t;
13640         }
13641         if (f == NULL) {
13642             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13643             DisplayError(buf, errno);
13644             return FALSE;
13645         } else {
13646             safeStrCpy(buf, lastMsg, MSG_SIZ);
13647             DisplayMessage(_("Waiting for access to save file"), "");
13648             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13649             DisplayMessage(_("Saving game"), "");
13650             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13651             result = SaveGame(f, 0, NULL);
13652             DisplayMessage(buf, "");
13653             return result;
13654         }
13655     }
13656 }
13657
13658 char *
13659 SavePart (char *str)
13660 {
13661     static char buf[MSG_SIZ];
13662     char *p;
13663
13664     p = strchr(str, ' ');
13665     if (p == NULL) return str;
13666     strncpy(buf, str, p - str);
13667     buf[p - str] = NULLCHAR;
13668     return buf;
13669 }
13670
13671 #define PGN_MAX_LINE 75
13672
13673 #define PGN_SIDE_WHITE  0
13674 #define PGN_SIDE_BLACK  1
13675
13676 static int
13677 FindFirstMoveOutOfBook (int side)
13678 {
13679     int result = -1;
13680
13681     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13682         int index = backwardMostMove;
13683         int has_book_hit = 0;
13684
13685         if( (index % 2) != side ) {
13686             index++;
13687         }
13688
13689         while( index < forwardMostMove ) {
13690             /* Check to see if engine is in book */
13691             int depth = pvInfoList[index].depth;
13692             int score = pvInfoList[index].score;
13693             int in_book = 0;
13694
13695             if( depth <= 2 ) {
13696                 in_book = 1;
13697             }
13698             else if( score == 0 && depth == 63 ) {
13699                 in_book = 1; /* Zappa */
13700             }
13701             else if( score == 2 && depth == 99 ) {
13702                 in_book = 1; /* Abrok */
13703             }
13704
13705             has_book_hit += in_book;
13706
13707             if( ! in_book ) {
13708                 result = index;
13709
13710                 break;
13711             }
13712
13713             index += 2;
13714         }
13715     }
13716
13717     return result;
13718 }
13719
13720 void
13721 GetOutOfBookInfo (char * buf)
13722 {
13723     int oob[2];
13724     int i;
13725     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13726
13727     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13728     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13729
13730     *buf = '\0';
13731
13732     if( oob[0] >= 0 || oob[1] >= 0 ) {
13733         for( i=0; i<2; i++ ) {
13734             int idx = oob[i];
13735
13736             if( idx >= 0 ) {
13737                 if( i > 0 && oob[0] >= 0 ) {
13738                     strcat( buf, "   " );
13739                 }
13740
13741                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13742                 sprintf( buf+strlen(buf), "%s%.2f",
13743                     pvInfoList[idx].score >= 0 ? "+" : "",
13744                     pvInfoList[idx].score / 100.0 );
13745             }
13746         }
13747     }
13748 }
13749
13750 /* Save game in PGN style */
13751 static void
13752 SaveGamePGN2 (FILE *f)
13753 {
13754     int i, offset, linelen, newblock;
13755 //    char *movetext;
13756     char numtext[32];
13757     int movelen, numlen, blank;
13758     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13759
13760     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13761
13762     PrintPGNTags(f, &gameInfo);
13763
13764     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13765
13766     if (backwardMostMove > 0 || startedFromSetupPosition) {
13767         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13768         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13769         fprintf(f, "\n{--------------\n");
13770         PrintPosition(f, backwardMostMove);
13771         fprintf(f, "--------------}\n");
13772         free(fen);
13773     }
13774     else {
13775         /* [AS] Out of book annotation */
13776         if( appData.saveOutOfBookInfo ) {
13777             char buf[64];
13778
13779             GetOutOfBookInfo( buf );
13780
13781             if( buf[0] != '\0' ) {
13782                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13783             }
13784         }
13785
13786         fprintf(f, "\n");
13787     }
13788
13789     i = backwardMostMove;
13790     linelen = 0;
13791     newblock = TRUE;
13792
13793     while (i < forwardMostMove) {
13794         /* Print comments preceding this move */
13795         if (commentList[i] != NULL) {
13796             if (linelen > 0) fprintf(f, "\n");
13797             fprintf(f, "%s", commentList[i]);
13798             linelen = 0;
13799             newblock = TRUE;
13800         }
13801
13802         /* Format move number */
13803         if ((i % 2) == 0)
13804           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13805         else
13806           if (newblock)
13807             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13808           else
13809             numtext[0] = NULLCHAR;
13810
13811         numlen = strlen(numtext);
13812         newblock = FALSE;
13813
13814         /* Print move number */
13815         blank = linelen > 0 && numlen > 0;
13816         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13817             fprintf(f, "\n");
13818             linelen = 0;
13819             blank = 0;
13820         }
13821         if (blank) {
13822             fprintf(f, " ");
13823             linelen++;
13824         }
13825         fprintf(f, "%s", numtext);
13826         linelen += numlen;
13827
13828         /* Get move */
13829         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13830         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13831
13832         /* Print move */
13833         blank = linelen > 0 && movelen > 0;
13834         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13835             fprintf(f, "\n");
13836             linelen = 0;
13837             blank = 0;
13838         }
13839         if (blank) {
13840             fprintf(f, " ");
13841             linelen++;
13842         }
13843         fprintf(f, "%s", move_buffer);
13844         linelen += movelen;
13845
13846         /* [AS] Add PV info if present */
13847         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13848             /* [HGM] add time */
13849             char buf[MSG_SIZ]; int seconds;
13850
13851             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13852
13853             if( seconds <= 0)
13854               buf[0] = 0;
13855             else
13856               if( seconds < 30 )
13857                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13858               else
13859                 {
13860                   seconds = (seconds + 4)/10; // round to full seconds
13861                   if( seconds < 60 )
13862                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13863                   else
13864                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13865                 }
13866
13867             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13868                       pvInfoList[i].score >= 0 ? "+" : "",
13869                       pvInfoList[i].score / 100.0,
13870                       pvInfoList[i].depth,
13871                       buf );
13872
13873             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13874
13875             /* Print score/depth */
13876             blank = linelen > 0 && movelen > 0;
13877             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13878                 fprintf(f, "\n");
13879                 linelen = 0;
13880                 blank = 0;
13881             }
13882             if (blank) {
13883                 fprintf(f, " ");
13884                 linelen++;
13885             }
13886             fprintf(f, "%s", move_buffer);
13887             linelen += movelen;
13888         }
13889
13890         i++;
13891     }
13892
13893     /* Start a new line */
13894     if (linelen > 0) fprintf(f, "\n");
13895
13896     /* Print comments after last move */
13897     if (commentList[i] != NULL) {
13898         fprintf(f, "%s\n", commentList[i]);
13899     }
13900
13901     /* Print result */
13902     if (gameInfo.resultDetails != NULL &&
13903         gameInfo.resultDetails[0] != NULLCHAR) {
13904         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13905         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13906            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13907             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13908         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13909     } else {
13910         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13911     }
13912 }
13913
13914 /* Save game in PGN style and close the file */
13915 int
13916 SaveGamePGN (FILE *f)
13917 {
13918     SaveGamePGN2(f);
13919     fclose(f);
13920     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13921     return TRUE;
13922 }
13923
13924 /* Save game in old style and close the file */
13925 int
13926 SaveGameOldStyle (FILE *f)
13927 {
13928     int i, offset;
13929     time_t tm;
13930
13931     tm = time((time_t *) NULL);
13932
13933     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13934     PrintOpponents(f);
13935
13936     if (backwardMostMove > 0 || startedFromSetupPosition) {
13937         fprintf(f, "\n[--------------\n");
13938         PrintPosition(f, backwardMostMove);
13939         fprintf(f, "--------------]\n");
13940     } else {
13941         fprintf(f, "\n");
13942     }
13943
13944     i = backwardMostMove;
13945     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13946
13947     while (i < forwardMostMove) {
13948         if (commentList[i] != NULL) {
13949             fprintf(f, "[%s]\n", commentList[i]);
13950         }
13951
13952         if ((i % 2) == 1) {
13953             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13954             i++;
13955         } else {
13956             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13957             i++;
13958             if (commentList[i] != NULL) {
13959                 fprintf(f, "\n");
13960                 continue;
13961             }
13962             if (i >= forwardMostMove) {
13963                 fprintf(f, "\n");
13964                 break;
13965             }
13966             fprintf(f, "%s\n", parseList[i]);
13967             i++;
13968         }
13969     }
13970
13971     if (commentList[i] != NULL) {
13972         fprintf(f, "[%s]\n", commentList[i]);
13973     }
13974
13975     /* This isn't really the old style, but it's close enough */
13976     if (gameInfo.resultDetails != NULL &&
13977         gameInfo.resultDetails[0] != NULLCHAR) {
13978         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13979                 gameInfo.resultDetails);
13980     } else {
13981         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13982     }
13983
13984     fclose(f);
13985     return TRUE;
13986 }
13987
13988 /* Save the current game to open file f and close the file */
13989 int
13990 SaveGame (FILE *f, int dummy, char *dummy2)
13991 {
13992     if (gameMode == EditPosition) EditPositionDone(TRUE);
13993     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13994     if (appData.oldSaveStyle)
13995       return SaveGameOldStyle(f);
13996     else
13997       return SaveGamePGN(f);
13998 }
13999
14000 /* Save the current position to the given file */
14001 int
14002 SavePositionToFile (char *filename)
14003 {
14004     FILE *f;
14005     char buf[MSG_SIZ];
14006
14007     if (strcmp(filename, "-") == 0) {
14008         return SavePosition(stdout, 0, NULL);
14009     } else {
14010         f = fopen(filename, "a");
14011         if (f == NULL) {
14012             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14013             DisplayError(buf, errno);
14014             return FALSE;
14015         } else {
14016             safeStrCpy(buf, lastMsg, MSG_SIZ);
14017             DisplayMessage(_("Waiting for access to save file"), "");
14018             flock(fileno(f), LOCK_EX); // [HGM] lock
14019             DisplayMessage(_("Saving position"), "");
14020             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14021             SavePosition(f, 0, NULL);
14022             DisplayMessage(buf, "");
14023             return TRUE;
14024         }
14025     }
14026 }
14027
14028 /* Save the current position to the given open file and close the file */
14029 int
14030 SavePosition (FILE *f, int dummy, char *dummy2)
14031 {
14032     time_t tm;
14033     char *fen;
14034
14035     if (gameMode == EditPosition) EditPositionDone(TRUE);
14036     if (appData.oldSaveStyle) {
14037         tm = time((time_t *) NULL);
14038
14039         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14040         PrintOpponents(f);
14041         fprintf(f, "[--------------\n");
14042         PrintPosition(f, currentMove);
14043         fprintf(f, "--------------]\n");
14044     } else {
14045         fen = PositionToFEN(currentMove, NULL, 1);
14046         fprintf(f, "%s\n", fen);
14047         free(fen);
14048     }
14049     fclose(f);
14050     return TRUE;
14051 }
14052
14053 void
14054 ReloadCmailMsgEvent (int unregister)
14055 {
14056 #if !WIN32
14057     static char *inFilename = NULL;
14058     static char *outFilename;
14059     int i;
14060     struct stat inbuf, outbuf;
14061     int status;
14062
14063     /* Any registered moves are unregistered if unregister is set, */
14064     /* i.e. invoked by the signal handler */
14065     if (unregister) {
14066         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14067             cmailMoveRegistered[i] = FALSE;
14068             if (cmailCommentList[i] != NULL) {
14069                 free(cmailCommentList[i]);
14070                 cmailCommentList[i] = NULL;
14071             }
14072         }
14073         nCmailMovesRegistered = 0;
14074     }
14075
14076     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14077         cmailResult[i] = CMAIL_NOT_RESULT;
14078     }
14079     nCmailResults = 0;
14080
14081     if (inFilename == NULL) {
14082         /* Because the filenames are static they only get malloced once  */
14083         /* and they never get freed                                      */
14084         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14085         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14086
14087         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14088         sprintf(outFilename, "%s.out", appData.cmailGameName);
14089     }
14090
14091     status = stat(outFilename, &outbuf);
14092     if (status < 0) {
14093         cmailMailedMove = FALSE;
14094     } else {
14095         status = stat(inFilename, &inbuf);
14096         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14097     }
14098
14099     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14100        counts the games, notes how each one terminated, etc.
14101
14102        It would be nice to remove this kludge and instead gather all
14103        the information while building the game list.  (And to keep it
14104        in the game list nodes instead of having a bunch of fixed-size
14105        parallel arrays.)  Note this will require getting each game's
14106        termination from the PGN tags, as the game list builder does
14107        not process the game moves.  --mann
14108        */
14109     cmailMsgLoaded = TRUE;
14110     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14111
14112     /* Load first game in the file or popup game menu */
14113     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14114
14115 #endif /* !WIN32 */
14116     return;
14117 }
14118
14119 int
14120 RegisterMove ()
14121 {
14122     FILE *f;
14123     char string[MSG_SIZ];
14124
14125     if (   cmailMailedMove
14126         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14127         return TRUE;            /* Allow free viewing  */
14128     }
14129
14130     /* Unregister move to ensure that we don't leave RegisterMove        */
14131     /* with the move registered when the conditions for registering no   */
14132     /* longer hold                                                       */
14133     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14134         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14135         nCmailMovesRegistered --;
14136
14137         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14138           {
14139               free(cmailCommentList[lastLoadGameNumber - 1]);
14140               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14141           }
14142     }
14143
14144     if (cmailOldMove == -1) {
14145         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14146         return FALSE;
14147     }
14148
14149     if (currentMove > cmailOldMove + 1) {
14150         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14151         return FALSE;
14152     }
14153
14154     if (currentMove < cmailOldMove) {
14155         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14156         return FALSE;
14157     }
14158
14159     if (forwardMostMove > currentMove) {
14160         /* Silently truncate extra moves */
14161         TruncateGame();
14162     }
14163
14164     if (   (currentMove == cmailOldMove + 1)
14165         || (   (currentMove == cmailOldMove)
14166             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14167                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14168         if (gameInfo.result != GameUnfinished) {
14169             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14170         }
14171
14172         if (commentList[currentMove] != NULL) {
14173             cmailCommentList[lastLoadGameNumber - 1]
14174               = StrSave(commentList[currentMove]);
14175         }
14176         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14177
14178         if (appData.debugMode)
14179           fprintf(debugFP, "Saving %s for game %d\n",
14180                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14181
14182         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14183
14184         f = fopen(string, "w");
14185         if (appData.oldSaveStyle) {
14186             SaveGameOldStyle(f); /* also closes the file */
14187
14188             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14189             f = fopen(string, "w");
14190             SavePosition(f, 0, NULL); /* also closes the file */
14191         } else {
14192             fprintf(f, "{--------------\n");
14193             PrintPosition(f, currentMove);
14194             fprintf(f, "--------------}\n\n");
14195
14196             SaveGame(f, 0, NULL); /* also closes the file*/
14197         }
14198
14199         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14200         nCmailMovesRegistered ++;
14201     } else if (nCmailGames == 1) {
14202         DisplayError(_("You have not made a move yet"), 0);
14203         return FALSE;
14204     }
14205
14206     return TRUE;
14207 }
14208
14209 void
14210 MailMoveEvent ()
14211 {
14212 #if !WIN32
14213     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14214     FILE *commandOutput;
14215     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14216     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14217     int nBuffers;
14218     int i;
14219     int archived;
14220     char *arcDir;
14221
14222     if (! cmailMsgLoaded) {
14223         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14224         return;
14225     }
14226
14227     if (nCmailGames == nCmailResults) {
14228         DisplayError(_("No unfinished games"), 0);
14229         return;
14230     }
14231
14232 #if CMAIL_PROHIBIT_REMAIL
14233     if (cmailMailedMove) {
14234       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);
14235         DisplayError(msg, 0);
14236         return;
14237     }
14238 #endif
14239
14240     if (! (cmailMailedMove || RegisterMove())) return;
14241
14242     if (   cmailMailedMove
14243         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14244       snprintf(string, MSG_SIZ, partCommandString,
14245                appData.debugMode ? " -v" : "", appData.cmailGameName);
14246         commandOutput = popen(string, "r");
14247
14248         if (commandOutput == NULL) {
14249             DisplayError(_("Failed to invoke cmail"), 0);
14250         } else {
14251             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14252                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14253             }
14254             if (nBuffers > 1) {
14255                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14256                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14257                 nBytes = MSG_SIZ - 1;
14258             } else {
14259                 (void) memcpy(msg, buffer, nBytes);
14260             }
14261             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14262
14263             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14264                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14265
14266                 archived = TRUE;
14267                 for (i = 0; i < nCmailGames; i ++) {
14268                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14269                         archived = FALSE;
14270                     }
14271                 }
14272                 if (   archived
14273                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14274                         != NULL)) {
14275                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14276                            arcDir,
14277                            appData.cmailGameName,
14278                            gameInfo.date);
14279                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14280                     cmailMsgLoaded = FALSE;
14281                 }
14282             }
14283
14284             DisplayInformation(msg);
14285             pclose(commandOutput);
14286         }
14287     } else {
14288         if ((*cmailMsg) != '\0') {
14289             DisplayInformation(cmailMsg);
14290         }
14291     }
14292
14293     return;
14294 #endif /* !WIN32 */
14295 }
14296
14297 char *
14298 CmailMsg ()
14299 {
14300 #if WIN32
14301     return NULL;
14302 #else
14303     int  prependComma = 0;
14304     char number[5];
14305     char string[MSG_SIZ];       /* Space for game-list */
14306     int  i;
14307
14308     if (!cmailMsgLoaded) return "";
14309
14310     if (cmailMailedMove) {
14311       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14312     } else {
14313         /* Create a list of games left */
14314       snprintf(string, MSG_SIZ, "[");
14315         for (i = 0; i < nCmailGames; i ++) {
14316             if (! (   cmailMoveRegistered[i]
14317                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14318                 if (prependComma) {
14319                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14320                 } else {
14321                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14322                     prependComma = 1;
14323                 }
14324
14325                 strcat(string, number);
14326             }
14327         }
14328         strcat(string, "]");
14329
14330         if (nCmailMovesRegistered + nCmailResults == 0) {
14331             switch (nCmailGames) {
14332               case 1:
14333                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14334                 break;
14335
14336               case 2:
14337                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14338                 break;
14339
14340               default:
14341                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14342                          nCmailGames);
14343                 break;
14344             }
14345         } else {
14346             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14347               case 1:
14348                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14349                          string);
14350                 break;
14351
14352               case 0:
14353                 if (nCmailResults == nCmailGames) {
14354                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14355                 } else {
14356                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14357                 }
14358                 break;
14359
14360               default:
14361                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14362                          string);
14363             }
14364         }
14365     }
14366     return cmailMsg;
14367 #endif /* WIN32 */
14368 }
14369
14370 void
14371 ResetGameEvent ()
14372 {
14373     if (gameMode == Training)
14374       SetTrainingModeOff();
14375
14376     Reset(TRUE, TRUE);
14377     cmailMsgLoaded = FALSE;
14378     if (appData.icsActive) {
14379       SendToICS(ics_prefix);
14380       SendToICS("refresh\n");
14381     }
14382 }
14383
14384 void
14385 ExitEvent (int status)
14386 {
14387     exiting++;
14388     if (exiting > 2) {
14389       /* Give up on clean exit */
14390       exit(status);
14391     }
14392     if (exiting > 1) {
14393       /* Keep trying for clean exit */
14394       return;
14395     }
14396
14397     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14398     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14399
14400     if (telnetISR != NULL) {
14401       RemoveInputSource(telnetISR);
14402     }
14403     if (icsPR != NoProc) {
14404       DestroyChildProcess(icsPR, TRUE);
14405     }
14406
14407     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14408     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14409
14410     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14411     /* make sure this other one finishes before killing it!                  */
14412     if(endingGame) { int count = 0;
14413         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14414         while(endingGame && count++ < 10) DoSleep(1);
14415         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14416     }
14417
14418     /* Kill off chess programs */
14419     if (first.pr != NoProc) {
14420         ExitAnalyzeMode();
14421
14422         DoSleep( appData.delayBeforeQuit );
14423         SendToProgram("quit\n", &first);
14424         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14425     }
14426     if (second.pr != NoProc) {
14427         DoSleep( appData.delayBeforeQuit );
14428         SendToProgram("quit\n", &second);
14429         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14430     }
14431     if (first.isr != NULL) {
14432         RemoveInputSource(first.isr);
14433     }
14434     if (second.isr != NULL) {
14435         RemoveInputSource(second.isr);
14436     }
14437
14438     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14439     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14440
14441     ShutDownFrontEnd();
14442     exit(status);
14443 }
14444
14445 void
14446 PauseEngine (ChessProgramState *cps)
14447 {
14448     SendToProgram("pause\n", cps);
14449     cps->pause = 2;
14450 }
14451
14452 void
14453 UnPauseEngine (ChessProgramState *cps)
14454 {
14455     SendToProgram("resume\n", cps);
14456     cps->pause = 1;
14457 }
14458
14459 void
14460 PauseEvent ()
14461 {
14462     if (appData.debugMode)
14463         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14464     if (pausing) {
14465         pausing = FALSE;
14466         ModeHighlight();
14467         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14468             StartClocks();
14469             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14470                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14471                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14472             }
14473             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14474             HandleMachineMove(stashedInputMove, stalledEngine);
14475             stalledEngine = NULL;
14476             return;
14477         }
14478         if (gameMode == MachinePlaysWhite ||
14479             gameMode == TwoMachinesPlay   ||
14480             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14481             if(first.pause)  UnPauseEngine(&first);
14482             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14483             if(second.pause) UnPauseEngine(&second);
14484             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14485             StartClocks();
14486         } else {
14487             DisplayBothClocks();
14488         }
14489         if (gameMode == PlayFromGameFile) {
14490             if (appData.timeDelay >= 0)
14491                 AutoPlayGameLoop();
14492         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14493             Reset(FALSE, TRUE);
14494             SendToICS(ics_prefix);
14495             SendToICS("refresh\n");
14496         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14497             ForwardInner(forwardMostMove);
14498         }
14499         pauseExamInvalid = FALSE;
14500     } else {
14501         switch (gameMode) {
14502           default:
14503             return;
14504           case IcsExamining:
14505             pauseExamForwardMostMove = forwardMostMove;
14506             pauseExamInvalid = FALSE;
14507             /* fall through */
14508           case IcsObserving:
14509           case IcsPlayingWhite:
14510           case IcsPlayingBlack:
14511             pausing = TRUE;
14512             ModeHighlight();
14513             return;
14514           case PlayFromGameFile:
14515             (void) StopLoadGameTimer();
14516             pausing = TRUE;
14517             ModeHighlight();
14518             break;
14519           case BeginningOfGame:
14520             if (appData.icsActive) return;
14521             /* else fall through */
14522           case MachinePlaysWhite:
14523           case MachinePlaysBlack:
14524           case TwoMachinesPlay:
14525             if (forwardMostMove == 0)
14526               return;           /* don't pause if no one has moved */
14527             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14528                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14529                 if(onMove->pause) {           // thinking engine can be paused
14530                     PauseEngine(onMove);      // do it
14531                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14532                         PauseEngine(onMove->other);
14533                     else
14534                         SendToProgram("easy\n", onMove->other);
14535                     StopClocks();
14536                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14537             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14538                 if(first.pause) {
14539                     PauseEngine(&first);
14540                     StopClocks();
14541                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14542             } else { // human on move, pause pondering by either method
14543                 if(first.pause)
14544                     PauseEngine(&first);
14545                 else if(appData.ponderNextMove)
14546                     SendToProgram("easy\n", &first);
14547                 StopClocks();
14548             }
14549             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14550           case AnalyzeMode:
14551             pausing = TRUE;
14552             ModeHighlight();
14553             break;
14554         }
14555     }
14556 }
14557
14558 void
14559 EditCommentEvent ()
14560 {
14561     char title[MSG_SIZ];
14562
14563     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14564       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14565     } else {
14566       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14567                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14568                parseList[currentMove - 1]);
14569     }
14570
14571     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14572 }
14573
14574
14575 void
14576 EditTagsEvent ()
14577 {
14578     char *tags = PGNTags(&gameInfo);
14579     bookUp = FALSE;
14580     EditTagsPopUp(tags, NULL);
14581     free(tags);
14582 }
14583
14584 void
14585 ToggleSecond ()
14586 {
14587   if(second.analyzing) {
14588     SendToProgram("exit\n", &second);
14589     second.analyzing = FALSE;
14590   } else {
14591     if (second.pr == NoProc) StartChessProgram(&second);
14592     InitChessProgram(&second, FALSE);
14593     FeedMovesToProgram(&second, currentMove);
14594
14595     SendToProgram("analyze\n", &second);
14596     second.analyzing = TRUE;
14597   }
14598 }
14599
14600 /* Toggle ShowThinking */
14601 void
14602 ToggleShowThinking()
14603 {
14604   appData.showThinking = !appData.showThinking;
14605   ShowThinkingEvent();
14606 }
14607
14608 int
14609 AnalyzeModeEvent ()
14610 {
14611     char buf[MSG_SIZ];
14612
14613     if (!first.analysisSupport) {
14614       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14615       DisplayError(buf, 0);
14616       return 0;
14617     }
14618     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14619     if (appData.icsActive) {
14620         if (gameMode != IcsObserving) {
14621           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14622             DisplayError(buf, 0);
14623             /* secure check */
14624             if (appData.icsEngineAnalyze) {
14625                 if (appData.debugMode)
14626                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14627                 ExitAnalyzeMode();
14628                 ModeHighlight();
14629             }
14630             return 0;
14631         }
14632         /* if enable, user wants to disable icsEngineAnalyze */
14633         if (appData.icsEngineAnalyze) {
14634                 ExitAnalyzeMode();
14635                 ModeHighlight();
14636                 return 0;
14637         }
14638         appData.icsEngineAnalyze = TRUE;
14639         if (appData.debugMode)
14640             fprintf(debugFP, "ICS engine analyze starting... \n");
14641     }
14642
14643     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14644     if (appData.noChessProgram || gameMode == AnalyzeMode)
14645       return 0;
14646
14647     if (gameMode != AnalyzeFile) {
14648         if (!appData.icsEngineAnalyze) {
14649                EditGameEvent();
14650                if (gameMode != EditGame) return 0;
14651         }
14652         if (!appData.showThinking) ToggleShowThinking();
14653         ResurrectChessProgram();
14654         SendToProgram("analyze\n", &first);
14655         first.analyzing = TRUE;
14656         /*first.maybeThinking = TRUE;*/
14657         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14658         EngineOutputPopUp();
14659     }
14660     if (!appData.icsEngineAnalyze) {
14661         gameMode = AnalyzeMode;
14662         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14663     }
14664     pausing = FALSE;
14665     ModeHighlight();
14666     SetGameInfo();
14667
14668     StartAnalysisClock();
14669     GetTimeMark(&lastNodeCountTime);
14670     lastNodeCount = 0;
14671     return 1;
14672 }
14673
14674 void
14675 AnalyzeFileEvent ()
14676 {
14677     if (appData.noChessProgram || gameMode == AnalyzeFile)
14678       return;
14679
14680     if (!first.analysisSupport) {
14681       char buf[MSG_SIZ];
14682       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14683       DisplayError(buf, 0);
14684       return;
14685     }
14686
14687     if (gameMode != AnalyzeMode) {
14688         keepInfo = 1; // mere annotating should not alter PGN tags
14689         EditGameEvent();
14690         keepInfo = 0;
14691         if (gameMode != EditGame) return;
14692         if (!appData.showThinking) ToggleShowThinking();
14693         ResurrectChessProgram();
14694         SendToProgram("analyze\n", &first);
14695         first.analyzing = TRUE;
14696         /*first.maybeThinking = TRUE;*/
14697         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14698         EngineOutputPopUp();
14699     }
14700     gameMode = AnalyzeFile;
14701     pausing = FALSE;
14702     ModeHighlight();
14703
14704     StartAnalysisClock();
14705     GetTimeMark(&lastNodeCountTime);
14706     lastNodeCount = 0;
14707     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14708     AnalysisPeriodicEvent(1);
14709 }
14710
14711 void
14712 MachineWhiteEvent ()
14713 {
14714     char buf[MSG_SIZ];
14715     char *bookHit = NULL;
14716
14717     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14718       return;
14719
14720
14721     if (gameMode == PlayFromGameFile ||
14722         gameMode == TwoMachinesPlay  ||
14723         gameMode == Training         ||
14724         gameMode == AnalyzeMode      ||
14725         gameMode == EndOfGame)
14726         EditGameEvent();
14727
14728     if (gameMode == EditPosition)
14729         EditPositionDone(TRUE);
14730
14731     if (!WhiteOnMove(currentMove)) {
14732         DisplayError(_("It is not White's turn"), 0);
14733         return;
14734     }
14735
14736     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14737       ExitAnalyzeMode();
14738
14739     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14740         gameMode == AnalyzeFile)
14741         TruncateGame();
14742
14743     ResurrectChessProgram();    /* in case it isn't running */
14744     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14745         gameMode = MachinePlaysWhite;
14746         ResetClocks();
14747     } else
14748     gameMode = MachinePlaysWhite;
14749     pausing = FALSE;
14750     ModeHighlight();
14751     SetGameInfo();
14752     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14753     DisplayTitle(buf);
14754     if (first.sendName) {
14755       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14756       SendToProgram(buf, &first);
14757     }
14758     if (first.sendTime) {
14759       if (first.useColors) {
14760         SendToProgram("black\n", &first); /*gnu kludge*/
14761       }
14762       SendTimeRemaining(&first, TRUE);
14763     }
14764     if (first.useColors) {
14765       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14766     }
14767     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14768     SetMachineThinkingEnables();
14769     first.maybeThinking = TRUE;
14770     StartClocks();
14771     firstMove = FALSE;
14772
14773     if (appData.autoFlipView && !flipView) {
14774       flipView = !flipView;
14775       DrawPosition(FALSE, NULL);
14776       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14777     }
14778
14779     if(bookHit) { // [HGM] book: simulate book reply
14780         static char bookMove[MSG_SIZ]; // a bit generous?
14781
14782         programStats.nodes = programStats.depth = programStats.time =
14783         programStats.score = programStats.got_only_move = 0;
14784         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14785
14786         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14787         strcat(bookMove, bookHit);
14788         HandleMachineMove(bookMove, &first);
14789     }
14790 }
14791
14792 void
14793 MachineBlackEvent ()
14794 {
14795   char buf[MSG_SIZ];
14796   char *bookHit = NULL;
14797
14798     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14799         return;
14800
14801
14802     if (gameMode == PlayFromGameFile ||
14803         gameMode == TwoMachinesPlay  ||
14804         gameMode == Training         ||
14805         gameMode == AnalyzeMode      ||
14806         gameMode == EndOfGame)
14807         EditGameEvent();
14808
14809     if (gameMode == EditPosition)
14810         EditPositionDone(TRUE);
14811
14812     if (WhiteOnMove(currentMove)) {
14813         DisplayError(_("It is not Black's turn"), 0);
14814         return;
14815     }
14816
14817     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14818       ExitAnalyzeMode();
14819
14820     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14821         gameMode == AnalyzeFile)
14822         TruncateGame();
14823
14824     ResurrectChessProgram();    /* in case it isn't running */
14825     gameMode = MachinePlaysBlack;
14826     pausing = FALSE;
14827     ModeHighlight();
14828     SetGameInfo();
14829     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14830     DisplayTitle(buf);
14831     if (first.sendName) {
14832       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14833       SendToProgram(buf, &first);
14834     }
14835     if (first.sendTime) {
14836       if (first.useColors) {
14837         SendToProgram("white\n", &first); /*gnu kludge*/
14838       }
14839       SendTimeRemaining(&first, FALSE);
14840     }
14841     if (first.useColors) {
14842       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14843     }
14844     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14845     SetMachineThinkingEnables();
14846     first.maybeThinking = TRUE;
14847     StartClocks();
14848
14849     if (appData.autoFlipView && flipView) {
14850       flipView = !flipView;
14851       DrawPosition(FALSE, NULL);
14852       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14853     }
14854     if(bookHit) { // [HGM] book: simulate book reply
14855         static char bookMove[MSG_SIZ]; // a bit generous?
14856
14857         programStats.nodes = programStats.depth = programStats.time =
14858         programStats.score = programStats.got_only_move = 0;
14859         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14860
14861         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14862         strcat(bookMove, bookHit);
14863         HandleMachineMove(bookMove, &first);
14864     }
14865 }
14866
14867
14868 void
14869 DisplayTwoMachinesTitle ()
14870 {
14871     char buf[MSG_SIZ];
14872     if (appData.matchGames > 0) {
14873         if(appData.tourneyFile[0]) {
14874           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14875                    gameInfo.white, _("vs."), gameInfo.black,
14876                    nextGame+1, appData.matchGames+1,
14877                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14878         } else
14879         if (first.twoMachinesColor[0] == 'w') {
14880           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14881                    gameInfo.white, _("vs."),  gameInfo.black,
14882                    first.matchWins, second.matchWins,
14883                    matchGame - 1 - (first.matchWins + second.matchWins));
14884         } else {
14885           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14886                    gameInfo.white, _("vs."), gameInfo.black,
14887                    second.matchWins, first.matchWins,
14888                    matchGame - 1 - (first.matchWins + second.matchWins));
14889         }
14890     } else {
14891       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14892     }
14893     DisplayTitle(buf);
14894 }
14895
14896 void
14897 SettingsMenuIfReady ()
14898 {
14899   if (second.lastPing != second.lastPong) {
14900     DisplayMessage("", _("Waiting for second chess program"));
14901     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14902     return;
14903   }
14904   ThawUI();
14905   DisplayMessage("", "");
14906   SettingsPopUp(&second);
14907 }
14908
14909 int
14910 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14911 {
14912     char buf[MSG_SIZ];
14913     if (cps->pr == NoProc) {
14914         StartChessProgram(cps);
14915         if (cps->protocolVersion == 1) {
14916           retry();
14917           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14918         } else {
14919           /* kludge: allow timeout for initial "feature" command */
14920           if(retry != TwoMachinesEventIfReady) FreezeUI();
14921           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14922           DisplayMessage("", buf);
14923           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14924         }
14925         return 1;
14926     }
14927     return 0;
14928 }
14929
14930 void
14931 TwoMachinesEvent P((void))
14932 {
14933     int i;
14934     char buf[MSG_SIZ];
14935     ChessProgramState *onmove;
14936     char *bookHit = NULL;
14937     static int stalling = 0;
14938     TimeMark now;
14939     long wait;
14940
14941     if (appData.noChessProgram) return;
14942
14943     switch (gameMode) {
14944       case TwoMachinesPlay:
14945         return;
14946       case MachinePlaysWhite:
14947       case MachinePlaysBlack:
14948         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14949             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14950             return;
14951         }
14952         /* fall through */
14953       case BeginningOfGame:
14954       case PlayFromGameFile:
14955       case EndOfGame:
14956         EditGameEvent();
14957         if (gameMode != EditGame) return;
14958         break;
14959       case EditPosition:
14960         EditPositionDone(TRUE);
14961         break;
14962       case AnalyzeMode:
14963       case AnalyzeFile:
14964         ExitAnalyzeMode();
14965         break;
14966       case EditGame:
14967       default:
14968         break;
14969     }
14970
14971 //    forwardMostMove = currentMove;
14972     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14973     startingEngine = TRUE;
14974
14975     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14976
14977     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14978     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14979       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14980       return;
14981     }
14982     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14983
14984     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14985                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14986         startingEngine = matchMode = FALSE;
14987         DisplayError("second engine does not play this", 0);
14988         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14989         EditGameEvent(); // switch back to EditGame mode
14990         return;
14991     }
14992
14993     if(!stalling) {
14994       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14995       SendToProgram("force\n", &second);
14996       stalling = 1;
14997       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14998       return;
14999     }
15000     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15001     if(appData.matchPause>10000 || appData.matchPause<10)
15002                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15003     wait = SubtractTimeMarks(&now, &pauseStart);
15004     if(wait < appData.matchPause) {
15005         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15006         return;
15007     }
15008     // we are now committed to starting the game
15009     stalling = 0;
15010     DisplayMessage("", "");
15011     if (startedFromSetupPosition) {
15012         SendBoard(&second, backwardMostMove);
15013     if (appData.debugMode) {
15014         fprintf(debugFP, "Two Machines\n");
15015     }
15016     }
15017     for (i = backwardMostMove; i < forwardMostMove; i++) {
15018         SendMoveToProgram(i, &second);
15019     }
15020
15021     gameMode = TwoMachinesPlay;
15022     pausing = startingEngine = FALSE;
15023     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15024     SetGameInfo();
15025     DisplayTwoMachinesTitle();
15026     firstMove = TRUE;
15027     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15028         onmove = &first;
15029     } else {
15030         onmove = &second;
15031     }
15032     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15033     SendToProgram(first.computerString, &first);
15034     if (first.sendName) {
15035       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15036       SendToProgram(buf, &first);
15037     }
15038     SendToProgram(second.computerString, &second);
15039     if (second.sendName) {
15040       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15041       SendToProgram(buf, &second);
15042     }
15043
15044     ResetClocks();
15045     if (!first.sendTime || !second.sendTime) {
15046         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15047         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15048     }
15049     if (onmove->sendTime) {
15050       if (onmove->useColors) {
15051         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15052       }
15053       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15054     }
15055     if (onmove->useColors) {
15056       SendToProgram(onmove->twoMachinesColor, onmove);
15057     }
15058     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15059 //    SendToProgram("go\n", onmove);
15060     onmove->maybeThinking = TRUE;
15061     SetMachineThinkingEnables();
15062
15063     StartClocks();
15064
15065     if(bookHit) { // [HGM] book: simulate book reply
15066         static char bookMove[MSG_SIZ]; // a bit generous?
15067
15068         programStats.nodes = programStats.depth = programStats.time =
15069         programStats.score = programStats.got_only_move = 0;
15070         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15071
15072         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15073         strcat(bookMove, bookHit);
15074         savedMessage = bookMove; // args for deferred call
15075         savedState = onmove;
15076         ScheduleDelayedEvent(DeferredBookMove, 1);
15077     }
15078 }
15079
15080 void
15081 TrainingEvent ()
15082 {
15083     if (gameMode == Training) {
15084       SetTrainingModeOff();
15085       gameMode = PlayFromGameFile;
15086       DisplayMessage("", _("Training mode off"));
15087     } else {
15088       gameMode = Training;
15089       animateTraining = appData.animate;
15090
15091       /* make sure we are not already at the end of the game */
15092       if (currentMove < forwardMostMove) {
15093         SetTrainingModeOn();
15094         DisplayMessage("", _("Training mode on"));
15095       } else {
15096         gameMode = PlayFromGameFile;
15097         DisplayError(_("Already at end of game"), 0);
15098       }
15099     }
15100     ModeHighlight();
15101 }
15102
15103 void
15104 IcsClientEvent ()
15105 {
15106     if (!appData.icsActive) return;
15107     switch (gameMode) {
15108       case IcsPlayingWhite:
15109       case IcsPlayingBlack:
15110       case IcsObserving:
15111       case IcsIdle:
15112       case BeginningOfGame:
15113       case IcsExamining:
15114         return;
15115
15116       case EditGame:
15117         break;
15118
15119       case EditPosition:
15120         EditPositionDone(TRUE);
15121         break;
15122
15123       case AnalyzeMode:
15124       case AnalyzeFile:
15125         ExitAnalyzeMode();
15126         break;
15127
15128       default:
15129         EditGameEvent();
15130         break;
15131     }
15132
15133     gameMode = IcsIdle;
15134     ModeHighlight();
15135     return;
15136 }
15137
15138 void
15139 EditGameEvent ()
15140 {
15141     int i;
15142
15143     switch (gameMode) {
15144       case Training:
15145         SetTrainingModeOff();
15146         break;
15147       case MachinePlaysWhite:
15148       case MachinePlaysBlack:
15149       case BeginningOfGame:
15150         SendToProgram("force\n", &first);
15151         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15152             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15153                 char buf[MSG_SIZ];
15154                 abortEngineThink = TRUE;
15155                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15156                 SendToProgram(buf, &first);
15157                 DisplayMessage("Aborting engine think", "");
15158                 FreezeUI();
15159             }
15160         }
15161         SetUserThinkingEnables();
15162         break;
15163       case PlayFromGameFile:
15164         (void) StopLoadGameTimer();
15165         if (gameFileFP != NULL) {
15166             gameFileFP = NULL;
15167         }
15168         break;
15169       case EditPosition:
15170         EditPositionDone(TRUE);
15171         break;
15172       case AnalyzeMode:
15173       case AnalyzeFile:
15174         ExitAnalyzeMode();
15175         SendToProgram("force\n", &first);
15176         break;
15177       case TwoMachinesPlay:
15178         GameEnds(EndOfFile, NULL, GE_PLAYER);
15179         ResurrectChessProgram();
15180         SetUserThinkingEnables();
15181         break;
15182       case EndOfGame:
15183         ResurrectChessProgram();
15184         break;
15185       case IcsPlayingBlack:
15186       case IcsPlayingWhite:
15187         DisplayError(_("Warning: You are still playing a game"), 0);
15188         break;
15189       case IcsObserving:
15190         DisplayError(_("Warning: You are still observing a game"), 0);
15191         break;
15192       case IcsExamining:
15193         DisplayError(_("Warning: You are still examining a game"), 0);
15194         break;
15195       case IcsIdle:
15196         break;
15197       case EditGame:
15198       default:
15199         return;
15200     }
15201
15202     pausing = FALSE;
15203     StopClocks();
15204     first.offeredDraw = second.offeredDraw = 0;
15205
15206     if (gameMode == PlayFromGameFile) {
15207         whiteTimeRemaining = timeRemaining[0][currentMove];
15208         blackTimeRemaining = timeRemaining[1][currentMove];
15209         DisplayTitle("");
15210     }
15211
15212     if (gameMode == MachinePlaysWhite ||
15213         gameMode == MachinePlaysBlack ||
15214         gameMode == TwoMachinesPlay ||
15215         gameMode == EndOfGame) {
15216         i = forwardMostMove;
15217         while (i > currentMove) {
15218             SendToProgram("undo\n", &first);
15219             i--;
15220         }
15221         if(!adjustedClock) {
15222         whiteTimeRemaining = timeRemaining[0][currentMove];
15223         blackTimeRemaining = timeRemaining[1][currentMove];
15224         DisplayBothClocks();
15225         }
15226         if (whiteFlag || blackFlag) {
15227             whiteFlag = blackFlag = 0;
15228         }
15229         DisplayTitle("");
15230     }
15231
15232     gameMode = EditGame;
15233     ModeHighlight();
15234     SetGameInfo();
15235 }
15236
15237
15238 void
15239 EditPositionEvent ()
15240 {
15241     if (gameMode == EditPosition) {
15242         EditGameEvent();
15243         return;
15244     }
15245
15246     EditGameEvent();
15247     if (gameMode != EditGame) return;
15248
15249     gameMode = EditPosition;
15250     ModeHighlight();
15251     SetGameInfo();
15252     if (currentMove > 0)
15253       CopyBoard(boards[0], boards[currentMove]);
15254
15255     blackPlaysFirst = !WhiteOnMove(currentMove);
15256     ResetClocks();
15257     currentMove = forwardMostMove = backwardMostMove = 0;
15258     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15259     DisplayMove(-1);
15260     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15261 }
15262
15263 void
15264 ExitAnalyzeMode ()
15265 {
15266     /* [DM] icsEngineAnalyze - possible call from other functions */
15267     if (appData.icsEngineAnalyze) {
15268         appData.icsEngineAnalyze = FALSE;
15269
15270         DisplayMessage("",_("Close ICS engine analyze..."));
15271     }
15272     if (first.analysisSupport && first.analyzing) {
15273       SendToBoth("exit\n");
15274       first.analyzing = second.analyzing = FALSE;
15275     }
15276     thinkOutput[0] = NULLCHAR;
15277 }
15278
15279 void
15280 EditPositionDone (Boolean fakeRights)
15281 {
15282     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15283
15284     startedFromSetupPosition = TRUE;
15285     InitChessProgram(&first, FALSE);
15286     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15287       boards[0][EP_STATUS] = EP_NONE;
15288       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15289       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15290         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15291         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15292       } else boards[0][CASTLING][2] = NoRights;
15293       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15294         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15295         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15296       } else boards[0][CASTLING][5] = NoRights;
15297       if(gameInfo.variant == VariantSChess) {
15298         int i;
15299         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15300           boards[0][VIRGIN][i] = 0;
15301           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15302           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15303         }
15304       }
15305     }
15306     SendToProgram("force\n", &first);
15307     if (blackPlaysFirst) {
15308         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15309         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15310         currentMove = forwardMostMove = backwardMostMove = 1;
15311         CopyBoard(boards[1], boards[0]);
15312     } else {
15313         currentMove = forwardMostMove = backwardMostMove = 0;
15314     }
15315     SendBoard(&first, forwardMostMove);
15316     if (appData.debugMode) {
15317         fprintf(debugFP, "EditPosDone\n");
15318     }
15319     DisplayTitle("");
15320     DisplayMessage("", "");
15321     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15322     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15323     gameMode = EditGame;
15324     ModeHighlight();
15325     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15326     ClearHighlights(); /* [AS] */
15327 }
15328
15329 /* Pause for `ms' milliseconds */
15330 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15331 void
15332 TimeDelay (long ms)
15333 {
15334     TimeMark m1, m2;
15335
15336     GetTimeMark(&m1);
15337     do {
15338         GetTimeMark(&m2);
15339     } while (SubtractTimeMarks(&m2, &m1) < ms);
15340 }
15341
15342 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15343 void
15344 SendMultiLineToICS (char *buf)
15345 {
15346     char temp[MSG_SIZ+1], *p;
15347     int len;
15348
15349     len = strlen(buf);
15350     if (len > MSG_SIZ)
15351       len = MSG_SIZ;
15352
15353     strncpy(temp, buf, len);
15354     temp[len] = 0;
15355
15356     p = temp;
15357     while (*p) {
15358         if (*p == '\n' || *p == '\r')
15359           *p = ' ';
15360         ++p;
15361     }
15362
15363     strcat(temp, "\n");
15364     SendToICS(temp);
15365     SendToPlayer(temp, strlen(temp));
15366 }
15367
15368 void
15369 SetWhiteToPlayEvent ()
15370 {
15371     if (gameMode == EditPosition) {
15372         blackPlaysFirst = FALSE;
15373         DisplayBothClocks();    /* works because currentMove is 0 */
15374     } else if (gameMode == IcsExamining) {
15375         SendToICS(ics_prefix);
15376         SendToICS("tomove white\n");
15377     }
15378 }
15379
15380 void
15381 SetBlackToPlayEvent ()
15382 {
15383     if (gameMode == EditPosition) {
15384         blackPlaysFirst = TRUE;
15385         currentMove = 1;        /* kludge */
15386         DisplayBothClocks();
15387         currentMove = 0;
15388     } else if (gameMode == IcsExamining) {
15389         SendToICS(ics_prefix);
15390         SendToICS("tomove black\n");
15391     }
15392 }
15393
15394 void
15395 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15396 {
15397     char buf[MSG_SIZ];
15398     ChessSquare piece = boards[0][y][x];
15399     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15400     static int lastVariant;
15401
15402     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15403
15404     switch (selection) {
15405       case ClearBoard:
15406         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15407         MarkTargetSquares(1);
15408         CopyBoard(currentBoard, boards[0]);
15409         CopyBoard(menuBoard, initialPosition);
15410         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15411             SendToICS(ics_prefix);
15412             SendToICS("bsetup clear\n");
15413         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15414             SendToICS(ics_prefix);
15415             SendToICS("clearboard\n");
15416         } else {
15417             int nonEmpty = 0;
15418             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15419                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15420                 for (y = 0; y < BOARD_HEIGHT; y++) {
15421                     if (gameMode == IcsExamining) {
15422                         if (boards[currentMove][y][x] != EmptySquare) {
15423                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15424                                     AAA + x, ONE + y);
15425                             SendToICS(buf);
15426                         }
15427                     } else if(boards[0][y][x] != DarkSquare) {
15428                         if(boards[0][y][x] != p) nonEmpty++;
15429                         boards[0][y][x] = p;
15430                     }
15431                 }
15432             }
15433             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15434                 int r;
15435                 for(r = 0; r < BOARD_HEIGHT; r++) {
15436                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15437                     ChessSquare p = menuBoard[r][x];
15438                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15439                   }
15440                 }
15441                 DisplayMessage("Clicking clock again restores position", "");
15442                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15443                 if(!nonEmpty) { // asked to clear an empty board
15444                     CopyBoard(boards[0], menuBoard);
15445                 } else
15446                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15447                     CopyBoard(boards[0], initialPosition);
15448                 } else
15449                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15450                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15451                     CopyBoard(boards[0], erasedBoard);
15452                 } else
15453                     CopyBoard(erasedBoard, currentBoard);
15454
15455             }
15456         }
15457         if (gameMode == EditPosition) {
15458             DrawPosition(FALSE, boards[0]);
15459         }
15460         break;
15461
15462       case WhitePlay:
15463         SetWhiteToPlayEvent();
15464         break;
15465
15466       case BlackPlay:
15467         SetBlackToPlayEvent();
15468         break;
15469
15470       case EmptySquare:
15471         if (gameMode == IcsExamining) {
15472             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15473             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15474             SendToICS(buf);
15475         } else {
15476             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15477                 if(x == BOARD_LEFT-2) {
15478                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15479                     boards[0][y][1] = 0;
15480                 } else
15481                 if(x == BOARD_RGHT+1) {
15482                     if(y >= gameInfo.holdingsSize) break;
15483                     boards[0][y][BOARD_WIDTH-2] = 0;
15484                 } else break;
15485             }
15486             boards[0][y][x] = EmptySquare;
15487             DrawPosition(FALSE, boards[0]);
15488         }
15489         break;
15490
15491       case PromotePiece:
15492         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15493            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15494             selection = (ChessSquare) (PROMOTED(piece));
15495         } else if(piece == EmptySquare) selection = WhiteSilver;
15496         else selection = (ChessSquare)((int)piece - 1);
15497         goto defaultlabel;
15498
15499       case DemotePiece:
15500         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15501            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15502             selection = (ChessSquare) (DEMOTED(piece));
15503         } else if(piece == EmptySquare) selection = BlackSilver;
15504         else selection = (ChessSquare)((int)piece + 1);
15505         goto defaultlabel;
15506
15507       case WhiteQueen:
15508       case BlackQueen:
15509         if(gameInfo.variant == VariantShatranj ||
15510            gameInfo.variant == VariantXiangqi  ||
15511            gameInfo.variant == VariantCourier  ||
15512            gameInfo.variant == VariantASEAN    ||
15513            gameInfo.variant == VariantMakruk     )
15514             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15515         goto defaultlabel;
15516
15517       case WhiteKing:
15518       case BlackKing:
15519         if(gameInfo.variant == VariantXiangqi)
15520             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15521         if(gameInfo.variant == VariantKnightmate)
15522             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15523       default:
15524         defaultlabel:
15525         if (gameMode == IcsExamining) {
15526             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15527             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15528                      PieceToChar(selection), AAA + x, ONE + y);
15529             SendToICS(buf);
15530         } else {
15531             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15532                 int n;
15533                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15534                     n = PieceToNumber(selection - BlackPawn);
15535                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15536                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15537                     boards[0][BOARD_HEIGHT-1-n][1]++;
15538                 } else
15539                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15540                     n = PieceToNumber(selection);
15541                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15542                     boards[0][n][BOARD_WIDTH-1] = selection;
15543                     boards[0][n][BOARD_WIDTH-2]++;
15544                 }
15545             } else
15546             boards[0][y][x] = selection;
15547             DrawPosition(TRUE, boards[0]);
15548             ClearHighlights();
15549             fromX = fromY = -1;
15550         }
15551         break;
15552     }
15553 }
15554
15555
15556 void
15557 DropMenuEvent (ChessSquare selection, int x, int y)
15558 {
15559     ChessMove moveType;
15560
15561     switch (gameMode) {
15562       case IcsPlayingWhite:
15563       case MachinePlaysBlack:
15564         if (!WhiteOnMove(currentMove)) {
15565             DisplayMoveError(_("It is Black's turn"));
15566             return;
15567         }
15568         moveType = WhiteDrop;
15569         break;
15570       case IcsPlayingBlack:
15571       case MachinePlaysWhite:
15572         if (WhiteOnMove(currentMove)) {
15573             DisplayMoveError(_("It is White's turn"));
15574             return;
15575         }
15576         moveType = BlackDrop;
15577         break;
15578       case EditGame:
15579         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15580         break;
15581       default:
15582         return;
15583     }
15584
15585     if (moveType == BlackDrop && selection < BlackPawn) {
15586       selection = (ChessSquare) ((int) selection
15587                                  + (int) BlackPawn - (int) WhitePawn);
15588     }
15589     if (boards[currentMove][y][x] != EmptySquare) {
15590         DisplayMoveError(_("That square is occupied"));
15591         return;
15592     }
15593
15594     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15595 }
15596
15597 void
15598 AcceptEvent ()
15599 {
15600     /* Accept a pending offer of any kind from opponent */
15601
15602     if (appData.icsActive) {
15603         SendToICS(ics_prefix);
15604         SendToICS("accept\n");
15605     } else if (cmailMsgLoaded) {
15606         if (currentMove == cmailOldMove &&
15607             commentList[cmailOldMove] != NULL &&
15608             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15609                    "Black offers a draw" : "White offers a draw")) {
15610             TruncateGame();
15611             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15612             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15613         } else {
15614             DisplayError(_("There is no pending offer on this move"), 0);
15615             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15616         }
15617     } else {
15618         /* Not used for offers from chess program */
15619     }
15620 }
15621
15622 void
15623 DeclineEvent ()
15624 {
15625     /* Decline a pending offer of any kind from opponent */
15626
15627     if (appData.icsActive) {
15628         SendToICS(ics_prefix);
15629         SendToICS("decline\n");
15630     } else if (cmailMsgLoaded) {
15631         if (currentMove == cmailOldMove &&
15632             commentList[cmailOldMove] != NULL &&
15633             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15634                    "Black offers a draw" : "White offers a draw")) {
15635 #ifdef NOTDEF
15636             AppendComment(cmailOldMove, "Draw declined", TRUE);
15637             DisplayComment(cmailOldMove - 1, "Draw declined");
15638 #endif /*NOTDEF*/
15639         } else {
15640             DisplayError(_("There is no pending offer on this move"), 0);
15641         }
15642     } else {
15643         /* Not used for offers from chess program */
15644     }
15645 }
15646
15647 void
15648 RematchEvent ()
15649 {
15650     /* Issue ICS rematch command */
15651     if (appData.icsActive) {
15652         SendToICS(ics_prefix);
15653         SendToICS("rematch\n");
15654     }
15655 }
15656
15657 void
15658 CallFlagEvent ()
15659 {
15660     /* Call your opponent's flag (claim a win on time) */
15661     if (appData.icsActive) {
15662         SendToICS(ics_prefix);
15663         SendToICS("flag\n");
15664     } else {
15665         switch (gameMode) {
15666           default:
15667             return;
15668           case MachinePlaysWhite:
15669             if (whiteFlag) {
15670                 if (blackFlag)
15671                   GameEnds(GameIsDrawn, "Both players ran out of time",
15672                            GE_PLAYER);
15673                 else
15674                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15675             } else {
15676                 DisplayError(_("Your opponent is not out of time"), 0);
15677             }
15678             break;
15679           case MachinePlaysBlack:
15680             if (blackFlag) {
15681                 if (whiteFlag)
15682                   GameEnds(GameIsDrawn, "Both players ran out of time",
15683                            GE_PLAYER);
15684                 else
15685                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15686             } else {
15687                 DisplayError(_("Your opponent is not out of time"), 0);
15688             }
15689             break;
15690         }
15691     }
15692 }
15693
15694 void
15695 ClockClick (int which)
15696 {       // [HGM] code moved to back-end from winboard.c
15697         if(which) { // black clock
15698           if (gameMode == EditPosition || gameMode == IcsExamining) {
15699             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15700             SetBlackToPlayEvent();
15701           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15702                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15703           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15704           } else if (shiftKey) {
15705             AdjustClock(which, -1);
15706           } else if (gameMode == IcsPlayingWhite ||
15707                      gameMode == MachinePlaysBlack) {
15708             CallFlagEvent();
15709           }
15710         } else { // white clock
15711           if (gameMode == EditPosition || gameMode == IcsExamining) {
15712             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15713             SetWhiteToPlayEvent();
15714           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15715                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15716           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15717           } else if (shiftKey) {
15718             AdjustClock(which, -1);
15719           } else if (gameMode == IcsPlayingBlack ||
15720                    gameMode == MachinePlaysWhite) {
15721             CallFlagEvent();
15722           }
15723         }
15724 }
15725
15726 void
15727 DrawEvent ()
15728 {
15729     /* Offer draw or accept pending draw offer from opponent */
15730
15731     if (appData.icsActive) {
15732         /* Note: tournament rules require draw offers to be
15733            made after you make your move but before you punch
15734            your clock.  Currently ICS doesn't let you do that;
15735            instead, you immediately punch your clock after making
15736            a move, but you can offer a draw at any time. */
15737
15738         SendToICS(ics_prefix);
15739         SendToICS("draw\n");
15740         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15741     } else if (cmailMsgLoaded) {
15742         if (currentMove == cmailOldMove &&
15743             commentList[cmailOldMove] != NULL &&
15744             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15745                    "Black offers a draw" : "White offers a draw")) {
15746             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15747             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15748         } else if (currentMove == cmailOldMove + 1) {
15749             char *offer = WhiteOnMove(cmailOldMove) ?
15750               "White offers a draw" : "Black offers a draw";
15751             AppendComment(currentMove, offer, TRUE);
15752             DisplayComment(currentMove - 1, offer);
15753             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15754         } else {
15755             DisplayError(_("You must make your move before offering a draw"), 0);
15756             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15757         }
15758     } else if (first.offeredDraw) {
15759         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15760     } else {
15761         if (first.sendDrawOffers) {
15762             SendToProgram("draw\n", &first);
15763             userOfferedDraw = TRUE;
15764         }
15765     }
15766 }
15767
15768 void
15769 AdjournEvent ()
15770 {
15771     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15772
15773     if (appData.icsActive) {
15774         SendToICS(ics_prefix);
15775         SendToICS("adjourn\n");
15776     } else {
15777         /* Currently GNU Chess doesn't offer or accept Adjourns */
15778     }
15779 }
15780
15781
15782 void
15783 AbortEvent ()
15784 {
15785     /* Offer Abort or accept pending Abort offer from opponent */
15786
15787     if (appData.icsActive) {
15788         SendToICS(ics_prefix);
15789         SendToICS("abort\n");
15790     } else {
15791         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15792     }
15793 }
15794
15795 void
15796 ResignEvent ()
15797 {
15798     /* Resign.  You can do this even if it's not your turn. */
15799
15800     if (appData.icsActive) {
15801         SendToICS(ics_prefix);
15802         SendToICS("resign\n");
15803     } else {
15804         switch (gameMode) {
15805           case MachinePlaysWhite:
15806             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15807             break;
15808           case MachinePlaysBlack:
15809             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15810             break;
15811           case EditGame:
15812             if (cmailMsgLoaded) {
15813                 TruncateGame();
15814                 if (WhiteOnMove(cmailOldMove)) {
15815                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15816                 } else {
15817                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15818                 }
15819                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15820             }
15821             break;
15822           default:
15823             break;
15824         }
15825     }
15826 }
15827
15828
15829 void
15830 StopObservingEvent ()
15831 {
15832     /* Stop observing current games */
15833     SendToICS(ics_prefix);
15834     SendToICS("unobserve\n");
15835 }
15836
15837 void
15838 StopExaminingEvent ()
15839 {
15840     /* Stop observing current game */
15841     SendToICS(ics_prefix);
15842     SendToICS("unexamine\n");
15843 }
15844
15845 void
15846 ForwardInner (int target)
15847 {
15848     int limit; int oldSeekGraphUp = seekGraphUp;
15849
15850     if (appData.debugMode)
15851         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15852                 target, currentMove, forwardMostMove);
15853
15854     if (gameMode == EditPosition)
15855       return;
15856
15857     seekGraphUp = FALSE;
15858     MarkTargetSquares(1);
15859     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15860
15861     if (gameMode == PlayFromGameFile && !pausing)
15862       PauseEvent();
15863
15864     if (gameMode == IcsExamining && pausing)
15865       limit = pauseExamForwardMostMove;
15866     else
15867       limit = forwardMostMove;
15868
15869     if (target > limit) target = limit;
15870
15871     if (target > 0 && moveList[target - 1][0]) {
15872         int fromX, fromY, toX, toY;
15873         toX = moveList[target - 1][2] - AAA;
15874         toY = moveList[target - 1][3] - ONE;
15875         if (moveList[target - 1][1] == '@') {
15876             if (appData.highlightLastMove) {
15877                 SetHighlights(-1, -1, toX, toY);
15878             }
15879         } else {
15880             fromX = moveList[target - 1][0] - AAA;
15881             fromY = moveList[target - 1][1] - ONE;
15882             if (target == currentMove + 1) {
15883                 if(moveList[target - 1][4] == ';') { // multi-leg
15884                     killX = moveList[target - 1][5] - AAA;
15885                     killY = moveList[target - 1][6] - ONE;
15886                 }
15887                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15888                 killX = killY = -1;
15889             }
15890             if (appData.highlightLastMove) {
15891                 SetHighlights(fromX, fromY, toX, toY);
15892             }
15893         }
15894     }
15895     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15896         gameMode == Training || gameMode == PlayFromGameFile ||
15897         gameMode == AnalyzeFile) {
15898         while (currentMove < target) {
15899             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15900             SendMoveToProgram(currentMove++, &first);
15901         }
15902     } else {
15903         currentMove = target;
15904     }
15905
15906     if (gameMode == EditGame || gameMode == EndOfGame) {
15907         whiteTimeRemaining = timeRemaining[0][currentMove];
15908         blackTimeRemaining = timeRemaining[1][currentMove];
15909     }
15910     DisplayBothClocks();
15911     DisplayMove(currentMove - 1);
15912     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15913     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15914     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15915         DisplayComment(currentMove - 1, commentList[currentMove]);
15916     }
15917     ClearMap(); // [HGM] exclude: invalidate map
15918 }
15919
15920
15921 void
15922 ForwardEvent ()
15923 {
15924     if (gameMode == IcsExamining && !pausing) {
15925         SendToICS(ics_prefix);
15926         SendToICS("forward\n");
15927     } else {
15928         ForwardInner(currentMove + 1);
15929     }
15930 }
15931
15932 void
15933 ToEndEvent ()
15934 {
15935     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15936         /* to optimze, we temporarily turn off analysis mode while we feed
15937          * the remaining moves to the engine. Otherwise we get analysis output
15938          * after each move.
15939          */
15940         if (first.analysisSupport) {
15941           SendToProgram("exit\nforce\n", &first);
15942           first.analyzing = FALSE;
15943         }
15944     }
15945
15946     if (gameMode == IcsExamining && !pausing) {
15947         SendToICS(ics_prefix);
15948         SendToICS("forward 999999\n");
15949     } else {
15950         ForwardInner(forwardMostMove);
15951     }
15952
15953     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15954         /* we have fed all the moves, so reactivate analysis mode */
15955         SendToProgram("analyze\n", &first);
15956         first.analyzing = TRUE;
15957         /*first.maybeThinking = TRUE;*/
15958         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15959     }
15960 }
15961
15962 void
15963 BackwardInner (int target)
15964 {
15965     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15966
15967     if (appData.debugMode)
15968         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15969                 target, currentMove, forwardMostMove);
15970
15971     if (gameMode == EditPosition) return;
15972     seekGraphUp = FALSE;
15973     MarkTargetSquares(1);
15974     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15975     if (currentMove <= backwardMostMove) {
15976         ClearHighlights();
15977         DrawPosition(full_redraw, boards[currentMove]);
15978         return;
15979     }
15980     if (gameMode == PlayFromGameFile && !pausing)
15981       PauseEvent();
15982
15983     if (moveList[target][0]) {
15984         int fromX, fromY, toX, toY;
15985         toX = moveList[target][2] - AAA;
15986         toY = moveList[target][3] - ONE;
15987         if (moveList[target][1] == '@') {
15988             if (appData.highlightLastMove) {
15989                 SetHighlights(-1, -1, toX, toY);
15990             }
15991         } else {
15992             fromX = moveList[target][0] - AAA;
15993             fromY = moveList[target][1] - ONE;
15994             if (target == currentMove - 1) {
15995                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15996             }
15997             if (appData.highlightLastMove) {
15998                 SetHighlights(fromX, fromY, toX, toY);
15999             }
16000         }
16001     }
16002     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16003         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16004         while (currentMove > target) {
16005             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16006                 // null move cannot be undone. Reload program with move history before it.
16007                 int i;
16008                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16009                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16010                 }
16011                 SendBoard(&first, i);
16012               if(second.analyzing) SendBoard(&second, i);
16013                 for(currentMove=i; currentMove<target; currentMove++) {
16014                     SendMoveToProgram(currentMove, &first);
16015                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16016                 }
16017                 break;
16018             }
16019             SendToBoth("undo\n");
16020             currentMove--;
16021         }
16022     } else {
16023         currentMove = target;
16024     }
16025
16026     if (gameMode == EditGame || gameMode == EndOfGame) {
16027         whiteTimeRemaining = timeRemaining[0][currentMove];
16028         blackTimeRemaining = timeRemaining[1][currentMove];
16029     }
16030     DisplayBothClocks();
16031     DisplayMove(currentMove - 1);
16032     DrawPosition(full_redraw, boards[currentMove]);
16033     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16034     // [HGM] PV info: routine tests if comment empty
16035     DisplayComment(currentMove - 1, commentList[currentMove]);
16036     ClearMap(); // [HGM] exclude: invalidate map
16037 }
16038
16039 void
16040 BackwardEvent ()
16041 {
16042     if (gameMode == IcsExamining && !pausing) {
16043         SendToICS(ics_prefix);
16044         SendToICS("backward\n");
16045     } else {
16046         BackwardInner(currentMove - 1);
16047     }
16048 }
16049
16050 void
16051 ToStartEvent ()
16052 {
16053     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16054         /* to optimize, we temporarily turn off analysis mode while we undo
16055          * all the moves. Otherwise we get analysis output after each undo.
16056          */
16057         if (first.analysisSupport) {
16058           SendToProgram("exit\nforce\n", &first);
16059           first.analyzing = FALSE;
16060         }
16061     }
16062
16063     if (gameMode == IcsExamining && !pausing) {
16064         SendToICS(ics_prefix);
16065         SendToICS("backward 999999\n");
16066     } else {
16067         BackwardInner(backwardMostMove);
16068     }
16069
16070     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16071         /* we have fed all the moves, so reactivate analysis mode */
16072         SendToProgram("analyze\n", &first);
16073         first.analyzing = TRUE;
16074         /*first.maybeThinking = TRUE;*/
16075         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16076     }
16077 }
16078
16079 void
16080 ToNrEvent (int to)
16081 {
16082   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16083   if (to >= forwardMostMove) to = forwardMostMove;
16084   if (to <= backwardMostMove) to = backwardMostMove;
16085   if (to < currentMove) {
16086     BackwardInner(to);
16087   } else {
16088     ForwardInner(to);
16089   }
16090 }
16091
16092 void
16093 RevertEvent (Boolean annotate)
16094 {
16095     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16096         return;
16097     }
16098     if (gameMode != IcsExamining) {
16099         DisplayError(_("You are not examining a game"), 0);
16100         return;
16101     }
16102     if (pausing) {
16103         DisplayError(_("You can't revert while pausing"), 0);
16104         return;
16105     }
16106     SendToICS(ics_prefix);
16107     SendToICS("revert\n");
16108 }
16109
16110 void
16111 RetractMoveEvent ()
16112 {
16113     switch (gameMode) {
16114       case MachinePlaysWhite:
16115       case MachinePlaysBlack:
16116         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16117             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16118             return;
16119         }
16120         if (forwardMostMove < 2) return;
16121         currentMove = forwardMostMove = forwardMostMove - 2;
16122         whiteTimeRemaining = timeRemaining[0][currentMove];
16123         blackTimeRemaining = timeRemaining[1][currentMove];
16124         DisplayBothClocks();
16125         DisplayMove(currentMove - 1);
16126         ClearHighlights();/*!! could figure this out*/
16127         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16128         SendToProgram("remove\n", &first);
16129         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16130         break;
16131
16132       case BeginningOfGame:
16133       default:
16134         break;
16135
16136       case IcsPlayingWhite:
16137       case IcsPlayingBlack:
16138         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16139             SendToICS(ics_prefix);
16140             SendToICS("takeback 2\n");
16141         } else {
16142             SendToICS(ics_prefix);
16143             SendToICS("takeback 1\n");
16144         }
16145         break;
16146     }
16147 }
16148
16149 void
16150 MoveNowEvent ()
16151 {
16152     ChessProgramState *cps;
16153
16154     switch (gameMode) {
16155       case MachinePlaysWhite:
16156         if (!WhiteOnMove(forwardMostMove)) {
16157             DisplayError(_("It is your turn"), 0);
16158             return;
16159         }
16160         cps = &first;
16161         break;
16162       case MachinePlaysBlack:
16163         if (WhiteOnMove(forwardMostMove)) {
16164             DisplayError(_("It is your turn"), 0);
16165             return;
16166         }
16167         cps = &first;
16168         break;
16169       case TwoMachinesPlay:
16170         if (WhiteOnMove(forwardMostMove) ==
16171             (first.twoMachinesColor[0] == 'w')) {
16172             cps = &first;
16173         } else {
16174             cps = &second;
16175         }
16176         break;
16177       case BeginningOfGame:
16178       default:
16179         return;
16180     }
16181     SendToProgram("?\n", cps);
16182 }
16183
16184 void
16185 TruncateGameEvent ()
16186 {
16187     EditGameEvent();
16188     if (gameMode != EditGame) return;
16189     TruncateGame();
16190 }
16191
16192 void
16193 TruncateGame ()
16194 {
16195     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16196     if (forwardMostMove > currentMove) {
16197         if (gameInfo.resultDetails != NULL) {
16198             free(gameInfo.resultDetails);
16199             gameInfo.resultDetails = NULL;
16200             gameInfo.result = GameUnfinished;
16201         }
16202         forwardMostMove = currentMove;
16203         HistorySet(parseList, backwardMostMove, forwardMostMove,
16204                    currentMove-1);
16205     }
16206 }
16207
16208 void
16209 HintEvent ()
16210 {
16211     if (appData.noChessProgram) return;
16212     switch (gameMode) {
16213       case MachinePlaysWhite:
16214         if (WhiteOnMove(forwardMostMove)) {
16215             DisplayError(_("Wait until your turn."), 0);
16216             return;
16217         }
16218         break;
16219       case BeginningOfGame:
16220       case MachinePlaysBlack:
16221         if (!WhiteOnMove(forwardMostMove)) {
16222             DisplayError(_("Wait until your turn."), 0);
16223             return;
16224         }
16225         break;
16226       default:
16227         DisplayError(_("No hint available"), 0);
16228         return;
16229     }
16230     SendToProgram("hint\n", &first);
16231     hintRequested = TRUE;
16232 }
16233
16234 int
16235 SaveSelected (FILE *g, int dummy, char *dummy2)
16236 {
16237     ListGame * lg = (ListGame *) gameList.head;
16238     int nItem, cnt=0;
16239     FILE *f;
16240
16241     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16242         DisplayError(_("Game list not loaded or empty"), 0);
16243         return 0;
16244     }
16245
16246     creatingBook = TRUE; // suppresses stuff during load game
16247
16248     /* Get list size */
16249     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16250         if(lg->position >= 0) { // selected?
16251             LoadGame(f, nItem, "", TRUE);
16252             SaveGamePGN2(g); // leaves g open
16253             cnt++; DoEvents();
16254         }
16255         lg = (ListGame *) lg->node.succ;
16256     }
16257
16258     fclose(g);
16259     creatingBook = FALSE;
16260
16261     return cnt;
16262 }
16263
16264 void
16265 CreateBookEvent ()
16266 {
16267     ListGame * lg = (ListGame *) gameList.head;
16268     FILE *f, *g;
16269     int nItem;
16270     static int secondTime = FALSE;
16271
16272     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16273         DisplayError(_("Game list not loaded or empty"), 0);
16274         return;
16275     }
16276
16277     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16278         fclose(g);
16279         secondTime++;
16280         DisplayNote(_("Book file exists! Try again for overwrite."));
16281         return;
16282     }
16283
16284     creatingBook = TRUE;
16285     secondTime = FALSE;
16286
16287     /* Get list size */
16288     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16289         if(lg->position >= 0) {
16290             LoadGame(f, nItem, "", TRUE);
16291             AddGameToBook(TRUE);
16292             DoEvents();
16293         }
16294         lg = (ListGame *) lg->node.succ;
16295     }
16296
16297     creatingBook = FALSE;
16298     FlushBook();
16299 }
16300
16301 void
16302 BookEvent ()
16303 {
16304     if (appData.noChessProgram) return;
16305     switch (gameMode) {
16306       case MachinePlaysWhite:
16307         if (WhiteOnMove(forwardMostMove)) {
16308             DisplayError(_("Wait until your turn."), 0);
16309             return;
16310         }
16311         break;
16312       case BeginningOfGame:
16313       case MachinePlaysBlack:
16314         if (!WhiteOnMove(forwardMostMove)) {
16315             DisplayError(_("Wait until your turn."), 0);
16316             return;
16317         }
16318         break;
16319       case EditPosition:
16320         EditPositionDone(TRUE);
16321         break;
16322       case TwoMachinesPlay:
16323         return;
16324       default:
16325         break;
16326     }
16327     SendToProgram("bk\n", &first);
16328     bookOutput[0] = NULLCHAR;
16329     bookRequested = TRUE;
16330 }
16331
16332 void
16333 AboutGameEvent ()
16334 {
16335     char *tags = PGNTags(&gameInfo);
16336     TagsPopUp(tags, CmailMsg());
16337     free(tags);
16338 }
16339
16340 /* end button procedures */
16341
16342 void
16343 PrintPosition (FILE *fp, int move)
16344 {
16345     int i, j;
16346
16347     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16348         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16349             char c = PieceToChar(boards[move][i][j]);
16350             fputc(c == '?' ? '.' : c, fp);
16351             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16352         }
16353     }
16354     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16355       fprintf(fp, "white to play\n");
16356     else
16357       fprintf(fp, "black to play\n");
16358 }
16359
16360 void
16361 PrintOpponents (FILE *fp)
16362 {
16363     if (gameInfo.white != NULL) {
16364         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16365     } else {
16366         fprintf(fp, "\n");
16367     }
16368 }
16369
16370 /* Find last component of program's own name, using some heuristics */
16371 void
16372 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16373 {
16374     char *p, *q, c;
16375     int local = (strcmp(host, "localhost") == 0);
16376     while (!local && (p = strchr(prog, ';')) != NULL) {
16377         p++;
16378         while (*p == ' ') p++;
16379         prog = p;
16380     }
16381     if (*prog == '"' || *prog == '\'') {
16382         q = strchr(prog + 1, *prog);
16383     } else {
16384         q = strchr(prog, ' ');
16385     }
16386     if (q == NULL) q = prog + strlen(prog);
16387     p = q;
16388     while (p >= prog && *p != '/' && *p != '\\') p--;
16389     p++;
16390     if(p == prog && *p == '"') p++;
16391     c = *q; *q = 0;
16392     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16393     memcpy(buf, p, q - p);
16394     buf[q - p] = NULLCHAR;
16395     if (!local) {
16396         strcat(buf, "@");
16397         strcat(buf, host);
16398     }
16399 }
16400
16401 char *
16402 TimeControlTagValue ()
16403 {
16404     char buf[MSG_SIZ];
16405     if (!appData.clockMode) {
16406       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16407     } else if (movesPerSession > 0) {
16408       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16409     } else if (timeIncrement == 0) {
16410       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16411     } else {
16412       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16413     }
16414     return StrSave(buf);
16415 }
16416
16417 void
16418 SetGameInfo ()
16419 {
16420     /* This routine is used only for certain modes */
16421     VariantClass v = gameInfo.variant;
16422     ChessMove r = GameUnfinished;
16423     char *p = NULL;
16424
16425     if(keepInfo) return;
16426
16427     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16428         r = gameInfo.result;
16429         p = gameInfo.resultDetails;
16430         gameInfo.resultDetails = NULL;
16431     }
16432     ClearGameInfo(&gameInfo);
16433     gameInfo.variant = v;
16434
16435     switch (gameMode) {
16436       case MachinePlaysWhite:
16437         gameInfo.event = StrSave( appData.pgnEventHeader );
16438         gameInfo.site = StrSave(HostName());
16439         gameInfo.date = PGNDate();
16440         gameInfo.round = StrSave("-");
16441         gameInfo.white = StrSave(first.tidy);
16442         gameInfo.black = StrSave(UserName());
16443         gameInfo.timeControl = TimeControlTagValue();
16444         break;
16445
16446       case MachinePlaysBlack:
16447         gameInfo.event = StrSave( appData.pgnEventHeader );
16448         gameInfo.site = StrSave(HostName());
16449         gameInfo.date = PGNDate();
16450         gameInfo.round = StrSave("-");
16451         gameInfo.white = StrSave(UserName());
16452         gameInfo.black = StrSave(first.tidy);
16453         gameInfo.timeControl = TimeControlTagValue();
16454         break;
16455
16456       case TwoMachinesPlay:
16457         gameInfo.event = StrSave( appData.pgnEventHeader );
16458         gameInfo.site = StrSave(HostName());
16459         gameInfo.date = PGNDate();
16460         if (roundNr > 0) {
16461             char buf[MSG_SIZ];
16462             snprintf(buf, MSG_SIZ, "%d", roundNr);
16463             gameInfo.round = StrSave(buf);
16464         } else {
16465             gameInfo.round = StrSave("-");
16466         }
16467         if (first.twoMachinesColor[0] == 'w') {
16468             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16469             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16470         } else {
16471             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16472             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16473         }
16474         gameInfo.timeControl = TimeControlTagValue();
16475         break;
16476
16477       case EditGame:
16478         gameInfo.event = StrSave("Edited game");
16479         gameInfo.site = StrSave(HostName());
16480         gameInfo.date = PGNDate();
16481         gameInfo.round = StrSave("-");
16482         gameInfo.white = StrSave("-");
16483         gameInfo.black = StrSave("-");
16484         gameInfo.result = r;
16485         gameInfo.resultDetails = p;
16486         break;
16487
16488       case EditPosition:
16489         gameInfo.event = StrSave("Edited position");
16490         gameInfo.site = StrSave(HostName());
16491         gameInfo.date = PGNDate();
16492         gameInfo.round = StrSave("-");
16493         gameInfo.white = StrSave("-");
16494         gameInfo.black = StrSave("-");
16495         break;
16496
16497       case IcsPlayingWhite:
16498       case IcsPlayingBlack:
16499       case IcsObserving:
16500       case IcsExamining:
16501         break;
16502
16503       case PlayFromGameFile:
16504         gameInfo.event = StrSave("Game from non-PGN file");
16505         gameInfo.site = StrSave(HostName());
16506         gameInfo.date = PGNDate();
16507         gameInfo.round = StrSave("-");
16508         gameInfo.white = StrSave("?");
16509         gameInfo.black = StrSave("?");
16510         break;
16511
16512       default:
16513         break;
16514     }
16515 }
16516
16517 void
16518 ReplaceComment (int index, char *text)
16519 {
16520     int len;
16521     char *p;
16522     float score;
16523
16524     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16525        pvInfoList[index-1].depth == len &&
16526        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16527        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16528     while (*text == '\n') text++;
16529     len = strlen(text);
16530     while (len > 0 && text[len - 1] == '\n') len--;
16531
16532     if (commentList[index] != NULL)
16533       free(commentList[index]);
16534
16535     if (len == 0) {
16536         commentList[index] = NULL;
16537         return;
16538     }
16539   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16540       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16541       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16542     commentList[index] = (char *) malloc(len + 2);
16543     strncpy(commentList[index], text, len);
16544     commentList[index][len] = '\n';
16545     commentList[index][len + 1] = NULLCHAR;
16546   } else {
16547     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16548     char *p;
16549     commentList[index] = (char *) malloc(len + 7);
16550     safeStrCpy(commentList[index], "{\n", 3);
16551     safeStrCpy(commentList[index]+2, text, len+1);
16552     commentList[index][len+2] = NULLCHAR;
16553     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16554     strcat(commentList[index], "\n}\n");
16555   }
16556 }
16557
16558 void
16559 CrushCRs (char *text)
16560 {
16561   char *p = text;
16562   char *q = text;
16563   char ch;
16564
16565   do {
16566     ch = *p++;
16567     if (ch == '\r') continue;
16568     *q++ = ch;
16569   } while (ch != '\0');
16570 }
16571
16572 void
16573 AppendComment (int index, char *text, Boolean addBraces)
16574 /* addBraces  tells if we should add {} */
16575 {
16576     int oldlen, len;
16577     char *old;
16578
16579 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16580     if(addBraces == 3) addBraces = 0; else // force appending literally
16581     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16582
16583     CrushCRs(text);
16584     while (*text == '\n') text++;
16585     len = strlen(text);
16586     while (len > 0 && text[len - 1] == '\n') len--;
16587     text[len] = NULLCHAR;
16588
16589     if (len == 0) return;
16590
16591     if (commentList[index] != NULL) {
16592       Boolean addClosingBrace = addBraces;
16593         old = commentList[index];
16594         oldlen = strlen(old);
16595         while(commentList[index][oldlen-1] ==  '\n')
16596           commentList[index][--oldlen] = NULLCHAR;
16597         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16598         safeStrCpy(commentList[index], old, oldlen + len + 6);
16599         free(old);
16600         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16601         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16602           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16603           while (*text == '\n') { text++; len--; }
16604           commentList[index][--oldlen] = NULLCHAR;
16605       }
16606         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16607         else          strcat(commentList[index], "\n");
16608         strcat(commentList[index], text);
16609         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16610         else          strcat(commentList[index], "\n");
16611     } else {
16612         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16613         if(addBraces)
16614           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16615         else commentList[index][0] = NULLCHAR;
16616         strcat(commentList[index], text);
16617         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16618         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16619     }
16620 }
16621
16622 static char *
16623 FindStr (char * text, char * sub_text)
16624 {
16625     char * result = strstr( text, sub_text );
16626
16627     if( result != NULL ) {
16628         result += strlen( sub_text );
16629     }
16630
16631     return result;
16632 }
16633
16634 /* [AS] Try to extract PV info from PGN comment */
16635 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16636 char *
16637 GetInfoFromComment (int index, char * text)
16638 {
16639     char * sep = text, *p;
16640
16641     if( text != NULL && index > 0 ) {
16642         int score = 0;
16643         int depth = 0;
16644         int time = -1, sec = 0, deci;
16645         char * s_eval = FindStr( text, "[%eval " );
16646         char * s_emt = FindStr( text, "[%emt " );
16647 #if 0
16648         if( s_eval != NULL || s_emt != NULL ) {
16649 #else
16650         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16651 #endif
16652             /* New style */
16653             char delim;
16654
16655             if( s_eval != NULL ) {
16656                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16657                     return text;
16658                 }
16659
16660                 if( delim != ']' ) {
16661                     return text;
16662                 }
16663             }
16664
16665             if( s_emt != NULL ) {
16666             }
16667                 return text;
16668         }
16669         else {
16670             /* We expect something like: [+|-]nnn.nn/dd */
16671             int score_lo = 0;
16672
16673             if(*text != '{') return text; // [HGM] braces: must be normal comment
16674
16675             sep = strchr( text, '/' );
16676             if( sep == NULL || sep < (text+4) ) {
16677                 return text;
16678             }
16679
16680             p = text;
16681             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16682             if(p[1] == '(') { // comment starts with PV
16683                p = strchr(p, ')'); // locate end of PV
16684                if(p == NULL || sep < p+5) return text;
16685                // at this point we have something like "{(.*) +0.23/6 ..."
16686                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16687                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16688                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16689             }
16690             time = -1; sec = -1; deci = -1;
16691             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16692                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16693                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16694                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16695                 return text;
16696             }
16697
16698             if( score_lo < 0 || score_lo >= 100 ) {
16699                 return text;
16700             }
16701
16702             if(sec >= 0) time = 600*time + 10*sec; else
16703             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16704
16705             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16706
16707             /* [HGM] PV time: now locate end of PV info */
16708             while( *++sep >= '0' && *sep <= '9'); // strip depth
16709             if(time >= 0)
16710             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16711             if(sec >= 0)
16712             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16713             if(deci >= 0)
16714             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16715             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16716         }
16717
16718         if( depth <= 0 ) {
16719             return text;
16720         }
16721
16722         if( time < 0 ) {
16723             time = -1;
16724         }
16725
16726         pvInfoList[index-1].depth = depth;
16727         pvInfoList[index-1].score = score;
16728         pvInfoList[index-1].time  = 10*time; // centi-sec
16729         if(*sep == '}') *sep = 0; else *--sep = '{';
16730         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16731     }
16732     return sep;
16733 }
16734
16735 void
16736 SendToProgram (char *message, ChessProgramState *cps)
16737 {
16738     int count, outCount, error;
16739     char buf[MSG_SIZ];
16740
16741     if (cps->pr == NoProc) return;
16742     Attention(cps);
16743
16744     if (appData.debugMode) {
16745         TimeMark now;
16746         GetTimeMark(&now);
16747         fprintf(debugFP, "%ld >%-6s: %s",
16748                 SubtractTimeMarks(&now, &programStartTime),
16749                 cps->which, message);
16750         if(serverFP)
16751             fprintf(serverFP, "%ld >%-6s: %s",
16752                 SubtractTimeMarks(&now, &programStartTime),
16753                 cps->which, message), fflush(serverFP);
16754     }
16755
16756     count = strlen(message);
16757     outCount = OutputToProcess(cps->pr, message, count, &error);
16758     if (outCount < count && !exiting
16759                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16760       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16761       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16762         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16763             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16764                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16765                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16766                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16767             } else {
16768                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16769                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16770                 gameInfo.result = res;
16771             }
16772             gameInfo.resultDetails = StrSave(buf);
16773         }
16774         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16775         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16776     }
16777 }
16778
16779 void
16780 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16781 {
16782     char *end_str;
16783     char buf[MSG_SIZ];
16784     ChessProgramState *cps = (ChessProgramState *)closure;
16785
16786     if (isr != cps->isr) return; /* Killed intentionally */
16787     if (count <= 0) {
16788         if (count == 0) {
16789             RemoveInputSource(cps->isr);
16790             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16791                     _(cps->which), cps->program);
16792             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16793             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16794                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16795                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16796                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16797                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16798                 } else {
16799                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16800                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16801                     gameInfo.result = res;
16802                 }
16803                 gameInfo.resultDetails = StrSave(buf);
16804             }
16805             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16806             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16807         } else {
16808             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16809                     _(cps->which), cps->program);
16810             RemoveInputSource(cps->isr);
16811
16812             /* [AS] Program is misbehaving badly... kill it */
16813             if( count == -2 ) {
16814                 DestroyChildProcess( cps->pr, 9 );
16815                 cps->pr = NoProc;
16816             }
16817
16818             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16819         }
16820         return;
16821     }
16822
16823     if ((end_str = strchr(message, '\r')) != NULL)
16824       *end_str = NULLCHAR;
16825     if ((end_str = strchr(message, '\n')) != NULL)
16826       *end_str = NULLCHAR;
16827
16828     if (appData.debugMode) {
16829         TimeMark now; int print = 1;
16830         char *quote = ""; char c; int i;
16831
16832         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16833                 char start = message[0];
16834                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16835                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16836                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16837                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16838                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16839                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16840                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16841                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16842                    sscanf(message, "hint: %c", &c)!=1 &&
16843                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16844                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16845                     print = (appData.engineComments >= 2);
16846                 }
16847                 message[0] = start; // restore original message
16848         }
16849         if(print) {
16850                 GetTimeMark(&now);
16851                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16852                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16853                         quote,
16854                         message);
16855                 if(serverFP)
16856                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16857                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16858                         quote,
16859                         message), fflush(serverFP);
16860         }
16861     }
16862
16863     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16864     if (appData.icsEngineAnalyze) {
16865         if (strstr(message, "whisper") != NULL ||
16866              strstr(message, "kibitz") != NULL ||
16867             strstr(message, "tellics") != NULL) return;
16868     }
16869
16870     HandleMachineMove(message, cps);
16871 }
16872
16873
16874 void
16875 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16876 {
16877     char buf[MSG_SIZ];
16878     int seconds;
16879
16880     if( timeControl_2 > 0 ) {
16881         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16882             tc = timeControl_2;
16883         }
16884     }
16885     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16886     inc /= cps->timeOdds;
16887     st  /= cps->timeOdds;
16888
16889     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16890
16891     if (st > 0) {
16892       /* Set exact time per move, normally using st command */
16893       if (cps->stKludge) {
16894         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16895         seconds = st % 60;
16896         if (seconds == 0) {
16897           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16898         } else {
16899           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16900         }
16901       } else {
16902         snprintf(buf, MSG_SIZ, "st %d\n", st);
16903       }
16904     } else {
16905       /* Set conventional or incremental time control, using level command */
16906       if (seconds == 0) {
16907         /* Note old gnuchess bug -- minutes:seconds used to not work.
16908            Fixed in later versions, but still avoid :seconds
16909            when seconds is 0. */
16910         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16911       } else {
16912         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16913                  seconds, inc/1000.);
16914       }
16915     }
16916     SendToProgram(buf, cps);
16917
16918     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16919     /* Orthogonally, limit search to given depth */
16920     if (sd > 0) {
16921       if (cps->sdKludge) {
16922         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16923       } else {
16924         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16925       }
16926       SendToProgram(buf, cps);
16927     }
16928
16929     if(cps->nps >= 0) { /* [HGM] nps */
16930         if(cps->supportsNPS == FALSE)
16931           cps->nps = -1; // don't use if engine explicitly says not supported!
16932         else {
16933           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16934           SendToProgram(buf, cps);
16935         }
16936     }
16937 }
16938
16939 ChessProgramState *
16940 WhitePlayer ()
16941 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16942 {
16943     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16944        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16945         return &second;
16946     return &first;
16947 }
16948
16949 void
16950 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16951 {
16952     char message[MSG_SIZ];
16953     long time, otime;
16954
16955     /* Note: this routine must be called when the clocks are stopped
16956        or when they have *just* been set or switched; otherwise
16957        it will be off by the time since the current tick started.
16958     */
16959     if (machineWhite) {
16960         time = whiteTimeRemaining / 10;
16961         otime = blackTimeRemaining / 10;
16962     } else {
16963         time = blackTimeRemaining / 10;
16964         otime = whiteTimeRemaining / 10;
16965     }
16966     /* [HGM] translate opponent's time by time-odds factor */
16967     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16968
16969     if (time <= 0) time = 1;
16970     if (otime <= 0) otime = 1;
16971
16972     snprintf(message, MSG_SIZ, "time %ld\n", time);
16973     SendToProgram(message, cps);
16974
16975     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16976     SendToProgram(message, cps);
16977 }
16978
16979 char *
16980 EngineDefinedVariant (ChessProgramState *cps, int n)
16981 {   // return name of n-th unknown variant that engine supports
16982     static char buf[MSG_SIZ];
16983     char *p, *s = cps->variants;
16984     if(!s) return NULL;
16985     do { // parse string from variants feature
16986       VariantClass v;
16987         p = strchr(s, ',');
16988         if(p) *p = NULLCHAR;
16989       v = StringToVariant(s);
16990       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16991         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16992             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16993                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16994                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16995                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16996             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16997         }
16998         if(p) *p++ = ',';
16999         if(n < 0) return buf;
17000     } while(s = p);
17001     return NULL;
17002 }
17003
17004 int
17005 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17006 {
17007   char buf[MSG_SIZ];
17008   int len = strlen(name);
17009   int val;
17010
17011   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17012     (*p) += len + 1;
17013     sscanf(*p, "%d", &val);
17014     *loc = (val != 0);
17015     while (**p && **p != ' ')
17016       (*p)++;
17017     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17018     SendToProgram(buf, cps);
17019     return TRUE;
17020   }
17021   return FALSE;
17022 }
17023
17024 int
17025 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17026 {
17027   char buf[MSG_SIZ];
17028   int len = strlen(name);
17029   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17030     (*p) += len + 1;
17031     sscanf(*p, "%d", loc);
17032     while (**p && **p != ' ') (*p)++;
17033     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17034     SendToProgram(buf, cps);
17035     return TRUE;
17036   }
17037   return FALSE;
17038 }
17039
17040 int
17041 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17042 {
17043   char buf[MSG_SIZ];
17044   int len = strlen(name);
17045   if (strncmp((*p), name, len) == 0
17046       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17047     (*p) += len + 2;
17048     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17049     sscanf(*p, "%[^\"]", *loc);
17050     while (**p && **p != '\"') (*p)++;
17051     if (**p == '\"') (*p)++;
17052     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17053     SendToProgram(buf, cps);
17054     return TRUE;
17055   }
17056   return FALSE;
17057 }
17058
17059 int
17060 ParseOption (Option *opt, ChessProgramState *cps)
17061 // [HGM] options: process the string that defines an engine option, and determine
17062 // name, type, default value, and allowed value range
17063 {
17064         char *p, *q, buf[MSG_SIZ];
17065         int n, min = (-1)<<31, max = 1<<31, def;
17066
17067         opt->target = &opt->value;   // OK for spin/slider and checkbox
17068         if(p = strstr(opt->name, " -spin ")) {
17069             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17070             if(max < min) max = min; // enforce consistency
17071             if(def < min) def = min;
17072             if(def > max) def = max;
17073             opt->value = def;
17074             opt->min = min;
17075             opt->max = max;
17076             opt->type = Spin;
17077         } else if((p = strstr(opt->name, " -slider "))) {
17078             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17079             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17080             if(max < min) max = min; // enforce consistency
17081             if(def < min) def = min;
17082             if(def > max) def = max;
17083             opt->value = def;
17084             opt->min = min;
17085             opt->max = max;
17086             opt->type = Spin; // Slider;
17087         } else if((p = strstr(opt->name, " -string "))) {
17088             opt->textValue = p+9;
17089             opt->type = TextBox;
17090             opt->target = &opt->textValue;
17091         } else if((p = strstr(opt->name, " -file "))) {
17092             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17093             opt->target = opt->textValue = p+7;
17094             opt->type = FileName; // FileName;
17095             opt->target = &opt->textValue;
17096         } else if((p = strstr(opt->name, " -path "))) {
17097             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17098             opt->target = opt->textValue = p+7;
17099             opt->type = PathName; // PathName;
17100             opt->target = &opt->textValue;
17101         } else if(p = strstr(opt->name, " -check ")) {
17102             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17103             opt->value = (def != 0);
17104             opt->type = CheckBox;
17105         } else if(p = strstr(opt->name, " -combo ")) {
17106             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17107             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17108             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17109             opt->value = n = 0;
17110             while(q = StrStr(q, " /// ")) {
17111                 n++; *q = 0;    // count choices, and null-terminate each of them
17112                 q += 5;
17113                 if(*q == '*') { // remember default, which is marked with * prefix
17114                     q++;
17115                     opt->value = n;
17116                 }
17117                 cps->comboList[cps->comboCnt++] = q;
17118             }
17119             cps->comboList[cps->comboCnt++] = NULL;
17120             opt->max = n + 1;
17121             opt->type = ComboBox;
17122         } else if(p = strstr(opt->name, " -button")) {
17123             opt->type = Button;
17124         } else if(p = strstr(opt->name, " -save")) {
17125             opt->type = SaveButton;
17126         } else return FALSE;
17127         *p = 0; // terminate option name
17128         // now look if the command-line options define a setting for this engine option.
17129         if(cps->optionSettings && cps->optionSettings[0])
17130             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17131         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17132           snprintf(buf, MSG_SIZ, "option %s", p);
17133                 if(p = strstr(buf, ",")) *p = 0;
17134                 if(q = strchr(buf, '=')) switch(opt->type) {
17135                     case ComboBox:
17136                         for(n=0; n<opt->max; n++)
17137                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17138                         break;
17139                     case TextBox:
17140                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17141                         break;
17142                     case Spin:
17143                     case CheckBox:
17144                         opt->value = atoi(q+1);
17145                     default:
17146                         break;
17147                 }
17148                 strcat(buf, "\n");
17149                 SendToProgram(buf, cps);
17150         }
17151         return TRUE;
17152 }
17153
17154 void
17155 FeatureDone (ChessProgramState *cps, int val)
17156 {
17157   DelayedEventCallback cb = GetDelayedEvent();
17158   if ((cb == InitBackEnd3 && cps == &first) ||
17159       (cb == SettingsMenuIfReady && cps == &second) ||
17160       (cb == LoadEngine) ||
17161       (cb == TwoMachinesEventIfReady)) {
17162     CancelDelayedEvent();
17163     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17164   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17165   cps->initDone = val;
17166   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17167 }
17168
17169 /* Parse feature command from engine */
17170 void
17171 ParseFeatures (char *args, ChessProgramState *cps)
17172 {
17173   char *p = args;
17174   char *q = NULL;
17175   int val;
17176   char buf[MSG_SIZ];
17177
17178   for (;;) {
17179     while (*p == ' ') p++;
17180     if (*p == NULLCHAR) return;
17181
17182     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17183     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17184     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17185     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17186     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17187     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17188     if (BoolFeature(&p, "reuse", &val, cps)) {
17189       /* Engine can disable reuse, but can't enable it if user said no */
17190       if (!val) cps->reuse = FALSE;
17191       continue;
17192     }
17193     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17194     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17195       if (gameMode == TwoMachinesPlay) {
17196         DisplayTwoMachinesTitle();
17197       } else {
17198         DisplayTitle("");
17199       }
17200       continue;
17201     }
17202     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17203     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17204     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17205     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17206     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17207     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17208     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17209     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17210     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17211     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17212     if (IntFeature(&p, "done", &val, cps)) {
17213       FeatureDone(cps, val);
17214       continue;
17215     }
17216     /* Added by Tord: */
17217     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17218     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17219     /* End of additions by Tord */
17220
17221     /* [HGM] added features: */
17222     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17223     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17224     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17225     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17226     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17227     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17228     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17229     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17230         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17231         FREE(cps->option[cps->nrOptions].name);
17232         cps->option[cps->nrOptions].name = q; q = NULL;
17233         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17234           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17235             SendToProgram(buf, cps);
17236             continue;
17237         }
17238         if(cps->nrOptions >= MAX_OPTIONS) {
17239             cps->nrOptions--;
17240             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17241             DisplayError(buf, 0);
17242         }
17243         continue;
17244     }
17245     /* End of additions by HGM */
17246
17247     /* unknown feature: complain and skip */
17248     q = p;
17249     while (*q && *q != '=') q++;
17250     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17251     SendToProgram(buf, cps);
17252     p = q;
17253     if (*p == '=') {
17254       p++;
17255       if (*p == '\"') {
17256         p++;
17257         while (*p && *p != '\"') p++;
17258         if (*p == '\"') p++;
17259       } else {
17260         while (*p && *p != ' ') p++;
17261       }
17262     }
17263   }
17264
17265 }
17266
17267 void
17268 PeriodicUpdatesEvent (int newState)
17269 {
17270     if (newState == appData.periodicUpdates)
17271       return;
17272
17273     appData.periodicUpdates=newState;
17274
17275     /* Display type changes, so update it now */
17276 //    DisplayAnalysis();
17277
17278     /* Get the ball rolling again... */
17279     if (newState) {
17280         AnalysisPeriodicEvent(1);
17281         StartAnalysisClock();
17282     }
17283 }
17284
17285 void
17286 PonderNextMoveEvent (int newState)
17287 {
17288     if (newState == appData.ponderNextMove) return;
17289     if (gameMode == EditPosition) EditPositionDone(TRUE);
17290     if (newState) {
17291         SendToProgram("hard\n", &first);
17292         if (gameMode == TwoMachinesPlay) {
17293             SendToProgram("hard\n", &second);
17294         }
17295     } else {
17296         SendToProgram("easy\n", &first);
17297         thinkOutput[0] = NULLCHAR;
17298         if (gameMode == TwoMachinesPlay) {
17299             SendToProgram("easy\n", &second);
17300         }
17301     }
17302     appData.ponderNextMove = newState;
17303 }
17304
17305 void
17306 NewSettingEvent (int option, int *feature, char *command, int value)
17307 {
17308     char buf[MSG_SIZ];
17309
17310     if (gameMode == EditPosition) EditPositionDone(TRUE);
17311     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17312     if(feature == NULL || *feature) SendToProgram(buf, &first);
17313     if (gameMode == TwoMachinesPlay) {
17314         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17315     }
17316 }
17317
17318 void
17319 ShowThinkingEvent ()
17320 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17321 {
17322     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17323     int newState = appData.showThinking
17324         // [HGM] thinking: other features now need thinking output as well
17325         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17326
17327     if (oldState == newState) return;
17328     oldState = newState;
17329     if (gameMode == EditPosition) EditPositionDone(TRUE);
17330     if (oldState) {
17331         SendToProgram("post\n", &first);
17332         if (gameMode == TwoMachinesPlay) {
17333             SendToProgram("post\n", &second);
17334         }
17335     } else {
17336         SendToProgram("nopost\n", &first);
17337         thinkOutput[0] = NULLCHAR;
17338         if (gameMode == TwoMachinesPlay) {
17339             SendToProgram("nopost\n", &second);
17340         }
17341     }
17342 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17343 }
17344
17345 void
17346 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17347 {
17348   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17349   if (pr == NoProc) return;
17350   AskQuestion(title, question, replyPrefix, pr);
17351 }
17352
17353 void
17354 TypeInEvent (char firstChar)
17355 {
17356     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17357         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17358         gameMode == AnalyzeMode || gameMode == EditGame ||
17359         gameMode == EditPosition || gameMode == IcsExamining ||
17360         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17361         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17362                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17363                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17364         gameMode == Training) PopUpMoveDialog(firstChar);
17365 }
17366
17367 void
17368 TypeInDoneEvent (char *move)
17369 {
17370         Board board;
17371         int n, fromX, fromY, toX, toY;
17372         char promoChar;
17373         ChessMove moveType;
17374
17375         // [HGM] FENedit
17376         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17377                 EditPositionPasteFEN(move);
17378                 return;
17379         }
17380         // [HGM] movenum: allow move number to be typed in any mode
17381         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17382           ToNrEvent(2*n-1);
17383           return;
17384         }
17385         // undocumented kludge: allow command-line option to be typed in!
17386         // (potentially fatal, and does not implement the effect of the option.)
17387         // should only be used for options that are values on which future decisions will be made,
17388         // and definitely not on options that would be used during initialization.
17389         if(strstr(move, "!!! -") == move) {
17390             ParseArgsFromString(move+4);
17391             return;
17392         }
17393
17394       if (gameMode != EditGame && currentMove != forwardMostMove &&
17395         gameMode != Training) {
17396         DisplayMoveError(_("Displayed move is not current"));
17397       } else {
17398         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17399           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17400         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17401         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17402           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17403           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17404         } else {
17405           DisplayMoveError(_("Could not parse move"));
17406         }
17407       }
17408 }
17409
17410 void
17411 DisplayMove (int moveNumber)
17412 {
17413     char message[MSG_SIZ];
17414     char res[MSG_SIZ];
17415     char cpThinkOutput[MSG_SIZ];
17416
17417     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17418
17419     if (moveNumber == forwardMostMove - 1 ||
17420         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17421
17422         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17423
17424         if (strchr(cpThinkOutput, '\n')) {
17425             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17426         }
17427     } else {
17428         *cpThinkOutput = NULLCHAR;
17429     }
17430
17431     /* [AS] Hide thinking from human user */
17432     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17433         *cpThinkOutput = NULLCHAR;
17434         if( thinkOutput[0] != NULLCHAR ) {
17435             int i;
17436
17437             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17438                 cpThinkOutput[i] = '.';
17439             }
17440             cpThinkOutput[i] = NULLCHAR;
17441             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17442         }
17443     }
17444
17445     if (moveNumber == forwardMostMove - 1 &&
17446         gameInfo.resultDetails != NULL) {
17447         if (gameInfo.resultDetails[0] == NULLCHAR) {
17448           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17449         } else {
17450           snprintf(res, MSG_SIZ, " {%s} %s",
17451                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17452         }
17453     } else {
17454         res[0] = NULLCHAR;
17455     }
17456
17457     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17458         DisplayMessage(res, cpThinkOutput);
17459     } else {
17460       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17461                 WhiteOnMove(moveNumber) ? " " : ".. ",
17462                 parseList[moveNumber], res);
17463         DisplayMessage(message, cpThinkOutput);
17464     }
17465 }
17466
17467 void
17468 DisplayComment (int moveNumber, char *text)
17469 {
17470     char title[MSG_SIZ];
17471
17472     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17473       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17474     } else {
17475       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17476               WhiteOnMove(moveNumber) ? " " : ".. ",
17477               parseList[moveNumber]);
17478     }
17479     if (text != NULL && (appData.autoDisplayComment || commentUp))
17480         CommentPopUp(title, text);
17481 }
17482
17483 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17484  * might be busy thinking or pondering.  It can be omitted if your
17485  * gnuchess is configured to stop thinking immediately on any user
17486  * input.  However, that gnuchess feature depends on the FIONREAD
17487  * ioctl, which does not work properly on some flavors of Unix.
17488  */
17489 void
17490 Attention (ChessProgramState *cps)
17491 {
17492 #if ATTENTION
17493     if (!cps->useSigint) return;
17494     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17495     switch (gameMode) {
17496       case MachinePlaysWhite:
17497       case MachinePlaysBlack:
17498       case TwoMachinesPlay:
17499       case IcsPlayingWhite:
17500       case IcsPlayingBlack:
17501       case AnalyzeMode:
17502       case AnalyzeFile:
17503         /* Skip if we know it isn't thinking */
17504         if (!cps->maybeThinking) return;
17505         if (appData.debugMode)
17506           fprintf(debugFP, "Interrupting %s\n", cps->which);
17507         InterruptChildProcess(cps->pr);
17508         cps->maybeThinking = FALSE;
17509         break;
17510       default:
17511         break;
17512     }
17513 #endif /*ATTENTION*/
17514 }
17515
17516 int
17517 CheckFlags ()
17518 {
17519     if (whiteTimeRemaining <= 0) {
17520         if (!whiteFlag) {
17521             whiteFlag = TRUE;
17522             if (appData.icsActive) {
17523                 if (appData.autoCallFlag &&
17524                     gameMode == IcsPlayingBlack && !blackFlag) {
17525                   SendToICS(ics_prefix);
17526                   SendToICS("flag\n");
17527                 }
17528             } else {
17529                 if (blackFlag) {
17530                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17531                 } else {
17532                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17533                     if (appData.autoCallFlag) {
17534                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17535                         return TRUE;
17536                     }
17537                 }
17538             }
17539         }
17540     }
17541     if (blackTimeRemaining <= 0) {
17542         if (!blackFlag) {
17543             blackFlag = TRUE;
17544             if (appData.icsActive) {
17545                 if (appData.autoCallFlag &&
17546                     gameMode == IcsPlayingWhite && !whiteFlag) {
17547                   SendToICS(ics_prefix);
17548                   SendToICS("flag\n");
17549                 }
17550             } else {
17551                 if (whiteFlag) {
17552                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17553                 } else {
17554                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17555                     if (appData.autoCallFlag) {
17556                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17557                         return TRUE;
17558                     }
17559                 }
17560             }
17561         }
17562     }
17563     return FALSE;
17564 }
17565
17566 void
17567 CheckTimeControl ()
17568 {
17569     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17570         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17571
17572     /*
17573      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17574      */
17575     if ( !WhiteOnMove(forwardMostMove) ) {
17576         /* White made time control */
17577         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17578         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17579         /* [HGM] time odds: correct new time quota for time odds! */
17580                                             / WhitePlayer()->timeOdds;
17581         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17582     } else {
17583         lastBlack -= blackTimeRemaining;
17584         /* Black made time control */
17585         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17586                                             / WhitePlayer()->other->timeOdds;
17587         lastWhite = whiteTimeRemaining;
17588     }
17589 }
17590
17591 void
17592 DisplayBothClocks ()
17593 {
17594     int wom = gameMode == EditPosition ?
17595       !blackPlaysFirst : WhiteOnMove(currentMove);
17596     DisplayWhiteClock(whiteTimeRemaining, wom);
17597     DisplayBlackClock(blackTimeRemaining, !wom);
17598 }
17599
17600
17601 /* Timekeeping seems to be a portability nightmare.  I think everyone
17602    has ftime(), but I'm really not sure, so I'm including some ifdefs
17603    to use other calls if you don't.  Clocks will be less accurate if
17604    you have neither ftime nor gettimeofday.
17605 */
17606
17607 /* VS 2008 requires the #include outside of the function */
17608 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17609 #include <sys/timeb.h>
17610 #endif
17611
17612 /* Get the current time as a TimeMark */
17613 void
17614 GetTimeMark (TimeMark *tm)
17615 {
17616 #if HAVE_GETTIMEOFDAY
17617
17618     struct timeval timeVal;
17619     struct timezone timeZone;
17620
17621     gettimeofday(&timeVal, &timeZone);
17622     tm->sec = (long) timeVal.tv_sec;
17623     tm->ms = (int) (timeVal.tv_usec / 1000L);
17624
17625 #else /*!HAVE_GETTIMEOFDAY*/
17626 #if HAVE_FTIME
17627
17628 // include <sys/timeb.h> / moved to just above start of function
17629     struct timeb timeB;
17630
17631     ftime(&timeB);
17632     tm->sec = (long) timeB.time;
17633     tm->ms = (int) timeB.millitm;
17634
17635 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17636     tm->sec = (long) time(NULL);
17637     tm->ms = 0;
17638 #endif
17639 #endif
17640 }
17641
17642 /* Return the difference in milliseconds between two
17643    time marks.  We assume the difference will fit in a long!
17644 */
17645 long
17646 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17647 {
17648     return 1000L*(tm2->sec - tm1->sec) +
17649            (long) (tm2->ms - tm1->ms);
17650 }
17651
17652
17653 /*
17654  * Code to manage the game clocks.
17655  *
17656  * In tournament play, black starts the clock and then white makes a move.
17657  * We give the human user a slight advantage if he is playing white---the
17658  * clocks don't run until he makes his first move, so it takes zero time.
17659  * Also, we don't account for network lag, so we could get out of sync
17660  * with GNU Chess's clock -- but then, referees are always right.
17661  */
17662
17663 static TimeMark tickStartTM;
17664 static long intendedTickLength;
17665
17666 long
17667 NextTickLength (long timeRemaining)
17668 {
17669     long nominalTickLength, nextTickLength;
17670
17671     if (timeRemaining > 0L && timeRemaining <= 10000L)
17672       nominalTickLength = 100L;
17673     else
17674       nominalTickLength = 1000L;
17675     nextTickLength = timeRemaining % nominalTickLength;
17676     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17677
17678     return nextTickLength;
17679 }
17680
17681 /* Adjust clock one minute up or down */
17682 void
17683 AdjustClock (Boolean which, int dir)
17684 {
17685     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17686     if(which) blackTimeRemaining += 60000*dir;
17687     else      whiteTimeRemaining += 60000*dir;
17688     DisplayBothClocks();
17689     adjustedClock = TRUE;
17690 }
17691
17692 /* Stop clocks and reset to a fresh time control */
17693 void
17694 ResetClocks ()
17695 {
17696     (void) StopClockTimer();
17697     if (appData.icsActive) {
17698         whiteTimeRemaining = blackTimeRemaining = 0;
17699     } else if (searchTime) {
17700         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17701         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17702     } else { /* [HGM] correct new time quote for time odds */
17703         whiteTC = blackTC = fullTimeControlString;
17704         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17705         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17706     }
17707     if (whiteFlag || blackFlag) {
17708         DisplayTitle("");
17709         whiteFlag = blackFlag = FALSE;
17710     }
17711     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17712     DisplayBothClocks();
17713     adjustedClock = FALSE;
17714 }
17715
17716 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17717
17718 /* Decrement running clock by amount of time that has passed */
17719 void
17720 DecrementClocks ()
17721 {
17722     long timeRemaining;
17723     long lastTickLength, fudge;
17724     TimeMark now;
17725
17726     if (!appData.clockMode) return;
17727     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17728
17729     GetTimeMark(&now);
17730
17731     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17732
17733     /* Fudge if we woke up a little too soon */
17734     fudge = intendedTickLength - lastTickLength;
17735     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17736
17737     if (WhiteOnMove(forwardMostMove)) {
17738         if(whiteNPS >= 0) lastTickLength = 0;
17739         timeRemaining = whiteTimeRemaining -= lastTickLength;
17740         if(timeRemaining < 0 && !appData.icsActive) {
17741             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17742             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17743                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17744                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17745             }
17746         }
17747         DisplayWhiteClock(whiteTimeRemaining - fudge,
17748                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17749     } else {
17750         if(blackNPS >= 0) lastTickLength = 0;
17751         timeRemaining = blackTimeRemaining -= lastTickLength;
17752         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17753             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17754             if(suddenDeath) {
17755                 blackStartMove = forwardMostMove;
17756                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17757             }
17758         }
17759         DisplayBlackClock(blackTimeRemaining - fudge,
17760                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17761     }
17762     if (CheckFlags()) return;
17763
17764     if(twoBoards) { // count down secondary board's clocks as well
17765         activePartnerTime -= lastTickLength;
17766         partnerUp = 1;
17767         if(activePartner == 'W')
17768             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17769         else
17770             DisplayBlackClock(activePartnerTime, TRUE);
17771         partnerUp = 0;
17772     }
17773
17774     tickStartTM = now;
17775     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17776     StartClockTimer(intendedTickLength);
17777
17778     /* if the time remaining has fallen below the alarm threshold, sound the
17779      * alarm. if the alarm has sounded and (due to a takeback or time control
17780      * with increment) the time remaining has increased to a level above the
17781      * threshold, reset the alarm so it can sound again.
17782      */
17783
17784     if (appData.icsActive && appData.icsAlarm) {
17785
17786         /* make sure we are dealing with the user's clock */
17787         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17788                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17789            )) return;
17790
17791         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17792             alarmSounded = FALSE;
17793         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17794             PlayAlarmSound();
17795             alarmSounded = TRUE;
17796         }
17797     }
17798 }
17799
17800
17801 /* A player has just moved, so stop the previously running
17802    clock and (if in clock mode) start the other one.
17803    We redisplay both clocks in case we're in ICS mode, because
17804    ICS gives us an update to both clocks after every move.
17805    Note that this routine is called *after* forwardMostMove
17806    is updated, so the last fractional tick must be subtracted
17807    from the color that is *not* on move now.
17808 */
17809 void
17810 SwitchClocks (int newMoveNr)
17811 {
17812     long lastTickLength;
17813     TimeMark now;
17814     int flagged = FALSE;
17815
17816     GetTimeMark(&now);
17817
17818     if (StopClockTimer() && appData.clockMode) {
17819         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17820         if (!WhiteOnMove(forwardMostMove)) {
17821             if(blackNPS >= 0) lastTickLength = 0;
17822             blackTimeRemaining -= lastTickLength;
17823            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17824 //         if(pvInfoList[forwardMostMove].time == -1)
17825                  pvInfoList[forwardMostMove].time =               // use GUI time
17826                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17827         } else {
17828            if(whiteNPS >= 0) lastTickLength = 0;
17829            whiteTimeRemaining -= lastTickLength;
17830            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17831 //         if(pvInfoList[forwardMostMove].time == -1)
17832                  pvInfoList[forwardMostMove].time =
17833                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17834         }
17835         flagged = CheckFlags();
17836     }
17837     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17838     CheckTimeControl();
17839
17840     if (flagged || !appData.clockMode) return;
17841
17842     switch (gameMode) {
17843       case MachinePlaysBlack:
17844       case MachinePlaysWhite:
17845       case BeginningOfGame:
17846         if (pausing) return;
17847         break;
17848
17849       case EditGame:
17850       case PlayFromGameFile:
17851       case IcsExamining:
17852         return;
17853
17854       default:
17855         break;
17856     }
17857
17858     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17859         if(WhiteOnMove(forwardMostMove))
17860              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17861         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17862     }
17863
17864     tickStartTM = now;
17865     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17866       whiteTimeRemaining : blackTimeRemaining);
17867     StartClockTimer(intendedTickLength);
17868 }
17869
17870
17871 /* Stop both clocks */
17872 void
17873 StopClocks ()
17874 {
17875     long lastTickLength;
17876     TimeMark now;
17877
17878     if (!StopClockTimer()) return;
17879     if (!appData.clockMode) return;
17880
17881     GetTimeMark(&now);
17882
17883     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17884     if (WhiteOnMove(forwardMostMove)) {
17885         if(whiteNPS >= 0) lastTickLength = 0;
17886         whiteTimeRemaining -= lastTickLength;
17887         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17888     } else {
17889         if(blackNPS >= 0) lastTickLength = 0;
17890         blackTimeRemaining -= lastTickLength;
17891         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17892     }
17893     CheckFlags();
17894 }
17895
17896 /* Start clock of player on move.  Time may have been reset, so
17897    if clock is already running, stop and restart it. */
17898 void
17899 StartClocks ()
17900 {
17901     (void) StopClockTimer(); /* in case it was running already */
17902     DisplayBothClocks();
17903     if (CheckFlags()) return;
17904
17905     if (!appData.clockMode) return;
17906     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17907
17908     GetTimeMark(&tickStartTM);
17909     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17910       whiteTimeRemaining : blackTimeRemaining);
17911
17912    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17913     whiteNPS = blackNPS = -1;
17914     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17915        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17916         whiteNPS = first.nps;
17917     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17918        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17919         blackNPS = first.nps;
17920     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17921         whiteNPS = second.nps;
17922     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17923         blackNPS = second.nps;
17924     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17925
17926     StartClockTimer(intendedTickLength);
17927 }
17928
17929 char *
17930 TimeString (long ms)
17931 {
17932     long second, minute, hour, day;
17933     char *sign = "";
17934     static char buf[32];
17935
17936     if (ms > 0 && ms <= 9900) {
17937       /* convert milliseconds to tenths, rounding up */
17938       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17939
17940       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17941       return buf;
17942     }
17943
17944     /* convert milliseconds to seconds, rounding up */
17945     /* use floating point to avoid strangeness of integer division
17946        with negative dividends on many machines */
17947     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17948
17949     if (second < 0) {
17950         sign = "-";
17951         second = -second;
17952     }
17953
17954     day = second / (60 * 60 * 24);
17955     second = second % (60 * 60 * 24);
17956     hour = second / (60 * 60);
17957     second = second % (60 * 60);
17958     minute = second / 60;
17959     second = second % 60;
17960
17961     if (day > 0)
17962       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17963               sign, day, hour, minute, second);
17964     else if (hour > 0)
17965       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17966     else
17967       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17968
17969     return buf;
17970 }
17971
17972
17973 /*
17974  * This is necessary because some C libraries aren't ANSI C compliant yet.
17975  */
17976 char *
17977 StrStr (char *string, char *match)
17978 {
17979     int i, length;
17980
17981     length = strlen(match);
17982
17983     for (i = strlen(string) - length; i >= 0; i--, string++)
17984       if (!strncmp(match, string, length))
17985         return string;
17986
17987     return NULL;
17988 }
17989
17990 char *
17991 StrCaseStr (char *string, char *match)
17992 {
17993     int i, j, length;
17994
17995     length = strlen(match);
17996
17997     for (i = strlen(string) - length; i >= 0; i--, string++) {
17998         for (j = 0; j < length; j++) {
17999             if (ToLower(match[j]) != ToLower(string[j]))
18000               break;
18001         }
18002         if (j == length) return string;
18003     }
18004
18005     return NULL;
18006 }
18007
18008 #ifndef _amigados
18009 int
18010 StrCaseCmp (char *s1, char *s2)
18011 {
18012     char c1, c2;
18013
18014     for (;;) {
18015         c1 = ToLower(*s1++);
18016         c2 = ToLower(*s2++);
18017         if (c1 > c2) return 1;
18018         if (c1 < c2) return -1;
18019         if (c1 == NULLCHAR) return 0;
18020     }
18021 }
18022
18023
18024 int
18025 ToLower (int c)
18026 {
18027     return isupper(c) ? tolower(c) : c;
18028 }
18029
18030
18031 int
18032 ToUpper (int c)
18033 {
18034     return islower(c) ? toupper(c) : c;
18035 }
18036 #endif /* !_amigados    */
18037
18038 char *
18039 StrSave (char *s)
18040 {
18041   char *ret;
18042
18043   if ((ret = (char *) malloc(strlen(s) + 1)))
18044     {
18045       safeStrCpy(ret, s, strlen(s)+1);
18046     }
18047   return ret;
18048 }
18049
18050 char *
18051 StrSavePtr (char *s, char **savePtr)
18052 {
18053     if (*savePtr) {
18054         free(*savePtr);
18055     }
18056     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18057       safeStrCpy(*savePtr, s, strlen(s)+1);
18058     }
18059     return(*savePtr);
18060 }
18061
18062 char *
18063 PGNDate ()
18064 {
18065     time_t clock;
18066     struct tm *tm;
18067     char buf[MSG_SIZ];
18068
18069     clock = time((time_t *)NULL);
18070     tm = localtime(&clock);
18071     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18072             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18073     return StrSave(buf);
18074 }
18075
18076
18077 char *
18078 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18079 {
18080     int i, j, fromX, fromY, toX, toY;
18081     int whiteToPlay, haveRights = nrCastlingRights;
18082     char buf[MSG_SIZ];
18083     char *p, *q;
18084     int emptycount;
18085     ChessSquare piece;
18086
18087     whiteToPlay = (gameMode == EditPosition) ?
18088       !blackPlaysFirst : (move % 2 == 0);
18089     p = buf;
18090
18091     /* Piece placement data */
18092     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18093         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18094         emptycount = 0;
18095         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18096             if (boards[move][i][j] == EmptySquare) {
18097                 emptycount++;
18098             } else { ChessSquare piece = boards[move][i][j];
18099                 if (emptycount > 0) {
18100                     if(emptycount<10) /* [HGM] can be >= 10 */
18101                         *p++ = '0' + emptycount;
18102                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18103                     emptycount = 0;
18104                 }
18105                 if(PieceToChar(piece) == '+') {
18106                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18107                     *p++ = '+';
18108                     piece = (ChessSquare)(CHUDEMOTED(piece));
18109                 }
18110                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18111                 if(*p = PieceSuffix(piece)) p++;
18112                 if(p[-1] == '~') {
18113                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18114                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18115                     *p++ = '~';
18116                 }
18117             }
18118         }
18119         if (emptycount > 0) {
18120             if(emptycount<10) /* [HGM] can be >= 10 */
18121                 *p++ = '0' + emptycount;
18122             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18123             emptycount = 0;
18124         }
18125         *p++ = '/';
18126     }
18127     *(p - 1) = ' ';
18128
18129     /* [HGM] print Crazyhouse or Shogi holdings */
18130     if( gameInfo.holdingsWidth ) {
18131         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18132         q = p;
18133         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18134             piece = boards[move][i][BOARD_WIDTH-1];
18135             if( piece != EmptySquare )
18136               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18137                   *p++ = PieceToChar(piece);
18138         }
18139         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18140             piece = boards[move][BOARD_HEIGHT-i-1][0];
18141             if( piece != EmptySquare )
18142               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18143                   *p++ = PieceToChar(piece);
18144         }
18145
18146         if( q == p ) *p++ = '-';
18147         *p++ = ']';
18148         *p++ = ' ';
18149     }
18150
18151     /* Active color */
18152     *p++ = whiteToPlay ? 'w' : 'b';
18153     *p++ = ' ';
18154
18155   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18156     haveRights = 0; q = p;
18157     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18158       piece = boards[move][0][i];
18159       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18160         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18161       }
18162     }
18163     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18164       piece = boards[move][BOARD_HEIGHT-1][i];
18165       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18166         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18167       }
18168     }
18169     if(p == q) *p++ = '-';
18170     *p++ = ' ';
18171   }
18172
18173   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18174     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18175   } else {
18176   if(haveRights) {
18177      int handW=0, handB=0;
18178      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18179         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18180         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18181      }
18182      q = p;
18183      if(appData.fischerCastling) {
18184         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18185            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18186                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18187         } else {
18188        /* [HGM] write directly from rights */
18189            if(boards[move][CASTLING][2] != NoRights &&
18190               boards[move][CASTLING][0] != NoRights   )
18191                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18192            if(boards[move][CASTLING][2] != NoRights &&
18193               boards[move][CASTLING][1] != NoRights   )
18194                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18195         }
18196         if(handB) {
18197            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18198                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18199         } else {
18200            if(boards[move][CASTLING][5] != NoRights &&
18201               boards[move][CASTLING][3] != NoRights   )
18202                 *p++ = boards[move][CASTLING][3] + AAA;
18203            if(boards[move][CASTLING][5] != NoRights &&
18204               boards[move][CASTLING][4] != NoRights   )
18205                 *p++ = boards[move][CASTLING][4] + AAA;
18206         }
18207      } else {
18208
18209         /* [HGM] write true castling rights */
18210         if( nrCastlingRights == 6 ) {
18211             int q, k=0;
18212             if(boards[move][CASTLING][0] != NoRights &&
18213                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18214             q = (boards[move][CASTLING][1] != NoRights &&
18215                  boards[move][CASTLING][2] != NoRights  );
18216             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18217                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18218                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18219                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18220             }
18221             if(q) *p++ = 'Q';
18222             k = 0;
18223             if(boards[move][CASTLING][3] != NoRights &&
18224                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18225             q = (boards[move][CASTLING][4] != NoRights &&
18226                  boards[move][CASTLING][5] != NoRights  );
18227             if(handB) {
18228                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18229                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18230                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18231             }
18232             if(q) *p++ = 'q';
18233         }
18234      }
18235      if (q == p) *p++ = '-'; /* No castling rights */
18236      *p++ = ' ';
18237   }
18238
18239   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18240      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18241      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18242     /* En passant target square */
18243     if (move > backwardMostMove) {
18244         fromX = moveList[move - 1][0] - AAA;
18245         fromY = moveList[move - 1][1] - ONE;
18246         toX = moveList[move - 1][2] - AAA;
18247         toY = moveList[move - 1][3] - ONE;
18248         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18249             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18250             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18251             fromX == toX) {
18252             /* 2-square pawn move just happened */
18253             *p++ = toX + AAA;
18254             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18255         } else {
18256             *p++ = '-';
18257         }
18258     } else if(move == backwardMostMove) {
18259         // [HGM] perhaps we should always do it like this, and forget the above?
18260         if((signed char)boards[move][EP_STATUS] >= 0) {
18261             *p++ = boards[move][EP_STATUS] + AAA;
18262             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18263         } else {
18264             *p++ = '-';
18265         }
18266     } else {
18267         *p++ = '-';
18268     }
18269     *p++ = ' ';
18270   }
18271   }
18272
18273     if(moveCounts)
18274     {   int i = 0, j=move;
18275
18276         /* [HGM] find reversible plies */
18277         if (appData.debugMode) { int k;
18278             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18279             for(k=backwardMostMove; k<=forwardMostMove; k++)
18280                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18281
18282         }
18283
18284         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18285         if( j == backwardMostMove ) i += initialRulePlies;
18286         sprintf(p, "%d ", i);
18287         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18288
18289         /* Fullmove number */
18290         sprintf(p, "%d", (move / 2) + 1);
18291     } else *--p = NULLCHAR;
18292
18293     return StrSave(buf);
18294 }
18295
18296 Boolean
18297 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18298 {
18299     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18300     char *p, c;
18301     int emptycount, virgin[BOARD_FILES];
18302     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18303
18304     p = fen;
18305
18306     /* Piece placement data */
18307     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18308         j = 0;
18309         for (;;) {
18310             if (*p == '/' || *p == ' ' || *p == '[' ) {
18311                 if(j > w) w = j;
18312                 emptycount = gameInfo.boardWidth - j;
18313                 while (emptycount--)
18314                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18315                 if (*p == '/') p++;
18316                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18317                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18318                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18319                     }
18320                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18321                 }
18322                 break;
18323 #if(BOARD_FILES >= 10)*0
18324             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18325                 p++; emptycount=10;
18326                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18327                 while (emptycount--)
18328                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18329 #endif
18330             } else if (*p == '*') {
18331                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18332             } else if (isdigit(*p)) {
18333                 emptycount = *p++ - '0';
18334                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18335                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18336                 while (emptycount--)
18337                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18338             } else if (*p == '<') {
18339                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18340                 else if (i != 0 || !shuffle) return FALSE;
18341                 p++;
18342             } else if (shuffle && *p == '>') {
18343                 p++; // for now ignore closing shuffle range, and assume rank-end
18344             } else if (*p == '?') {
18345                 if (j >= gameInfo.boardWidth) return FALSE;
18346                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18347                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18348             } else if (*p == '+' || isalpha(*p)) {
18349                 char *q, *s = SUFFIXES;
18350                 if (j >= gameInfo.boardWidth) return FALSE;
18351                 if(*p=='+') {
18352                     char c = *++p;
18353                     if(q = strchr(s, p[1])) p++;
18354                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18355                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18356                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18357                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18358                 } else {
18359                     char c = *p++;
18360                     if(q = strchr(s, *p)) p++;
18361                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18362                 }
18363
18364                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18365                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18366                     piece = (ChessSquare) (PROMOTED(piece));
18367                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18368                     p++;
18369                 }
18370                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18371                 if(piece == king) wKingRank = i;
18372                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18373             } else {
18374                 return FALSE;
18375             }
18376         }
18377     }
18378     while (*p == '/' || *p == ' ') p++;
18379
18380     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18381
18382     /* [HGM] by default clear Crazyhouse holdings, if present */
18383     if(gameInfo.holdingsWidth) {
18384        for(i=0; i<BOARD_HEIGHT; i++) {
18385            board[i][0]             = EmptySquare; /* black holdings */
18386            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18387            board[i][1]             = (ChessSquare) 0; /* black counts */
18388            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18389        }
18390     }
18391
18392     /* [HGM] look for Crazyhouse holdings here */
18393     while(*p==' ') p++;
18394     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18395         int swap=0, wcnt=0, bcnt=0;
18396         if(*p == '[') p++;
18397         if(*p == '<') swap++, p++;
18398         if(*p == '-' ) p++; /* empty holdings */ else {
18399             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18400             /* if we would allow FEN reading to set board size, we would   */
18401             /* have to add holdings and shift the board read so far here   */
18402             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18403                 p++;
18404                 if((int) piece >= (int) BlackPawn ) {
18405                     i = (int)piece - (int)BlackPawn;
18406                     i = PieceToNumber((ChessSquare)i);
18407                     if( i >= gameInfo.holdingsSize ) return FALSE;
18408                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18409                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18410                     bcnt++;
18411                 } else {
18412                     i = (int)piece - (int)WhitePawn;
18413                     i = PieceToNumber((ChessSquare)i);
18414                     if( i >= gameInfo.holdingsSize ) return FALSE;
18415                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18416                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18417                     wcnt++;
18418                 }
18419             }
18420             if(subst) { // substitute back-rank question marks by holdings pieces
18421                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18422                     int k, m, n = bcnt + 1;
18423                     if(board[0][j] == ClearBoard) {
18424                         if(!wcnt) return FALSE;
18425                         n = rand() % wcnt;
18426                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18427                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18428                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18429                             break;
18430                         }
18431                     }
18432                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18433                         if(!bcnt) return FALSE;
18434                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18435                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18436                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18437                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18438                             break;
18439                         }
18440                     }
18441                 }
18442                 subst = 0;
18443             }
18444         }
18445         if(*p == ']') p++;
18446     }
18447
18448     if(subst) return FALSE; // substitution requested, but no holdings
18449
18450     while(*p == ' ') p++;
18451
18452     /* Active color */
18453     c = *p++;
18454     if(appData.colorNickNames) {
18455       if( c == appData.colorNickNames[0] ) c = 'w'; else
18456       if( c == appData.colorNickNames[1] ) c = 'b';
18457     }
18458     switch (c) {
18459       case 'w':
18460         *blackPlaysFirst = FALSE;
18461         break;
18462       case 'b':
18463         *blackPlaysFirst = TRUE;
18464         break;
18465       default:
18466         return FALSE;
18467     }
18468
18469     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18470     /* return the extra info in global variiables             */
18471
18472     while(*p==' ') p++;
18473
18474     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18475         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18476         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18477     }
18478
18479     /* set defaults in case FEN is incomplete */
18480     board[EP_STATUS] = EP_UNKNOWN;
18481     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18482     for(i=0; i<nrCastlingRights; i++ ) {
18483         board[CASTLING][i] =
18484             appData.fischerCastling ? NoRights : initialRights[i];
18485     }   /* assume possible unless obviously impossible */
18486     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18487     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18488     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18489                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18490     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18491     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18492     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18493                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18494     FENrulePlies = 0;
18495
18496     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18497       char *q = p;
18498       int w=0, b=0;
18499       while(isalpha(*p)) {
18500         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18501         if(islower(*p)) b |= 1 << (*p++ - 'a');
18502       }
18503       if(*p == '-') p++;
18504       if(p != q) {
18505         board[TOUCHED_W] = ~w;
18506         board[TOUCHED_B] = ~b;
18507         while(*p == ' ') p++;
18508       }
18509     } else
18510
18511     if(nrCastlingRights) {
18512       int fischer = 0;
18513       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18514       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18515           /* castling indicator present, so default becomes no castlings */
18516           for(i=0; i<nrCastlingRights; i++ ) {
18517                  board[CASTLING][i] = NoRights;
18518           }
18519       }
18520       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18521              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18522              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18523              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18524         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18525
18526         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18527             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18528             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18529         }
18530         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18531             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18532         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18533                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18534         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18535                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18536         switch(c) {
18537           case'K':
18538               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18539               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18540               board[CASTLING][2] = whiteKingFile;
18541               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18542               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18543               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18544               break;
18545           case'Q':
18546               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18547               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18548               board[CASTLING][2] = whiteKingFile;
18549               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18550               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18551               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18552               break;
18553           case'k':
18554               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18555               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18556               board[CASTLING][5] = blackKingFile;
18557               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18558               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18559               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18560               break;
18561           case'q':
18562               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18563               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18564               board[CASTLING][5] = blackKingFile;
18565               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18566               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18567               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18568           case '-':
18569               break;
18570           default: /* FRC castlings */
18571               if(c >= 'a') { /* black rights */
18572                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18573                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18574                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18575                   if(i == BOARD_RGHT) break;
18576                   board[CASTLING][5] = i;
18577                   c -= AAA;
18578                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18579                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18580                   if(c > i)
18581                       board[CASTLING][3] = c;
18582                   else
18583                       board[CASTLING][4] = c;
18584               } else { /* white rights */
18585                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18586                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18587                     if(board[0][i] == WhiteKing) break;
18588                   if(i == BOARD_RGHT) break;
18589                   board[CASTLING][2] = i;
18590                   c -= AAA - 'a' + 'A';
18591                   if(board[0][c] >= WhiteKing) break;
18592                   if(c > i)
18593                       board[CASTLING][0] = c;
18594                   else
18595                       board[CASTLING][1] = c;
18596               }
18597         }
18598       }
18599       for(i=0; i<nrCastlingRights; i++)
18600         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18601       if(gameInfo.variant == VariantSChess)
18602         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18603       if(fischer && shuffle) appData.fischerCastling = TRUE;
18604     if (appData.debugMode) {
18605         fprintf(debugFP, "FEN castling rights:");
18606         for(i=0; i<nrCastlingRights; i++)
18607         fprintf(debugFP, " %d", board[CASTLING][i]);
18608         fprintf(debugFP, "\n");
18609     }
18610
18611       while(*p==' ') p++;
18612     }
18613
18614     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18615
18616     /* read e.p. field in games that know e.p. capture */
18617     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18618        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18619        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18620       if(*p=='-') {
18621         p++; board[EP_STATUS] = EP_NONE;
18622       } else {
18623          char c = *p++ - AAA;
18624
18625          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18626          if(*p >= '0' && *p <='9') p++;
18627          board[EP_STATUS] = c;
18628       }
18629     }
18630
18631
18632     if(sscanf(p, "%d", &i) == 1) {
18633         FENrulePlies = i; /* 50-move ply counter */
18634         /* (The move number is still ignored)    */
18635     }
18636
18637     return TRUE;
18638 }
18639
18640 void
18641 EditPositionPasteFEN (char *fen)
18642 {
18643   if (fen != NULL) {
18644     Board initial_position;
18645
18646     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18647       DisplayError(_("Bad FEN position in clipboard"), 0);
18648       return ;
18649     } else {
18650       int savedBlackPlaysFirst = blackPlaysFirst;
18651       EditPositionEvent();
18652       blackPlaysFirst = savedBlackPlaysFirst;
18653       CopyBoard(boards[0], initial_position);
18654       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18655       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18656       DisplayBothClocks();
18657       DrawPosition(FALSE, boards[currentMove]);
18658     }
18659   }
18660 }
18661
18662 static char cseq[12] = "\\   ";
18663
18664 Boolean
18665 set_cont_sequence (char *new_seq)
18666 {
18667     int len;
18668     Boolean ret;
18669
18670     // handle bad attempts to set the sequence
18671         if (!new_seq)
18672                 return 0; // acceptable error - no debug
18673
18674     len = strlen(new_seq);
18675     ret = (len > 0) && (len < sizeof(cseq));
18676     if (ret)
18677       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18678     else if (appData.debugMode)
18679       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18680     return ret;
18681 }
18682
18683 /*
18684     reformat a source message so words don't cross the width boundary.  internal
18685     newlines are not removed.  returns the wrapped size (no null character unless
18686     included in source message).  If dest is NULL, only calculate the size required
18687     for the dest buffer.  lp argument indicats line position upon entry, and it's
18688     passed back upon exit.
18689 */
18690 int
18691 wrap (char *dest, char *src, int count, int width, int *lp)
18692 {
18693     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18694
18695     cseq_len = strlen(cseq);
18696     old_line = line = *lp;
18697     ansi = len = clen = 0;
18698
18699     for (i=0; i < count; i++)
18700     {
18701         if (src[i] == '\033')
18702             ansi = 1;
18703
18704         // if we hit the width, back up
18705         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18706         {
18707             // store i & len in case the word is too long
18708             old_i = i, old_len = len;
18709
18710             // find the end of the last word
18711             while (i && src[i] != ' ' && src[i] != '\n')
18712             {
18713                 i--;
18714                 len--;
18715             }
18716
18717             // word too long?  restore i & len before splitting it
18718             if ((old_i-i+clen) >= width)
18719             {
18720                 i = old_i;
18721                 len = old_len;
18722             }
18723
18724             // extra space?
18725             if (i && src[i-1] == ' ')
18726                 len--;
18727
18728             if (src[i] != ' ' && src[i] != '\n')
18729             {
18730                 i--;
18731                 if (len)
18732                     len--;
18733             }
18734
18735             // now append the newline and continuation sequence
18736             if (dest)
18737                 dest[len] = '\n';
18738             len++;
18739             if (dest)
18740                 strncpy(dest+len, cseq, cseq_len);
18741             len += cseq_len;
18742             line = cseq_len;
18743             clen = cseq_len;
18744             continue;
18745         }
18746
18747         if (dest)
18748             dest[len] = src[i];
18749         len++;
18750         if (!ansi)
18751             line++;
18752         if (src[i] == '\n')
18753             line = 0;
18754         if (src[i] == 'm')
18755             ansi = 0;
18756     }
18757     if (dest && appData.debugMode)
18758     {
18759         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18760             count, width, line, len, *lp);
18761         show_bytes(debugFP, src, count);
18762         fprintf(debugFP, "\ndest: ");
18763         show_bytes(debugFP, dest, len);
18764         fprintf(debugFP, "\n");
18765     }
18766     *lp = dest ? line : old_line;
18767
18768     return len;
18769 }
18770
18771 // [HGM] vari: routines for shelving variations
18772 Boolean modeRestore = FALSE;
18773
18774 void
18775 PushInner (int firstMove, int lastMove)
18776 {
18777         int i, j, nrMoves = lastMove - firstMove;
18778
18779         // push current tail of game on stack
18780         savedResult[storedGames] = gameInfo.result;
18781         savedDetails[storedGames] = gameInfo.resultDetails;
18782         gameInfo.resultDetails = NULL;
18783         savedFirst[storedGames] = firstMove;
18784         savedLast [storedGames] = lastMove;
18785         savedFramePtr[storedGames] = framePtr;
18786         framePtr -= nrMoves; // reserve space for the boards
18787         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18788             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18789             for(j=0; j<MOVE_LEN; j++)
18790                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18791             for(j=0; j<2*MOVE_LEN; j++)
18792                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18793             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18794             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18795             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18796             pvInfoList[firstMove+i-1].depth = 0;
18797             commentList[framePtr+i] = commentList[firstMove+i];
18798             commentList[firstMove+i] = NULL;
18799         }
18800
18801         storedGames++;
18802         forwardMostMove = firstMove; // truncate game so we can start variation
18803 }
18804
18805 void
18806 PushTail (int firstMove, int lastMove)
18807 {
18808         if(appData.icsActive) { // only in local mode
18809                 forwardMostMove = currentMove; // mimic old ICS behavior
18810                 return;
18811         }
18812         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18813
18814         PushInner(firstMove, lastMove);
18815         if(storedGames == 1) GreyRevert(FALSE);
18816         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18817 }
18818
18819 void
18820 PopInner (Boolean annotate)
18821 {
18822         int i, j, nrMoves;
18823         char buf[8000], moveBuf[20];
18824
18825         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18826         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18827         nrMoves = savedLast[storedGames] - currentMove;
18828         if(annotate) {
18829                 int cnt = 10;
18830                 if(!WhiteOnMove(currentMove))
18831                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18832                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18833                 for(i=currentMove; i<forwardMostMove; i++) {
18834                         if(WhiteOnMove(i))
18835                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18836                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18837                         strcat(buf, moveBuf);
18838                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18839                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18840                 }
18841                 strcat(buf, ")");
18842         }
18843         for(i=1; i<=nrMoves; i++) { // copy last variation back
18844             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18845             for(j=0; j<MOVE_LEN; j++)
18846                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18847             for(j=0; j<2*MOVE_LEN; j++)
18848                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18849             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18850             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18851             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18852             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18853             commentList[currentMove+i] = commentList[framePtr+i];
18854             commentList[framePtr+i] = NULL;
18855         }
18856         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18857         framePtr = savedFramePtr[storedGames];
18858         gameInfo.result = savedResult[storedGames];
18859         if(gameInfo.resultDetails != NULL) {
18860             free(gameInfo.resultDetails);
18861       }
18862         gameInfo.resultDetails = savedDetails[storedGames];
18863         forwardMostMove = currentMove + nrMoves;
18864 }
18865
18866 Boolean
18867 PopTail (Boolean annotate)
18868 {
18869         if(appData.icsActive) return FALSE; // only in local mode
18870         if(!storedGames) return FALSE; // sanity
18871         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18872
18873         PopInner(annotate);
18874         if(currentMove < forwardMostMove) ForwardEvent(); else
18875         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18876
18877         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18878         return TRUE;
18879 }
18880
18881 void
18882 CleanupTail ()
18883 {       // remove all shelved variations
18884         int i;
18885         for(i=0; i<storedGames; i++) {
18886             if(savedDetails[i])
18887                 free(savedDetails[i]);
18888             savedDetails[i] = NULL;
18889         }
18890         for(i=framePtr; i<MAX_MOVES; i++) {
18891                 if(commentList[i]) free(commentList[i]);
18892                 commentList[i] = NULL;
18893         }
18894         framePtr = MAX_MOVES-1;
18895         storedGames = 0;
18896 }
18897
18898 void
18899 LoadVariation (int index, char *text)
18900 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18901         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18902         int level = 0, move;
18903
18904         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18905         // first find outermost bracketing variation
18906         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18907             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18908                 if(*p == '{') wait = '}'; else
18909                 if(*p == '[') wait = ']'; else
18910                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18911                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18912             }
18913             if(*p == wait) wait = NULLCHAR; // closing ]} found
18914             p++;
18915         }
18916         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18917         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18918         end[1] = NULLCHAR; // clip off comment beyond variation
18919         ToNrEvent(currentMove-1);
18920         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18921         // kludge: use ParsePV() to append variation to game
18922         move = currentMove;
18923         ParsePV(start, TRUE, TRUE);
18924         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18925         ClearPremoveHighlights();
18926         CommentPopDown();
18927         ToNrEvent(currentMove+1);
18928 }
18929
18930 void
18931 LoadTheme ()
18932 {
18933     char *p, *q, buf[MSG_SIZ];
18934     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18935         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18936         ParseArgsFromString(buf);
18937         ActivateTheme(TRUE); // also redo colors
18938         return;
18939     }
18940     p = nickName;
18941     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18942     {
18943         int len;
18944         q = appData.themeNames;
18945         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18946       if(appData.useBitmaps) {
18947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18948                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18949                 appData.liteBackTextureMode,
18950                 appData.darkBackTextureMode );
18951       } else {
18952         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18953                 Col2Text(2),   // lightSquareColor
18954                 Col2Text(3) ); // darkSquareColor
18955       }
18956       if(appData.useBorder) {
18957         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18958                 appData.border);
18959       } else {
18960         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18961       }
18962       if(appData.useFont) {
18963         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18964                 appData.renderPiecesWithFont,
18965                 appData.fontToPieceTable,
18966                 Col2Text(9),    // appData.fontBackColorWhite
18967                 Col2Text(10) ); // appData.fontForeColorBlack
18968       } else {
18969         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18970                 appData.pieceDirectory);
18971         if(!appData.pieceDirectory[0])
18972           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18973                 Col2Text(0),   // whitePieceColor
18974                 Col2Text(1) ); // blackPieceColor
18975       }
18976       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18977                 Col2Text(4),   // highlightSquareColor
18978                 Col2Text(5) ); // premoveHighlightColor
18979         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18980         if(insert != q) insert[-1] = NULLCHAR;
18981         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18982         if(q)   free(q);
18983     }
18984     ActivateTheme(FALSE);
18985 }