Add -men option for changing piece moves
[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         return;
1000     }
1001     p = engineName;
1002     while(q = strchr(p, SLASH)) p = q+1;
1003     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004     if(engineDir[0] != NULLCHAR) {
1005         ASSIGN(appData.directory[i], engineDir); p = engineName;
1006     } else if(p != engineName) { // derive directory from engine path, when not given
1007         p[-1] = 0;
1008         ASSIGN(appData.directory[i], engineName);
1009         p[-1] = SLASH;
1010         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011     } else { ASSIGN(appData.directory[i], "."); }
1012     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013     if(params[0]) {
1014         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015         snprintf(command, MSG_SIZ, "%s %s", p, params);
1016         p = command;
1017     }
1018     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019     ASSIGN(appData.chessProgram[i], p);
1020     appData.isUCI[i] = isUCI;
1021     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022     appData.hasOwnBookUCI[i] = hasBook;
1023     if(!nickName[0]) useNick = FALSE;
1024     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1025     if(addToList) {
1026         int len;
1027         char quote;
1028         q = firstChessProgramNames;
1029         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032                         quote, p, quote, appData.directory[i],
1033                         useNick ? " -fn \"" : "",
1034                         useNick ? nickName : "",
1035                         useNick ? "\"" : "",
1036                         v1 ? " -firstProtocolVersion 1" : "",
1037                         hasBook ? "" : " -fNoOwnBookUCI",
1038                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039                         storeVariant ? " -variant " : "",
1040                         storeVariant ? VariantName(gameInfo.variant) : "");
1041         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043         if(insert != q) insert[-1] = NULLCHAR;
1044         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045         if(q)   free(q);
1046         FloatToFront(&appData.recentEngineList, buf);
1047     }
1048     ReplaceEngine(cps, i);
1049 }
1050
1051 void
1052 InitTimeControls ()
1053 {
1054     int matched, min, sec;
1055     /*
1056      * Parse timeControl resource
1057      */
1058     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059                           appData.movesPerSession)) {
1060         char buf[MSG_SIZ];
1061         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062         DisplayFatalError(buf, 0, 2);
1063     }
1064
1065     /*
1066      * Parse searchTime resource
1067      */
1068     if (*appData.searchTime != NULLCHAR) {
1069         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070         if (matched == 1) {
1071             searchTime = min * 60;
1072         } else if (matched == 2) {
1073             searchTime = min * 60 + sec;
1074         } else {
1075             char buf[MSG_SIZ];
1076             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077             DisplayFatalError(buf, 0, 2);
1078         }
1079     }
1080 }
1081
1082 void
1083 InitBackEnd1 ()
1084 {
1085
1086     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088
1089     GetTimeMark(&programStartTime);
1090     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091     appData.seedBase = random() + (random()<<15);
1092     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093
1094     ClearProgramStats();
1095     programStats.ok_to_send = 1;
1096     programStats.seen_stat = 0;
1097
1098     /*
1099      * Initialize game list
1100      */
1101     ListNew(&gameList);
1102
1103
1104     /*
1105      * Internet chess server status
1106      */
1107     if (appData.icsActive) {
1108         appData.matchMode = FALSE;
1109         appData.matchGames = 0;
1110 #if ZIPPY
1111         appData.noChessProgram = !appData.zippyPlay;
1112 #else
1113         appData.zippyPlay = FALSE;
1114         appData.zippyTalk = FALSE;
1115         appData.noChessProgram = TRUE;
1116 #endif
1117         if (*appData.icsHelper != NULLCHAR) {
1118             appData.useTelnet = TRUE;
1119             appData.telnetProgram = appData.icsHelper;
1120         }
1121     } else {
1122         appData.zippyTalk = appData.zippyPlay = FALSE;
1123     }
1124
1125     /* [AS] Initialize pv info list [HGM] and game state */
1126     {
1127         int i, j;
1128
1129         for( i=0; i<=framePtr; i++ ) {
1130             pvInfoList[i].depth = -1;
1131             boards[i][EP_STATUS] = EP_NONE;
1132             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1133         }
1134     }
1135
1136     InitTimeControls();
1137
1138     /* [AS] Adjudication threshold */
1139     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140
1141     InitEngine(&first, 0);
1142     InitEngine(&second, 1);
1143     CommonEngineInit();
1144
1145     pairing.which = "pairing"; // pairing engine
1146     pairing.pr = NoProc;
1147     pairing.isr = NULL;
1148     pairing.program = appData.pairingEngine;
1149     pairing.host = "localhost";
1150     pairing.dir = ".";
1151
1152     if (appData.icsActive) {
1153         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1154     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155         appData.clockMode = FALSE;
1156         first.sendTime = second.sendTime = 0;
1157     }
1158
1159 #if ZIPPY
1160     /* Override some settings from environment variables, for backward
1161        compatibility.  Unfortunately it's not feasible to have the env
1162        vars just set defaults, at least in xboard.  Ugh.
1163     */
1164     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1165       ZippyInit();
1166     }
1167 #endif
1168
1169     if (!appData.icsActive) {
1170       char buf[MSG_SIZ];
1171       int len;
1172
1173       /* Check for variants that are supported only in ICS mode,
1174          or not at all.  Some that are accepted here nevertheless
1175          have bugs; see comments below.
1176       */
1177       VariantClass variant = StringToVariant(appData.variant);
1178       switch (variant) {
1179       case VariantBughouse:     /* need four players and two boards */
1180       case VariantKriegspiel:   /* need to hide pieces and move details */
1181         /* case VariantFischeRandom: (Fabien: moved below) */
1182         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183         if( (len >= MSG_SIZ) && appData.debugMode )
1184           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185
1186         DisplayFatalError(buf, 0, 2);
1187         return;
1188
1189       case VariantUnknown:
1190       case VariantLoadable:
1191       case Variant29:
1192       case Variant30:
1193       case Variant31:
1194       case Variant32:
1195       case Variant33:
1196       case Variant34:
1197       case Variant35:
1198       case Variant36:
1199       default:
1200         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201         if( (len >= MSG_SIZ) && appData.debugMode )
1202           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203
1204         DisplayFatalError(buf, 0, 2);
1205         return;
1206
1207       case VariantNormal:     /* definitely works! */
1208         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1210           return;
1211         }
1212       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1213       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1214       case VariantGothic:     /* [HGM] should work */
1215       case VariantCapablanca: /* [HGM] should work */
1216       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1217       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1218       case VariantChu:        /* [HGM] experimental */
1219       case VariantKnightmate: /* [HGM] should work */
1220       case VariantCylinder:   /* [HGM] untested */
1221       case VariantFalcon:     /* [HGM] untested */
1222       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223                                  offboard interposition not understood */
1224       case VariantWildCastle: /* pieces not automatically shuffled */
1225       case VariantNoCastle:   /* pieces not automatically shuffled */
1226       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227       case VariantLosers:     /* should work except for win condition,
1228                                  and doesn't know captures are mandatory */
1229       case VariantSuicide:    /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantGiveaway:   /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantTwoKings:   /* should work */
1234       case VariantAtomic:     /* should work except for win condition */
1235       case Variant3Check:     /* should work except for win condition */
1236       case VariantShatranj:   /* should work except for all win conditions */
1237       case VariantMakruk:     /* should work except for draw countdown */
1238       case VariantASEAN :     /* should work except for draw countdown */
1239       case VariantBerolina:   /* might work if TestLegality is off */
1240       case VariantCapaRandom: /* should work */
1241       case VariantJanus:      /* should work */
1242       case VariantSuper:      /* experimental */
1243       case VariantGreat:      /* experimental, requires legality testing to be off */
1244       case VariantSChess:     /* S-Chess, should work */
1245       case VariantGrand:      /* should work */
1246       case VariantSpartan:    /* should work */
1247       case VariantLion:       /* should work */
1248       case VariantChuChess:   /* should work */
1249         break;
1250       }
1251     }
1252
1253 }
1254
1255 int
1256 NextIntegerFromString (char ** str, long * value)
1257 {
1258     int result = -1;
1259     char * s = *str;
1260
1261     while( *s == ' ' || *s == '\t' ) {
1262         s++;
1263     }
1264
1265     *value = 0;
1266
1267     if( *s >= '0' && *s <= '9' ) {
1268         while( *s >= '0' && *s <= '9' ) {
1269             *value = *value * 10 + (*s - '0');
1270             s++;
1271         }
1272
1273         result = 0;
1274     }
1275
1276     *str = s;
1277
1278     return result;
1279 }
1280
1281 int
1282 NextTimeControlFromString (char ** str, long * value)
1283 {
1284     long temp;
1285     int result = NextIntegerFromString( str, &temp );
1286
1287     if( result == 0 ) {
1288         *value = temp * 60; /* Minutes */
1289         if( **str == ':' ) {
1290             (*str)++;
1291             result = NextIntegerFromString( str, &temp );
1292             *value += temp; /* Seconds */
1293         }
1294     }
1295
1296     return result;
1297 }
1298
1299 int
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302     int result = -1, type = 0; long temp, temp2;
1303
1304     if(**str != ':') return -1; // old params remain in force!
1305     (*str)++;
1306     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307     if( NextIntegerFromString( str, &temp ) ) return -1;
1308     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1309
1310     if(**str != '/') {
1311         /* time only: incremental or sudden-death time control */
1312         if(**str == '+') { /* increment follows; read it */
1313             (*str)++;
1314             if(**str == '!') type = *(*str)++; // Bronstein TC
1315             if(result = NextIntegerFromString( str, &temp2)) return -1;
1316             *inc = temp2 * 1000;
1317             if(**str == '.') { // read fraction of increment
1318                 char *start = ++(*str);
1319                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320                 temp2 *= 1000;
1321                 while(start++ < *str) temp2 /= 10;
1322                 *inc += temp2;
1323             }
1324         } else *inc = 0;
1325         *moves = 0; *tc = temp * 1000; *incType = type;
1326         return 0;
1327     }
1328
1329     (*str)++; /* classical time control */
1330     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1331
1332     if(result == 0) {
1333         *moves = temp;
1334         *tc    = temp2 * 1000;
1335         *inc   = 0;
1336         *incType = type;
1337     }
1338     return result;
1339 }
1340
1341 int
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 {   /* [HGM] get time to add from the multi-session time-control string */
1344     int incType, moves=1; /* kludge to force reading of first session */
1345     long time, increment;
1346     char *s = tcString;
1347
1348     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349     do {
1350         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352         if(movenr == -1) return time;    /* last move before new session     */
1353         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355         if(!moves) return increment;     /* current session is incremental   */
1356         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357     } while(movenr >= -1);               /* try again for next session       */
1358
1359     return 0; // no new time quota on this move
1360 }
1361
1362 int
1363 ParseTimeControl (char *tc, float ti, int mps)
1364 {
1365   long tc1;
1366   long tc2;
1367   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1368   int min, sec=0;
1369
1370   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1373   if(ti > 0) {
1374
1375     if(mps)
1376       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377     else
1378       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1379   } else {
1380     if(mps)
1381       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382     else
1383       snprintf(buf, MSG_SIZ, ":%s", mytc);
1384   }
1385   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386
1387   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1388     return FALSE;
1389   }
1390
1391   if( *tc == '/' ) {
1392     /* Parse second time control */
1393     tc++;
1394
1395     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1396       return FALSE;
1397     }
1398
1399     if( tc2 == 0 ) {
1400       return FALSE;
1401     }
1402
1403     timeControl_2 = tc2 * 1000;
1404   }
1405   else {
1406     timeControl_2 = 0;
1407   }
1408
1409   if( tc1 == 0 ) {
1410     return FALSE;
1411   }
1412
1413   timeControl = tc1 * 1000;
1414
1415   if (ti >= 0) {
1416     timeIncrement = ti * 1000;  /* convert to ms */
1417     movesPerSession = 0;
1418   } else {
1419     timeIncrement = 0;
1420     movesPerSession = mps;
1421   }
1422   return TRUE;
1423 }
1424
1425 void
1426 InitBackEnd2 ()
1427 {
1428     if (appData.debugMode) {
1429 #    ifdef __GIT_VERSION
1430       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 #    else
1432       fprintf(debugFP, "Version: %s\n", programVersion);
1433 #    endif
1434     }
1435     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436
1437     set_cont_sequence(appData.wrapContSeq);
1438     if (appData.matchGames > 0) {
1439         appData.matchMode = TRUE;
1440     } else if (appData.matchMode) {
1441         appData.matchGames = 1;
1442     }
1443     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444         appData.matchGames = appData.sameColorGames;
1445     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1448     }
1449     Reset(TRUE, FALSE);
1450     if (appData.noChessProgram || first.protocolVersion == 1) {
1451       InitBackEnd3();
1452     } else {
1453       /* kludge: allow timeout for initial "feature" commands */
1454       FreezeUI();
1455       DisplayMessage("", _("Starting chess program"));
1456       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1457     }
1458 }
1459
1460 int
1461 CalculateIndex (int index, int gameNr)
1462 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463     int res;
1464     if(index > 0) return index; // fixed nmber
1465     if(index == 0) return 1;
1466     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1468     return res;
1469 }
1470
1471 int
1472 LoadGameOrPosition (int gameNr)
1473 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474     if (*appData.loadGameFile != NULLCHAR) {
1475         if (!LoadGameFromFile(appData.loadGameFile,
1476                 CalculateIndex(appData.loadGameIndex, gameNr),
1477                               appData.loadGameFile, FALSE)) {
1478             DisplayFatalError(_("Bad game file"), 0, 1);
1479             return 0;
1480         }
1481     } else if (*appData.loadPositionFile != NULLCHAR) {
1482         if (!LoadPositionFromFile(appData.loadPositionFile,
1483                 CalculateIndex(appData.loadPositionIndex, gameNr),
1484                                   appData.loadPositionFile)) {
1485             DisplayFatalError(_("Bad position file"), 0, 1);
1486             return 0;
1487         }
1488     }
1489     return 1;
1490 }
1491
1492 void
1493 ReserveGame (int gameNr, char resChar)
1494 {
1495     FILE *tf = fopen(appData.tourneyFile, "r+");
1496     char *p, *q, c, buf[MSG_SIZ];
1497     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498     safeStrCpy(buf, lastMsg, MSG_SIZ);
1499     DisplayMessage(_("Pick new game"), "");
1500     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501     ParseArgsFromFile(tf);
1502     p = q = appData.results;
1503     if(appData.debugMode) {
1504       char *r = appData.participants;
1505       fprintf(debugFP, "results = '%s'\n", p);
1506       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507       fprintf(debugFP, "\n");
1508     }
1509     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510     nextGame = q - p;
1511     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512     safeStrCpy(q, p, strlen(p) + 2);
1513     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1517         q[nextGame] = '*';
1518     }
1519     fseek(tf, -(strlen(p)+4), SEEK_END);
1520     c = fgetc(tf);
1521     if(c != '"') // depending on DOS or Unix line endings we can be one off
1522          fseek(tf, -(strlen(p)+2), SEEK_END);
1523     else fseek(tf, -(strlen(p)+3), SEEK_END);
1524     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525     DisplayMessage(buf, "");
1526     free(p); appData.results = q;
1527     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529       int round = appData.defaultMatchGames * appData.tourneyType;
1530       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1531          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532         UnloadEngine(&first);  // next game belongs to other pairing;
1533         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534     }
1535     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1536 }
1537
1538 void
1539 MatchEvent (int mode)
1540 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541         int dummy;
1542         if(matchMode) { // already in match mode: switch it off
1543             abortMatch = TRUE;
1544             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1545             return;
1546         }
1547 //      if(gameMode != BeginningOfGame) {
1548 //          DisplayError(_("You can only start a match from the initial position."), 0);
1549 //          return;
1550 //      }
1551         abortMatch = FALSE;
1552         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553         /* Set up machine vs. machine match */
1554         nextGame = 0;
1555         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556         if(appData.tourneyFile[0]) {
1557             ReserveGame(-1, 0);
1558             if(nextGame > appData.matchGames) {
1559                 char buf[MSG_SIZ];
1560                 if(strchr(appData.results, '*') == NULL) {
1561                     FILE *f;
1562                     appData.tourneyCycles++;
1563                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564                         fclose(f);
1565                         NextTourneyGame(-1, &dummy);
1566                         ReserveGame(-1, 0);
1567                         if(nextGame <= appData.matchGames) {
1568                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569                             matchMode = mode;
1570                             ScheduleDelayedEvent(NextMatchGame, 10000);
1571                             return;
1572                         }
1573                     }
1574                 }
1575                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576                 DisplayError(buf, 0);
1577                 appData.tourneyFile[0] = 0;
1578                 return;
1579             }
1580         } else
1581         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1582             DisplayFatalError(_("Can't have a match with no chess programs"),
1583                               0, 2);
1584             return;
1585         }
1586         matchMode = mode;
1587         matchGame = roundNr = 1;
1588         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1589         NextMatchGame();
1590 }
1591
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1593
1594 void
1595 InitBackEnd3 P((void))
1596 {
1597     GameMode initialMode;
1598     char buf[MSG_SIZ];
1599     int err, len;
1600
1601     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1602        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1603         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1604        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1605        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606         char c, *q = first.variants, *p = strchr(q, ',');
1607         if(p) *p = NULLCHAR;
1608         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609             int w, h, s;
1610             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613             Reset(TRUE, FALSE);         // and re-initialize
1614         }
1615         if(p) *p = ',';
1616     }
1617
1618     InitChessProgram(&first, startedFromSetupPosition);
1619
1620     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1621         free(programVersion);
1622         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1625     }
1626
1627     if (appData.icsActive) {
1628 #ifdef WIN32
1629         /* [DM] Make a console window if needed [HGM] merged ifs */
1630         ConsoleCreate();
1631 #endif
1632         err = establish();
1633         if (err != 0)
1634           {
1635             if (*appData.icsCommPort != NULLCHAR)
1636               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637                              appData.icsCommPort);
1638             else
1639               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640                         appData.icsHost, appData.icsPort);
1641
1642             if( (len >= MSG_SIZ) && appData.debugMode )
1643               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644
1645             DisplayFatalError(buf, err, 1);
1646             return;
1647         }
1648         SetICSMode();
1649         telnetISR =
1650           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651         fromUserISR =
1652           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655     } else if (appData.noChessProgram) {
1656         SetNCPMode();
1657     } else {
1658         SetGNUMode();
1659     }
1660
1661     if (*appData.cmailGameName != NULLCHAR) {
1662         SetCmailMode();
1663         OpenLoopback(&cmailPR);
1664         cmailISR =
1665           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1666     }
1667
1668     ThawUI();
1669     DisplayMessage("", "");
1670     if (StrCaseCmp(appData.initialMode, "") == 0) {
1671       initialMode = BeginningOfGame;
1672       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1676         ModeHighlight();
1677       }
1678     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679       initialMode = TwoMachinesPlay;
1680     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681       initialMode = AnalyzeFile;
1682     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683       initialMode = AnalyzeMode;
1684     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685       initialMode = MachinePlaysWhite;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687       initialMode = MachinePlaysBlack;
1688     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689       initialMode = EditGame;
1690     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691       initialMode = EditPosition;
1692     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693       initialMode = Training;
1694     } else {
1695       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696       if( (len >= MSG_SIZ) && appData.debugMode )
1697         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698
1699       DisplayFatalError(buf, 0, 2);
1700       return;
1701     }
1702
1703     if (appData.matchMode) {
1704         if(appData.tourneyFile[0]) { // start tourney from command line
1705             FILE *f;
1706             if(f = fopen(appData.tourneyFile, "r")) {
1707                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708                 fclose(f);
1709                 appData.clockMode = TRUE;
1710                 SetGNUMode();
1711             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1712         }
1713         MatchEvent(TRUE);
1714     } else if (*appData.cmailGameName != NULLCHAR) {
1715         /* Set up cmail mode */
1716         ReloadCmailMsgEvent(TRUE);
1717     } else {
1718         /* Set up other modes */
1719         if (initialMode == AnalyzeFile) {
1720           if (*appData.loadGameFile == NULLCHAR) {
1721             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1722             return;
1723           }
1724         }
1725         if (*appData.loadGameFile != NULLCHAR) {
1726             (void) LoadGameFromFile(appData.loadGameFile,
1727                                     appData.loadGameIndex,
1728                                     appData.loadGameFile, TRUE);
1729         } else if (*appData.loadPositionFile != NULLCHAR) {
1730             (void) LoadPositionFromFile(appData.loadPositionFile,
1731                                         appData.loadPositionIndex,
1732                                         appData.loadPositionFile);
1733             /* [HGM] try to make self-starting even after FEN load */
1734             /* to allow automatic setup of fairy variants with wtm */
1735             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736                 gameMode = BeginningOfGame;
1737                 setboardSpoiledMachineBlack = 1;
1738             }
1739             /* [HGM] loadPos: make that every new game uses the setup */
1740             /* from file as long as we do not switch variant          */
1741             if(!blackPlaysFirst) {
1742                 startedFromPositionFile = TRUE;
1743                 CopyBoard(filePosition, boards[0]);
1744                 CopyBoard(initialPosition, boards[0]);
1745             }
1746         } else if(*appData.fen != NULLCHAR) {
1747             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1748                 startedFromPositionFile = TRUE;
1749                 Reset(TRUE, TRUE);
1750             }
1751         }
1752         if (initialMode == AnalyzeMode) {
1753           if (appData.noChessProgram) {
1754             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1755             return;
1756           }
1757           if (appData.icsActive) {
1758             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1759             return;
1760           }
1761           AnalyzeModeEvent();
1762         } else if (initialMode == AnalyzeFile) {
1763           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1764           ShowThinkingEvent();
1765           AnalyzeFileEvent();
1766           AnalysisPeriodicEvent(1);
1767         } else if (initialMode == MachinePlaysWhite) {
1768           if (appData.noChessProgram) {
1769             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1770                               0, 2);
1771             return;
1772           }
1773           if (appData.icsActive) {
1774             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1775                               0, 2);
1776             return;
1777           }
1778           MachineWhiteEvent();
1779         } else if (initialMode == MachinePlaysBlack) {
1780           if (appData.noChessProgram) {
1781             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1782                               0, 2);
1783             return;
1784           }
1785           if (appData.icsActive) {
1786             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1787                               0, 2);
1788             return;
1789           }
1790           MachineBlackEvent();
1791         } else if (initialMode == TwoMachinesPlay) {
1792           if (appData.noChessProgram) {
1793             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1794                               0, 2);
1795             return;
1796           }
1797           if (appData.icsActive) {
1798             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1799                               0, 2);
1800             return;
1801           }
1802           TwoMachinesEvent();
1803         } else if (initialMode == EditGame) {
1804           EditGameEvent();
1805         } else if (initialMode == EditPosition) {
1806           EditPositionEvent();
1807         } else if (initialMode == Training) {
1808           if (*appData.loadGameFile == NULLCHAR) {
1809             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1810             return;
1811           }
1812           TrainingEvent();
1813         }
1814     }
1815 }
1816
1817 void
1818 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1819 {
1820     DisplayBook(current+1);
1821
1822     MoveHistorySet( movelist, first, last, current, pvInfoList );
1823
1824     EvalGraphSet( first, last, current, pvInfoList );
1825
1826     MakeEngineOutputTitle();
1827 }
1828
1829 /*
1830  * Establish will establish a contact to a remote host.port.
1831  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1832  *  used to talk to the host.
1833  * Returns 0 if okay, error code if not.
1834  */
1835 int
1836 establish ()
1837 {
1838     char buf[MSG_SIZ];
1839
1840     if (*appData.icsCommPort != NULLCHAR) {
1841         /* Talk to the host through a serial comm port */
1842         return OpenCommPort(appData.icsCommPort, &icsPR);
1843
1844     } else if (*appData.gateway != NULLCHAR) {
1845         if (*appData.remoteShell == NULLCHAR) {
1846             /* Use the rcmd protocol to run telnet program on a gateway host */
1847             snprintf(buf, sizeof(buf), "%s %s %s",
1848                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1849             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1850
1851         } else {
1852             /* Use the rsh program to run telnet program on a gateway host */
1853             if (*appData.remoteUser == NULLCHAR) {
1854                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1855                         appData.gateway, appData.telnetProgram,
1856                         appData.icsHost, appData.icsPort);
1857             } else {
1858                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1859                         appData.remoteShell, appData.gateway,
1860                         appData.remoteUser, appData.telnetProgram,
1861                         appData.icsHost, appData.icsPort);
1862             }
1863             return StartChildProcess(buf, "", &icsPR);
1864
1865         }
1866     } else if (appData.useTelnet) {
1867         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1868
1869     } else {
1870         /* TCP socket interface differs somewhat between
1871            Unix and NT; handle details in the front end.
1872            */
1873         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1874     }
1875 }
1876
1877 void
1878 EscapeExpand (char *p, char *q)
1879 {       // [HGM] initstring: routine to shape up string arguments
1880         while(*p++ = *q++) if(p[-1] == '\\')
1881             switch(*q++) {
1882                 case 'n': p[-1] = '\n'; break;
1883                 case 'r': p[-1] = '\r'; break;
1884                 case 't': p[-1] = '\t'; break;
1885                 case '\\': p[-1] = '\\'; break;
1886                 case 0: *p = 0; return;
1887                 default: p[-1] = q[-1]; break;
1888             }
1889 }
1890
1891 void
1892 show_bytes (FILE *fp, char *buf, int count)
1893 {
1894     while (count--) {
1895         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1896             fprintf(fp, "\\%03o", *buf & 0xff);
1897         } else {
1898             putc(*buf, fp);
1899         }
1900         buf++;
1901     }
1902     fflush(fp);
1903 }
1904
1905 /* Returns an errno value */
1906 int
1907 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1908 {
1909     char buf[8192], *p, *q, *buflim;
1910     int left, newcount, outcount;
1911
1912     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1913         *appData.gateway != NULLCHAR) {
1914         if (appData.debugMode) {
1915             fprintf(debugFP, ">ICS: ");
1916             show_bytes(debugFP, message, count);
1917             fprintf(debugFP, "\n");
1918         }
1919         return OutputToProcess(pr, message, count, outError);
1920     }
1921
1922     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1923     p = message;
1924     q = buf;
1925     left = count;
1926     newcount = 0;
1927     while (left) {
1928         if (q >= buflim) {
1929             if (appData.debugMode) {
1930                 fprintf(debugFP, ">ICS: ");
1931                 show_bytes(debugFP, buf, newcount);
1932                 fprintf(debugFP, "\n");
1933             }
1934             outcount = OutputToProcess(pr, buf, newcount, outError);
1935             if (outcount < newcount) return -1; /* to be sure */
1936             q = buf;
1937             newcount = 0;
1938         }
1939         if (*p == '\n') {
1940             *q++ = '\r';
1941             newcount++;
1942         } else if (((unsigned char) *p) == TN_IAC) {
1943             *q++ = (char) TN_IAC;
1944             newcount ++;
1945         }
1946         *q++ = *p++;
1947         newcount++;
1948         left--;
1949     }
1950     if (appData.debugMode) {
1951         fprintf(debugFP, ">ICS: ");
1952         show_bytes(debugFP, buf, newcount);
1953         fprintf(debugFP, "\n");
1954     }
1955     outcount = OutputToProcess(pr, buf, newcount, outError);
1956     if (outcount < newcount) return -1; /* to be sure */
1957     return count;
1958 }
1959
1960 void
1961 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1962 {
1963     int outError, outCount;
1964     static int gotEof = 0;
1965     static FILE *ini;
1966
1967     /* Pass data read from player on to ICS */
1968     if (count > 0) {
1969         gotEof = 0;
1970         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1971         if (outCount < count) {
1972             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1973         }
1974         if(have_sent_ICS_logon == 2) {
1975           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1976             fprintf(ini, "%s", message);
1977             have_sent_ICS_logon = 3;
1978           } else
1979             have_sent_ICS_logon = 1;
1980         } else if(have_sent_ICS_logon == 3) {
1981             fprintf(ini, "%s", message);
1982             fclose(ini);
1983           have_sent_ICS_logon = 1;
1984         }
1985     } else if (count < 0) {
1986         RemoveInputSource(isr);
1987         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1988     } else if (gotEof++ > 0) {
1989         RemoveInputSource(isr);
1990         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1991     }
1992 }
1993
1994 void
1995 KeepAlive ()
1996 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1997     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1998     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1999     SendToICS("date\n");
2000     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2001 }
2002
2003 /* added routine for printf style output to ics */
2004 void
2005 ics_printf (char *format, ...)
2006 {
2007     char buffer[MSG_SIZ];
2008     va_list args;
2009
2010     va_start(args, format);
2011     vsnprintf(buffer, sizeof(buffer), format, args);
2012     buffer[sizeof(buffer)-1] = '\0';
2013     SendToICS(buffer);
2014     va_end(args);
2015 }
2016
2017 void
2018 SendToICS (char *s)
2019 {
2020     int count, outCount, outError;
2021
2022     if (icsPR == NoProc) return;
2023
2024     count = strlen(s);
2025     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2026     if (outCount < count) {
2027         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2028     }
2029 }
2030
2031 /* This is used for sending logon scripts to the ICS. Sending
2032    without a delay causes problems when using timestamp on ICC
2033    (at least on my machine). */
2034 void
2035 SendToICSDelayed (char *s, long msdelay)
2036 {
2037     int count, outCount, outError;
2038
2039     if (icsPR == NoProc) return;
2040
2041     count = strlen(s);
2042     if (appData.debugMode) {
2043         fprintf(debugFP, ">ICS: ");
2044         show_bytes(debugFP, s, count);
2045         fprintf(debugFP, "\n");
2046     }
2047     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2048                                       msdelay);
2049     if (outCount < count) {
2050         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2051     }
2052 }
2053
2054
2055 /* Remove all highlighting escape sequences in s
2056    Also deletes any suffix starting with '('
2057    */
2058 char *
2059 StripHighlightAndTitle (char *s)
2060 {
2061     static char retbuf[MSG_SIZ];
2062     char *p = retbuf;
2063
2064     while (*s != NULLCHAR) {
2065         while (*s == '\033') {
2066             while (*s != NULLCHAR && !isalpha(*s)) s++;
2067             if (*s != NULLCHAR) s++;
2068         }
2069         while (*s != NULLCHAR && *s != '\033') {
2070             if (*s == '(' || *s == '[') {
2071                 *p = NULLCHAR;
2072                 return retbuf;
2073             }
2074             *p++ = *s++;
2075         }
2076     }
2077     *p = NULLCHAR;
2078     return retbuf;
2079 }
2080
2081 /* Remove all highlighting escape sequences in s */
2082 char *
2083 StripHighlight (char *s)
2084 {
2085     static char retbuf[MSG_SIZ];
2086     char *p = retbuf;
2087
2088     while (*s != NULLCHAR) {
2089         while (*s == '\033') {
2090             while (*s != NULLCHAR && !isalpha(*s)) s++;
2091             if (*s != NULLCHAR) s++;
2092         }
2093         while (*s != NULLCHAR && *s != '\033') {
2094             *p++ = *s++;
2095         }
2096     }
2097     *p = NULLCHAR;
2098     return retbuf;
2099 }
2100
2101 char engineVariant[MSG_SIZ];
2102 char *variantNames[] = VARIANT_NAMES;
2103 char *
2104 VariantName (VariantClass v)
2105 {
2106     if(v == VariantUnknown || *engineVariant) return engineVariant;
2107     return variantNames[v];
2108 }
2109
2110
2111 /* Identify a variant from the strings the chess servers use or the
2112    PGN Variant tag names we use. */
2113 VariantClass
2114 StringToVariant (char *e)
2115 {
2116     char *p;
2117     int wnum = -1;
2118     VariantClass v = VariantNormal;
2119     int i, found = FALSE;
2120     char buf[MSG_SIZ], c;
2121     int len;
2122
2123     if (!e) return v;
2124
2125     /* [HGM] skip over optional board-size prefixes */
2126     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2127         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2128         while( *e++ != '_');
2129     }
2130
2131     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2132         v = VariantNormal;
2133         found = TRUE;
2134     } else
2135     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2136       if (p = StrCaseStr(e, variantNames[i])) {
2137         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2138         v = (VariantClass) i;
2139         found = TRUE;
2140         break;
2141       }
2142     }
2143
2144     if (!found) {
2145       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2146           || StrCaseStr(e, "wild/fr")
2147           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2148         v = VariantFischeRandom;
2149       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2150                  (i = 1, p = StrCaseStr(e, "w"))) {
2151         p += i;
2152         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2153         if (isdigit(*p)) {
2154           wnum = atoi(p);
2155         } else {
2156           wnum = -1;
2157         }
2158         switch (wnum) {
2159         case 0: /* FICS only, actually */
2160         case 1:
2161           /* Castling legal even if K starts on d-file */
2162           v = VariantWildCastle;
2163           break;
2164         case 2:
2165         case 3:
2166         case 4:
2167           /* Castling illegal even if K & R happen to start in
2168              normal positions. */
2169           v = VariantNoCastle;
2170           break;
2171         case 5:
2172         case 7:
2173         case 8:
2174         case 10:
2175         case 11:
2176         case 12:
2177         case 13:
2178         case 14:
2179         case 15:
2180         case 18:
2181         case 19:
2182           /* Castling legal iff K & R start in normal positions */
2183           v = VariantNormal;
2184           break;
2185         case 6:
2186         case 20:
2187         case 21:
2188           /* Special wilds for position setup; unclear what to do here */
2189           v = VariantLoadable;
2190           break;
2191         case 9:
2192           /* Bizarre ICC game */
2193           v = VariantTwoKings;
2194           break;
2195         case 16:
2196           v = VariantKriegspiel;
2197           break;
2198         case 17:
2199           v = VariantLosers;
2200           break;
2201         case 22:
2202           v = VariantFischeRandom;
2203           break;
2204         case 23:
2205           v = VariantCrazyhouse;
2206           break;
2207         case 24:
2208           v = VariantBughouse;
2209           break;
2210         case 25:
2211           v = Variant3Check;
2212           break;
2213         case 26:
2214           /* Not quite the same as FICS suicide! */
2215           v = VariantGiveaway;
2216           break;
2217         case 27:
2218           v = VariantAtomic;
2219           break;
2220         case 28:
2221           v = VariantShatranj;
2222           break;
2223
2224         /* Temporary names for future ICC types.  The name *will* change in
2225            the next xboard/WinBoard release after ICC defines it. */
2226         case 29:
2227           v = Variant29;
2228           break;
2229         case 30:
2230           v = Variant30;
2231           break;
2232         case 31:
2233           v = Variant31;
2234           break;
2235         case 32:
2236           v = Variant32;
2237           break;
2238         case 33:
2239           v = Variant33;
2240           break;
2241         case 34:
2242           v = Variant34;
2243           break;
2244         case 35:
2245           v = Variant35;
2246           break;
2247         case 36:
2248           v = Variant36;
2249           break;
2250         case 37:
2251           v = VariantShogi;
2252           break;
2253         case 38:
2254           v = VariantXiangqi;
2255           break;
2256         case 39:
2257           v = VariantCourier;
2258           break;
2259         case 40:
2260           v = VariantGothic;
2261           break;
2262         case 41:
2263           v = VariantCapablanca;
2264           break;
2265         case 42:
2266           v = VariantKnightmate;
2267           break;
2268         case 43:
2269           v = VariantFairy;
2270           break;
2271         case 44:
2272           v = VariantCylinder;
2273           break;
2274         case 45:
2275           v = VariantFalcon;
2276           break;
2277         case 46:
2278           v = VariantCapaRandom;
2279           break;
2280         case 47:
2281           v = VariantBerolina;
2282           break;
2283         case 48:
2284           v = VariantJanus;
2285           break;
2286         case 49:
2287           v = VariantSuper;
2288           break;
2289         case 50:
2290           v = VariantGreat;
2291           break;
2292         case -1:
2293           /* Found "wild" or "w" in the string but no number;
2294              must assume it's normal chess. */
2295           v = VariantNormal;
2296           break;
2297         default:
2298           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2299           if( (len >= MSG_SIZ) && appData.debugMode )
2300             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2301
2302           DisplayError(buf, 0);
2303           v = VariantUnknown;
2304           break;
2305         }
2306       }
2307     }
2308     if (appData.debugMode) {
2309       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2310               e, wnum, VariantName(v));
2311     }
2312     return v;
2313 }
2314
2315 static int leftover_start = 0, leftover_len = 0;
2316 char star_match[STAR_MATCH_N][MSG_SIZ];
2317
2318 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2319    advance *index beyond it, and set leftover_start to the new value of
2320    *index; else return FALSE.  If pattern contains the character '*', it
2321    matches any sequence of characters not containing '\r', '\n', or the
2322    character following the '*' (if any), and the matched sequence(s) are
2323    copied into star_match.
2324    */
2325 int
2326 looking_at ( char *buf, int *index, char *pattern)
2327 {
2328     char *bufp = &buf[*index], *patternp = pattern;
2329     int star_count = 0;
2330     char *matchp = star_match[0];
2331
2332     for (;;) {
2333         if (*patternp == NULLCHAR) {
2334             *index = leftover_start = bufp - buf;
2335             *matchp = NULLCHAR;
2336             return TRUE;
2337         }
2338         if (*bufp == NULLCHAR) return FALSE;
2339         if (*patternp == '*') {
2340             if (*bufp == *(patternp + 1)) {
2341                 *matchp = NULLCHAR;
2342                 matchp = star_match[++star_count];
2343                 patternp += 2;
2344                 bufp++;
2345                 continue;
2346             } else if (*bufp == '\n' || *bufp == '\r') {
2347                 patternp++;
2348                 if (*patternp == NULLCHAR)
2349                   continue;
2350                 else
2351                   return FALSE;
2352             } else {
2353                 *matchp++ = *bufp++;
2354                 continue;
2355             }
2356         }
2357         if (*patternp != *bufp) return FALSE;
2358         patternp++;
2359         bufp++;
2360     }
2361 }
2362
2363 void
2364 SendToPlayer (char *data, int length)
2365 {
2366     int error, outCount;
2367     outCount = OutputToProcess(NoProc, data, length, &error);
2368     if (outCount < length) {
2369         DisplayFatalError(_("Error writing to display"), error, 1);
2370     }
2371 }
2372
2373 void
2374 PackHolding (char packed[], char *holding)
2375 {
2376     char *p = holding;
2377     char *q = packed;
2378     int runlength = 0;
2379     int curr = 9999;
2380     do {
2381         if (*p == curr) {
2382             runlength++;
2383         } else {
2384             switch (runlength) {
2385               case 0:
2386                 break;
2387               case 1:
2388                 *q++ = curr;
2389                 break;
2390               case 2:
2391                 *q++ = curr;
2392                 *q++ = curr;
2393                 break;
2394               default:
2395                 sprintf(q, "%d", runlength);
2396                 while (*q) q++;
2397                 *q++ = curr;
2398                 break;
2399             }
2400             runlength = 1;
2401             curr = *p;
2402         }
2403     } while (*p++);
2404     *q = NULLCHAR;
2405 }
2406
2407 /* Telnet protocol requests from the front end */
2408 void
2409 TelnetRequest (unsigned char ddww, unsigned char option)
2410 {
2411     unsigned char msg[3];
2412     int outCount, outError;
2413
2414     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2415
2416     if (appData.debugMode) {
2417         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2418         switch (ddww) {
2419           case TN_DO:
2420             ddwwStr = "DO";
2421             break;
2422           case TN_DONT:
2423             ddwwStr = "DONT";
2424             break;
2425           case TN_WILL:
2426             ddwwStr = "WILL";
2427             break;
2428           case TN_WONT:
2429             ddwwStr = "WONT";
2430             break;
2431           default:
2432             ddwwStr = buf1;
2433             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2434             break;
2435         }
2436         switch (option) {
2437           case TN_ECHO:
2438             optionStr = "ECHO";
2439             break;
2440           default:
2441             optionStr = buf2;
2442             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2443             break;
2444         }
2445         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2446     }
2447     msg[0] = TN_IAC;
2448     msg[1] = ddww;
2449     msg[2] = option;
2450     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2451     if (outCount < 3) {
2452         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2453     }
2454 }
2455
2456 void
2457 DoEcho ()
2458 {
2459     if (!appData.icsActive) return;
2460     TelnetRequest(TN_DO, TN_ECHO);
2461 }
2462
2463 void
2464 DontEcho ()
2465 {
2466     if (!appData.icsActive) return;
2467     TelnetRequest(TN_DONT, TN_ECHO);
2468 }
2469
2470 void
2471 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2472 {
2473     /* put the holdings sent to us by the server on the board holdings area */
2474     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2475     char p;
2476     ChessSquare piece;
2477
2478     if(gameInfo.holdingsWidth < 2)  return;
2479     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2480         return; // prevent overwriting by pre-board holdings
2481
2482     if( (int)lowestPiece >= BlackPawn ) {
2483         holdingsColumn = 0;
2484         countsColumn = 1;
2485         holdingsStartRow = BOARD_HEIGHT-1;
2486         direction = -1;
2487     } else {
2488         holdingsColumn = BOARD_WIDTH-1;
2489         countsColumn = BOARD_WIDTH-2;
2490         holdingsStartRow = 0;
2491         direction = 1;
2492     }
2493
2494     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2495         board[i][holdingsColumn] = EmptySquare;
2496         board[i][countsColumn]   = (ChessSquare) 0;
2497     }
2498     while( (p=*holdings++) != NULLCHAR ) {
2499         piece = CharToPiece( ToUpper(p) );
2500         if(piece == EmptySquare) continue;
2501         /*j = (int) piece - (int) WhitePawn;*/
2502         j = PieceToNumber(piece);
2503         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2504         if(j < 0) continue;               /* should not happen */
2505         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2506         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2507         board[holdingsStartRow+j*direction][countsColumn]++;
2508     }
2509 }
2510
2511
2512 void
2513 VariantSwitch (Board board, VariantClass newVariant)
2514 {
2515    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2516    static Board oldBoard;
2517
2518    startedFromPositionFile = FALSE;
2519    if(gameInfo.variant == newVariant) return;
2520
2521    /* [HGM] This routine is called each time an assignment is made to
2522     * gameInfo.variant during a game, to make sure the board sizes
2523     * are set to match the new variant. If that means adding or deleting
2524     * holdings, we shift the playing board accordingly
2525     * This kludge is needed because in ICS observe mode, we get boards
2526     * of an ongoing game without knowing the variant, and learn about the
2527     * latter only later. This can be because of the move list we requested,
2528     * in which case the game history is refilled from the beginning anyway,
2529     * but also when receiving holdings of a crazyhouse game. In the latter
2530     * case we want to add those holdings to the already received position.
2531     */
2532
2533
2534    if (appData.debugMode) {
2535      fprintf(debugFP, "Switch board from %s to %s\n",
2536              VariantName(gameInfo.variant), VariantName(newVariant));
2537      setbuf(debugFP, NULL);
2538    }
2539    shuffleOpenings = 0;       /* [HGM] shuffle */
2540    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2541    switch(newVariant)
2542      {
2543      case VariantShogi:
2544        newWidth = 9;  newHeight = 9;
2545        gameInfo.holdingsSize = 7;
2546      case VariantBughouse:
2547      case VariantCrazyhouse:
2548        newHoldingsWidth = 2; break;
2549      case VariantGreat:
2550        newWidth = 10;
2551      case VariantSuper:
2552        newHoldingsWidth = 2;
2553        gameInfo.holdingsSize = 8;
2554        break;
2555      case VariantGothic:
2556      case VariantCapablanca:
2557      case VariantCapaRandom:
2558        newWidth = 10;
2559      default:
2560        newHoldingsWidth = gameInfo.holdingsSize = 0;
2561      };
2562
2563    if(newWidth  != gameInfo.boardWidth  ||
2564       newHeight != gameInfo.boardHeight ||
2565       newHoldingsWidth != gameInfo.holdingsWidth ) {
2566
2567      /* shift position to new playing area, if needed */
2568      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2569        for(i=0; i<BOARD_HEIGHT; i++)
2570          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2571            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2572              board[i][j];
2573        for(i=0; i<newHeight; i++) {
2574          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2575          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2576        }
2577      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2578        for(i=0; i<BOARD_HEIGHT; i++)
2579          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2580            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2581              board[i][j];
2582      }
2583      board[HOLDINGS_SET] = 0;
2584      gameInfo.boardWidth  = newWidth;
2585      gameInfo.boardHeight = newHeight;
2586      gameInfo.holdingsWidth = newHoldingsWidth;
2587      gameInfo.variant = newVariant;
2588      InitDrawingSizes(-2, 0);
2589    } else gameInfo.variant = newVariant;
2590    CopyBoard(oldBoard, board);   // remember correctly formatted board
2591      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2592    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2593 }
2594
2595 static int loggedOn = FALSE;
2596
2597 /*-- Game start info cache: --*/
2598 int gs_gamenum;
2599 char gs_kind[MSG_SIZ];
2600 static char player1Name[128] = "";
2601 static char player2Name[128] = "";
2602 static char cont_seq[] = "\n\\   ";
2603 static int player1Rating = -1;
2604 static int player2Rating = -1;
2605 /*----------------------------*/
2606
2607 ColorClass curColor = ColorNormal;
2608 int suppressKibitz = 0;
2609
2610 // [HGM] seekgraph
2611 Boolean soughtPending = FALSE;
2612 Boolean seekGraphUp;
2613 #define MAX_SEEK_ADS 200
2614 #define SQUARE 0x80
2615 char *seekAdList[MAX_SEEK_ADS];
2616 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2617 float tcList[MAX_SEEK_ADS];
2618 char colorList[MAX_SEEK_ADS];
2619 int nrOfSeekAds = 0;
2620 int minRating = 1010, maxRating = 2800;
2621 int hMargin = 10, vMargin = 20, h, w;
2622 extern int squareSize, lineGap;
2623
2624 void
2625 PlotSeekAd (int i)
2626 {
2627         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2628         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2629         if(r < minRating+100 && r >=0 ) r = minRating+100;
2630         if(r > maxRating) r = maxRating;
2631         if(tc < 1.f) tc = 1.f;
2632         if(tc > 95.f) tc = 95.f;
2633         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2634         y = ((double)r - minRating)/(maxRating - minRating)
2635             * (h-vMargin-squareSize/8-1) + vMargin;
2636         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2637         if(strstr(seekAdList[i], " u ")) color = 1;
2638         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2639            !strstr(seekAdList[i], "bullet") &&
2640            !strstr(seekAdList[i], "blitz") &&
2641            !strstr(seekAdList[i], "standard") ) color = 2;
2642         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2643         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2644 }
2645
2646 void
2647 PlotSingleSeekAd (int i)
2648 {
2649         PlotSeekAd(i);
2650 }
2651
2652 void
2653 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2654 {
2655         char buf[MSG_SIZ], *ext = "";
2656         VariantClass v = StringToVariant(type);
2657         if(strstr(type, "wild")) {
2658             ext = type + 4; // append wild number
2659             if(v == VariantFischeRandom) type = "chess960"; else
2660             if(v == VariantLoadable) type = "setup"; else
2661             type = VariantName(v);
2662         }
2663         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2664         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2665             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2666             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2667             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2668             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2669             seekNrList[nrOfSeekAds] = nr;
2670             zList[nrOfSeekAds] = 0;
2671             seekAdList[nrOfSeekAds++] = StrSave(buf);
2672             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2673         }
2674 }
2675
2676 void
2677 EraseSeekDot (int i)
2678 {
2679     int x = xList[i], y = yList[i], d=squareSize/4, k;
2680     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2681     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2682     // now replot every dot that overlapped
2683     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2684         int xx = xList[k], yy = yList[k];
2685         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2686             DrawSeekDot(xx, yy, colorList[k]);
2687     }
2688 }
2689
2690 void
2691 RemoveSeekAd (int nr)
2692 {
2693         int i;
2694         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2695             EraseSeekDot(i);
2696             if(seekAdList[i]) free(seekAdList[i]);
2697             seekAdList[i] = seekAdList[--nrOfSeekAds];
2698             seekNrList[i] = seekNrList[nrOfSeekAds];
2699             ratingList[i] = ratingList[nrOfSeekAds];
2700             colorList[i]  = colorList[nrOfSeekAds];
2701             tcList[i] = tcList[nrOfSeekAds];
2702             xList[i]  = xList[nrOfSeekAds];
2703             yList[i]  = yList[nrOfSeekAds];
2704             zList[i]  = zList[nrOfSeekAds];
2705             seekAdList[nrOfSeekAds] = NULL;
2706             break;
2707         }
2708 }
2709
2710 Boolean
2711 MatchSoughtLine (char *line)
2712 {
2713     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2714     int nr, base, inc, u=0; char dummy;
2715
2716     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2717        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2718        (u=1) &&
2719        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2720         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2721         // match: compact and save the line
2722         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2723         return TRUE;
2724     }
2725     return FALSE;
2726 }
2727
2728 int
2729 DrawSeekGraph ()
2730 {
2731     int i;
2732     if(!seekGraphUp) return FALSE;
2733     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2734     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2735
2736     DrawSeekBackground(0, 0, w, h);
2737     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2738     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2739     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2740         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2741         yy = h-1-yy;
2742         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2743         if(i%500 == 0) {
2744             char buf[MSG_SIZ];
2745             snprintf(buf, MSG_SIZ, "%d", i);
2746             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2747         }
2748     }
2749     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2750     for(i=1; i<100; i+=(i<10?1:5)) {
2751         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2752         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2753         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2754             char buf[MSG_SIZ];
2755             snprintf(buf, MSG_SIZ, "%d", i);
2756             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2757         }
2758     }
2759     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2760     return TRUE;
2761 }
2762
2763 int
2764 SeekGraphClick (ClickType click, int x, int y, int moving)
2765 {
2766     static int lastDown = 0, displayed = 0, lastSecond;
2767     if(y < 0) return FALSE;
2768     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2769         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2770         if(!seekGraphUp) return FALSE;
2771         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2772         DrawPosition(TRUE, NULL);
2773         return TRUE;
2774     }
2775     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2776         if(click == Release || moving) return FALSE;
2777         nrOfSeekAds = 0;
2778         soughtPending = TRUE;
2779         SendToICS(ics_prefix);
2780         SendToICS("sought\n"); // should this be "sought all"?
2781     } else { // issue challenge based on clicked ad
2782         int dist = 10000; int i, closest = 0, second = 0;
2783         for(i=0; i<nrOfSeekAds; i++) {
2784             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2785             if(d < dist) { dist = d; closest = i; }
2786             second += (d - zList[i] < 120); // count in-range ads
2787             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2788         }
2789         if(dist < 120) {
2790             char buf[MSG_SIZ];
2791             second = (second > 1);
2792             if(displayed != closest || second != lastSecond) {
2793                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2794                 lastSecond = second; displayed = closest;
2795             }
2796             if(click == Press) {
2797                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2798                 lastDown = closest;
2799                 return TRUE;
2800             } // on press 'hit', only show info
2801             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2802             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2803             SendToICS(ics_prefix);
2804             SendToICS(buf);
2805             return TRUE; // let incoming board of started game pop down the graph
2806         } else if(click == Release) { // release 'miss' is ignored
2807             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2808             if(moving == 2) { // right up-click
2809                 nrOfSeekAds = 0; // refresh graph
2810                 soughtPending = TRUE;
2811                 SendToICS(ics_prefix);
2812                 SendToICS("sought\n"); // should this be "sought all"?
2813             }
2814             return TRUE;
2815         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2816         // press miss or release hit 'pop down' seek graph
2817         seekGraphUp = FALSE;
2818         DrawPosition(TRUE, NULL);
2819     }
2820     return TRUE;
2821 }
2822
2823 void
2824 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2825 {
2826 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2827 #define STARTED_NONE 0
2828 #define STARTED_MOVES 1
2829 #define STARTED_BOARD 2
2830 #define STARTED_OBSERVE 3
2831 #define STARTED_HOLDINGS 4
2832 #define STARTED_CHATTER 5
2833 #define STARTED_COMMENT 6
2834 #define STARTED_MOVES_NOHIDE 7
2835
2836     static int started = STARTED_NONE;
2837     static char parse[20000];
2838     static int parse_pos = 0;
2839     static char buf[BUF_SIZE + 1];
2840     static int firstTime = TRUE, intfSet = FALSE;
2841     static ColorClass prevColor = ColorNormal;
2842     static int savingComment = FALSE;
2843     static int cmatch = 0; // continuation sequence match
2844     char *bp;
2845     char str[MSG_SIZ];
2846     int i, oldi;
2847     int buf_len;
2848     int next_out;
2849     int tkind;
2850     int backup;    /* [DM] For zippy color lines */
2851     char *p;
2852     char talker[MSG_SIZ]; // [HGM] chat
2853     int channel, collective=0;
2854
2855     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2856
2857     if (appData.debugMode) {
2858       if (!error) {
2859         fprintf(debugFP, "<ICS: ");
2860         show_bytes(debugFP, data, count);
2861         fprintf(debugFP, "\n");
2862       }
2863     }
2864
2865     if (appData.debugMode) { int f = forwardMostMove;
2866         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2867                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2868                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2869     }
2870     if (count > 0) {
2871         /* If last read ended with a partial line that we couldn't parse,
2872            prepend it to the new read and try again. */
2873         if (leftover_len > 0) {
2874             for (i=0; i<leftover_len; i++)
2875               buf[i] = buf[leftover_start + i];
2876         }
2877
2878     /* copy new characters into the buffer */
2879     bp = buf + leftover_len;
2880     buf_len=leftover_len;
2881     for (i=0; i<count; i++)
2882     {
2883         // ignore these
2884         if (data[i] == '\r')
2885             continue;
2886
2887         // join lines split by ICS?
2888         if (!appData.noJoin)
2889         {
2890             /*
2891                 Joining just consists of finding matches against the
2892                 continuation sequence, and discarding that sequence
2893                 if found instead of copying it.  So, until a match
2894                 fails, there's nothing to do since it might be the
2895                 complete sequence, and thus, something we don't want
2896                 copied.
2897             */
2898             if (data[i] == cont_seq[cmatch])
2899             {
2900                 cmatch++;
2901                 if (cmatch == strlen(cont_seq))
2902                 {
2903                     cmatch = 0; // complete match.  just reset the counter
2904
2905                     /*
2906                         it's possible for the ICS to not include the space
2907                         at the end of the last word, making our [correct]
2908                         join operation fuse two separate words.  the server
2909                         does this when the space occurs at the width setting.
2910                     */
2911                     if (!buf_len || buf[buf_len-1] != ' ')
2912                     {
2913                         *bp++ = ' ';
2914                         buf_len++;
2915                     }
2916                 }
2917                 continue;
2918             }
2919             else if (cmatch)
2920             {
2921                 /*
2922                     match failed, so we have to copy what matched before
2923                     falling through and copying this character.  In reality,
2924                     this will only ever be just the newline character, but
2925                     it doesn't hurt to be precise.
2926                 */
2927                 strncpy(bp, cont_seq, cmatch);
2928                 bp += cmatch;
2929                 buf_len += cmatch;
2930                 cmatch = 0;
2931             }
2932         }
2933
2934         // copy this char
2935         *bp++ = data[i];
2936         buf_len++;
2937     }
2938
2939         buf[buf_len] = NULLCHAR;
2940 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2941         next_out = 0;
2942         leftover_start = 0;
2943
2944         i = 0;
2945         while (i < buf_len) {
2946             /* Deal with part of the TELNET option negotiation
2947                protocol.  We refuse to do anything beyond the
2948                defaults, except that we allow the WILL ECHO option,
2949                which ICS uses to turn off password echoing when we are
2950                directly connected to it.  We reject this option
2951                if localLineEditing mode is on (always on in xboard)
2952                and we are talking to port 23, which might be a real
2953                telnet server that will try to keep WILL ECHO on permanently.
2954              */
2955             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2956                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2957                 unsigned char option;
2958                 oldi = i;
2959                 switch ((unsigned char) buf[++i]) {
2960                   case TN_WILL:
2961                     if (appData.debugMode)
2962                       fprintf(debugFP, "\n<WILL ");
2963                     switch (option = (unsigned char) buf[++i]) {
2964                       case TN_ECHO:
2965                         if (appData.debugMode)
2966                           fprintf(debugFP, "ECHO ");
2967                         /* Reply only if this is a change, according
2968                            to the protocol rules. */
2969                         if (remoteEchoOption) break;
2970                         if (appData.localLineEditing &&
2971                             atoi(appData.icsPort) == TN_PORT) {
2972                             TelnetRequest(TN_DONT, TN_ECHO);
2973                         } else {
2974                             EchoOff();
2975                             TelnetRequest(TN_DO, TN_ECHO);
2976                             remoteEchoOption = TRUE;
2977                         }
2978                         break;
2979                       default:
2980                         if (appData.debugMode)
2981                           fprintf(debugFP, "%d ", option);
2982                         /* Whatever this is, we don't want it. */
2983                         TelnetRequest(TN_DONT, option);
2984                         break;
2985                     }
2986                     break;
2987                   case TN_WONT:
2988                     if (appData.debugMode)
2989                       fprintf(debugFP, "\n<WONT ");
2990                     switch (option = (unsigned char) buf[++i]) {
2991                       case TN_ECHO:
2992                         if (appData.debugMode)
2993                           fprintf(debugFP, "ECHO ");
2994                         /* Reply only if this is a change, according
2995                            to the protocol rules. */
2996                         if (!remoteEchoOption) break;
2997                         EchoOn();
2998                         TelnetRequest(TN_DONT, TN_ECHO);
2999                         remoteEchoOption = FALSE;
3000                         break;
3001                       default:
3002                         if (appData.debugMode)
3003                           fprintf(debugFP, "%d ", (unsigned char) option);
3004                         /* Whatever this is, it must already be turned
3005                            off, because we never agree to turn on
3006                            anything non-default, so according to the
3007                            protocol rules, we don't reply. */
3008                         break;
3009                     }
3010                     break;
3011                   case TN_DO:
3012                     if (appData.debugMode)
3013                       fprintf(debugFP, "\n<DO ");
3014                     switch (option = (unsigned char) buf[++i]) {
3015                       default:
3016                         /* Whatever this is, we refuse to do it. */
3017                         if (appData.debugMode)
3018                           fprintf(debugFP, "%d ", option);
3019                         TelnetRequest(TN_WONT, option);
3020                         break;
3021                     }
3022                     break;
3023                   case TN_DONT:
3024                     if (appData.debugMode)
3025                       fprintf(debugFP, "\n<DONT ");
3026                     switch (option = (unsigned char) buf[++i]) {
3027                       default:
3028                         if (appData.debugMode)
3029                           fprintf(debugFP, "%d ", option);
3030                         /* Whatever this is, we are already not doing
3031                            it, because we never agree to do anything
3032                            non-default, so according to the protocol
3033                            rules, we don't reply. */
3034                         break;
3035                     }
3036                     break;
3037                   case TN_IAC:
3038                     if (appData.debugMode)
3039                       fprintf(debugFP, "\n<IAC ");
3040                     /* Doubled IAC; pass it through */
3041                     i--;
3042                     break;
3043                   default:
3044                     if (appData.debugMode)
3045                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3046                     /* Drop all other telnet commands on the floor */
3047                     break;
3048                 }
3049                 if (oldi > next_out)
3050                   SendToPlayer(&buf[next_out], oldi - next_out);
3051                 if (++i > next_out)
3052                   next_out = i;
3053                 continue;
3054             }
3055
3056             /* OK, this at least will *usually* work */
3057             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3058                 loggedOn = TRUE;
3059             }
3060
3061             if (loggedOn && !intfSet) {
3062                 if (ics_type == ICS_ICC) {
3063                   snprintf(str, MSG_SIZ,
3064                           "/set-quietly interface %s\n/set-quietly style 12\n",
3065                           programVersion);
3066                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3067                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3068                 } else if (ics_type == ICS_CHESSNET) {
3069                   snprintf(str, MSG_SIZ, "/style 12\n");
3070                 } else {
3071                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3072                   strcat(str, programVersion);
3073                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3074                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3075                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3076 #ifdef WIN32
3077                   strcat(str, "$iset nohighlight 1\n");
3078 #endif
3079                   strcat(str, "$iset lock 1\n$style 12\n");
3080                 }
3081                 SendToICS(str);
3082                 NotifyFrontendLogin();
3083                 intfSet = TRUE;
3084             }
3085
3086             if (started == STARTED_COMMENT) {
3087                 /* Accumulate characters in comment */
3088                 parse[parse_pos++] = buf[i];
3089                 if (buf[i] == '\n') {
3090                     parse[parse_pos] = NULLCHAR;
3091                     if(chattingPartner>=0) {
3092                         char mess[MSG_SIZ];
3093                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3094                         OutputChatMessage(chattingPartner, mess);
3095                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3096                             int p;
3097                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3098                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3099                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3100                                 OutputChatMessage(p, mess);
3101                                 break;
3102                             }
3103                         }
3104                         chattingPartner = -1;
3105                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3106                         collective = 0;
3107                     } else
3108                     if(!suppressKibitz) // [HGM] kibitz
3109                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3110                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3111                         int nrDigit = 0, nrAlph = 0, j;
3112                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3113                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3114                         parse[parse_pos] = NULLCHAR;
3115                         // try to be smart: if it does not look like search info, it should go to
3116                         // ICS interaction window after all, not to engine-output window.
3117                         for(j=0; j<parse_pos; j++) { // count letters and digits
3118                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3119                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3120                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3121                         }
3122                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3123                             int depth=0; float score;
3124                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3125                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3126                                 pvInfoList[forwardMostMove-1].depth = depth;
3127                                 pvInfoList[forwardMostMove-1].score = 100*score;
3128                             }
3129                             OutputKibitz(suppressKibitz, parse);
3130                         } else {
3131                             char tmp[MSG_SIZ];
3132                             if(gameMode == IcsObserving) // restore original ICS messages
3133                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3134                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3135                             else
3136                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3137                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3138                             SendToPlayer(tmp, strlen(tmp));
3139                         }
3140                         next_out = i+1; // [HGM] suppress printing in ICS window
3141                     }
3142                     started = STARTED_NONE;
3143                 } else {
3144                     /* Don't match patterns against characters in comment */
3145                     i++;
3146                     continue;
3147                 }
3148             }
3149             if (started == STARTED_CHATTER) {
3150                 if (buf[i] != '\n') {
3151                     /* Don't match patterns against characters in chatter */
3152                     i++;
3153                     continue;
3154                 }
3155                 started = STARTED_NONE;
3156                 if(suppressKibitz) next_out = i+1;
3157             }
3158
3159             /* Kludge to deal with rcmd protocol */
3160             if (firstTime && looking_at(buf, &i, "\001*")) {
3161                 DisplayFatalError(&buf[1], 0, 1);
3162                 continue;
3163             } else {
3164                 firstTime = FALSE;
3165             }
3166
3167             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3168                 ics_type = ICS_ICC;
3169                 ics_prefix = "/";
3170                 if (appData.debugMode)
3171                   fprintf(debugFP, "ics_type %d\n", ics_type);
3172                 continue;
3173             }
3174             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3175                 ics_type = ICS_FICS;
3176                 ics_prefix = "$";
3177                 if (appData.debugMode)
3178                   fprintf(debugFP, "ics_type %d\n", ics_type);
3179                 continue;
3180             }
3181             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3182                 ics_type = ICS_CHESSNET;
3183                 ics_prefix = "/";
3184                 if (appData.debugMode)
3185                   fprintf(debugFP, "ics_type %d\n", ics_type);
3186                 continue;
3187             }
3188
3189             if (!loggedOn &&
3190                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3191                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3192                  looking_at(buf, &i, "will be \"*\""))) {
3193               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3194               continue;
3195             }
3196
3197             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3198               char buf[MSG_SIZ];
3199               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3200               DisplayIcsInteractionTitle(buf);
3201               have_set_title = TRUE;
3202             }
3203
3204             /* skip finger notes */
3205             if (started == STARTED_NONE &&
3206                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3207                  (buf[i] == '1' && buf[i+1] == '0')) &&
3208                 buf[i+2] == ':' && buf[i+3] == ' ') {
3209               started = STARTED_CHATTER;
3210               i += 3;
3211               continue;
3212             }
3213
3214             oldi = i;
3215             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3216             if(appData.seekGraph) {
3217                 if(soughtPending && MatchSoughtLine(buf+i)) {
3218                     i = strstr(buf+i, "rated") - buf;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     next_out = leftover_start = i;
3221                     started = STARTED_CHATTER;
3222                     suppressKibitz = TRUE;
3223                     continue;
3224                 }
3225                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3226                         && looking_at(buf, &i, "* ads displayed")) {
3227                     soughtPending = FALSE;
3228                     seekGraphUp = TRUE;
3229                     DrawSeekGraph();
3230                     continue;
3231                 }
3232                 if(appData.autoRefresh) {
3233                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3234                         int s = (ics_type == ICS_ICC); // ICC format differs
3235                         if(seekGraphUp)
3236                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3237                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3238                         looking_at(buf, &i, "*% "); // eat prompt
3239                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3240                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3241                         next_out = i; // suppress
3242                         continue;
3243                     }
3244                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3245                         char *p = star_match[0];
3246                         while(*p) {
3247                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3248                             while(*p && *p++ != ' '); // next
3249                         }
3250                         looking_at(buf, &i, "*% "); // eat prompt
3251                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3252                         next_out = i;
3253                         continue;
3254                     }
3255                 }
3256             }
3257
3258             /* skip formula vars */
3259             if (started == STARTED_NONE &&
3260                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3261               started = STARTED_CHATTER;
3262               i += 3;
3263               continue;
3264             }
3265
3266             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3267             if (appData.autoKibitz && started == STARTED_NONE &&
3268                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3269                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3270                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3271                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3272                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3273                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3274                         suppressKibitz = TRUE;
3275                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3276                         next_out = i;
3277                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3278                                 && (gameMode == IcsPlayingWhite)) ||
3279                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3280                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3281                             started = STARTED_CHATTER; // own kibitz we simply discard
3282                         else {
3283                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3284                             parse_pos = 0; parse[0] = NULLCHAR;
3285                             savingComment = TRUE;
3286                             suppressKibitz = gameMode != IcsObserving ? 2 :
3287                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3288                         }
3289                         continue;
3290                 } else
3291                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3292                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3293                          && atoi(star_match[0])) {
3294                     // suppress the acknowledgements of our own autoKibitz
3295                     char *p;
3296                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3297                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3298                     SendToPlayer(star_match[0], strlen(star_match[0]));
3299                     if(looking_at(buf, &i, "*% ")) // eat prompt
3300                         suppressKibitz = FALSE;
3301                     next_out = i;
3302                     continue;
3303                 }
3304             } // [HGM] kibitz: end of patch
3305
3306             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3307
3308             // [HGM] chat: intercept tells by users for which we have an open chat window
3309             channel = -1;
3310             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3311                                            looking_at(buf, &i, "* whispers:") ||
3312                                            looking_at(buf, &i, "* kibitzes:") ||
3313                                            looking_at(buf, &i, "* shouts:") ||
3314                                            looking_at(buf, &i, "* c-shouts:") ||
3315                                            looking_at(buf, &i, "--> * ") ||
3316                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3317                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3318                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3319                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3320                 int p;
3321                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3322                 chattingPartner = -1; collective = 0;
3323
3324                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3325                 for(p=0; p<MAX_CHAT; p++) {
3326                     collective = 1;
3327                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3328                     talker[0] = '['; strcat(talker, "] ");
3329                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3330                     chattingPartner = p; break;
3331                     }
3332                 } else
3333                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3334                 for(p=0; p<MAX_CHAT; p++) {
3335                     collective = 1;
3336                     if(!strcmp("kibitzes", chatPartner[p])) {
3337                         talker[0] = '['; strcat(talker, "] ");
3338                         chattingPartner = p; break;
3339                     }
3340                 } else
3341                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3342                 for(p=0; p<MAX_CHAT; p++) {
3343                     collective = 1;
3344                     if(!strcmp("whispers", chatPartner[p])) {
3345                         talker[0] = '['; strcat(talker, "] ");
3346                         chattingPartner = p; break;
3347                     }
3348                 } else
3349                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3350                   if(buf[i-8] == '-' && buf[i-3] == 't')
3351                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3352                     collective = 1;
3353                     if(!strcmp("c-shouts", chatPartner[p])) {
3354                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3355                         chattingPartner = p; break;
3356                     }
3357                   }
3358                   if(chattingPartner < 0)
3359                   for(p=0; p<MAX_CHAT; p++) {
3360                     collective = 1;
3361                     if(!strcmp("shouts", chatPartner[p])) {
3362                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3363                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3364                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3365                         chattingPartner = p; break;
3366                     }
3367                   }
3368                 }
3369                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3370                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3371                     talker[0] = 0;
3372                     Colorize(ColorTell, FALSE);
3373                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3374                     collective |= 2;
3375                     chattingPartner = p; break;
3376                 }
3377                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3378                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3379                     started = STARTED_COMMENT;
3380                     parse_pos = 0; parse[0] = NULLCHAR;
3381                     savingComment = 3 + chattingPartner; // counts as TRUE
3382                     if(collective == 3) i = oldi; else {
3383                         suppressKibitz = TRUE;
3384                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3385                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3386                         continue;
3387                     }
3388                 }
3389             } // [HGM] chat: end of patch
3390
3391           backup = i;
3392             if (appData.zippyTalk || appData.zippyPlay) {
3393                 /* [DM] Backup address for color zippy lines */
3394 #if ZIPPY
3395                if (loggedOn == TRUE)
3396                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3397                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3398 #endif
3399             } // [DM] 'else { ' deleted
3400                 if (
3401                     /* Regular tells and says */
3402                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3403                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3404                     looking_at(buf, &i, "* says: ") ||
3405                     /* Don't color "message" or "messages" output */
3406                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3407                     looking_at(buf, &i, "*. * at *:*: ") ||
3408                     looking_at(buf, &i, "--* (*:*): ") ||
3409                     /* Message notifications (same color as tells) */
3410                     looking_at(buf, &i, "* has left a message ") ||
3411                     looking_at(buf, &i, "* just sent you a message:\n") ||
3412                     /* Whispers and kibitzes */
3413                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3414                     looking_at(buf, &i, "* kibitzes: ") ||
3415                     /* Channel tells */
3416                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3417
3418                   if (tkind == 1 && strchr(star_match[0], ':')) {
3419                       /* Avoid "tells you:" spoofs in channels */
3420                      tkind = 3;
3421                   }
3422                   if (star_match[0][0] == NULLCHAR ||
3423                       strchr(star_match[0], ' ') ||
3424                       (tkind == 3 && strchr(star_match[1], ' '))) {
3425                     /* Reject bogus matches */
3426                     i = oldi;
3427                   } else {
3428                     if (appData.colorize) {
3429                       if (oldi > next_out) {
3430                         SendToPlayer(&buf[next_out], oldi - next_out);
3431                         next_out = oldi;
3432                       }
3433                       switch (tkind) {
3434                       case 1:
3435                         Colorize(ColorTell, FALSE);
3436                         curColor = ColorTell;
3437                         break;
3438                       case 2:
3439                         Colorize(ColorKibitz, FALSE);
3440                         curColor = ColorKibitz;
3441                         break;
3442                       case 3:
3443                         p = strrchr(star_match[1], '(');
3444                         if (p == NULL) {
3445                           p = star_match[1];
3446                         } else {
3447                           p++;
3448                         }
3449                         if (atoi(p) == 1) {
3450                           Colorize(ColorChannel1, FALSE);
3451                           curColor = ColorChannel1;
3452                         } else {
3453                           Colorize(ColorChannel, FALSE);
3454                           curColor = ColorChannel;
3455                         }
3456                         break;
3457                       case 5:
3458                         curColor = ColorNormal;
3459                         break;
3460                       }
3461                     }
3462                     if (started == STARTED_NONE && appData.autoComment &&
3463                         (gameMode == IcsObserving ||
3464                          gameMode == IcsPlayingWhite ||
3465                          gameMode == IcsPlayingBlack)) {
3466                       parse_pos = i - oldi;
3467                       memcpy(parse, &buf[oldi], parse_pos);
3468                       parse[parse_pos] = NULLCHAR;
3469                       started = STARTED_COMMENT;
3470                       savingComment = TRUE;
3471                     } else if(collective != 3) {
3472                       started = STARTED_CHATTER;
3473                       savingComment = FALSE;
3474                     }
3475                     loggedOn = TRUE;
3476                     continue;
3477                   }
3478                 }
3479
3480                 if (looking_at(buf, &i, "* s-shouts: ") ||
3481                     looking_at(buf, &i, "* c-shouts: ")) {
3482                     if (appData.colorize) {
3483                         if (oldi > next_out) {
3484                             SendToPlayer(&buf[next_out], oldi - next_out);
3485                             next_out = oldi;
3486                         }
3487                         Colorize(ColorSShout, FALSE);
3488                         curColor = ColorSShout;
3489                     }
3490                     loggedOn = TRUE;
3491                     started = STARTED_CHATTER;
3492                     continue;
3493                 }
3494
3495                 if (looking_at(buf, &i, "--->")) {
3496                     loggedOn = TRUE;
3497                     continue;
3498                 }
3499
3500                 if (looking_at(buf, &i, "* shouts: ") ||
3501                     looking_at(buf, &i, "--> ")) {
3502                     if (appData.colorize) {
3503                         if (oldi > next_out) {
3504                             SendToPlayer(&buf[next_out], oldi - next_out);
3505                             next_out = oldi;
3506                         }
3507                         Colorize(ColorShout, FALSE);
3508                         curColor = ColorShout;
3509                     }
3510                     loggedOn = TRUE;
3511                     started = STARTED_CHATTER;
3512                     continue;
3513                 }
3514
3515                 if (looking_at( buf, &i, "Challenge:")) {
3516                     if (appData.colorize) {
3517                         if (oldi > next_out) {
3518                             SendToPlayer(&buf[next_out], oldi - next_out);
3519                             next_out = oldi;
3520                         }
3521                         Colorize(ColorChallenge, FALSE);
3522                         curColor = ColorChallenge;
3523                     }
3524                     loggedOn = TRUE;
3525                     continue;
3526                 }
3527
3528                 if (looking_at(buf, &i, "* offers you") ||
3529                     looking_at(buf, &i, "* offers to be") ||
3530                     looking_at(buf, &i, "* would like to") ||
3531                     looking_at(buf, &i, "* requests to") ||
3532                     looking_at(buf, &i, "Your opponent offers") ||
3533                     looking_at(buf, &i, "Your opponent requests")) {
3534
3535                     if (appData.colorize) {
3536                         if (oldi > next_out) {
3537                             SendToPlayer(&buf[next_out], oldi - next_out);
3538                             next_out = oldi;
3539                         }
3540                         Colorize(ColorRequest, FALSE);
3541                         curColor = ColorRequest;
3542                     }
3543                     continue;
3544                 }
3545
3546                 if (looking_at(buf, &i, "* (*) seeking")) {
3547                     if (appData.colorize) {
3548                         if (oldi > next_out) {
3549                             SendToPlayer(&buf[next_out], oldi - next_out);
3550                             next_out = oldi;
3551                         }
3552                         Colorize(ColorSeek, FALSE);
3553                         curColor = ColorSeek;
3554                     }
3555                     continue;
3556             }
3557
3558           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3559
3560             if (looking_at(buf, &i, "\\   ")) {
3561                 if (prevColor != ColorNormal) {
3562                     if (oldi > next_out) {
3563                         SendToPlayer(&buf[next_out], oldi - next_out);
3564                         next_out = oldi;
3565                     }
3566                     Colorize(prevColor, TRUE);
3567                     curColor = prevColor;
3568                 }
3569                 if (savingComment) {
3570                     parse_pos = i - oldi;
3571                     memcpy(parse, &buf[oldi], parse_pos);
3572                     parse[parse_pos] = NULLCHAR;
3573                     started = STARTED_COMMENT;
3574                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3575                         chattingPartner = savingComment - 3; // kludge to remember the box
3576                 } else {
3577                     started = STARTED_CHATTER;
3578                 }
3579                 continue;
3580             }
3581
3582             if (looking_at(buf, &i, "Black Strength :") ||
3583                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3584                 looking_at(buf, &i, "<10>") ||
3585                 looking_at(buf, &i, "#@#")) {
3586                 /* Wrong board style */
3587                 loggedOn = TRUE;
3588                 SendToICS(ics_prefix);
3589                 SendToICS("set style 12\n");
3590                 SendToICS(ics_prefix);
3591                 SendToICS("refresh\n");
3592                 continue;
3593             }
3594
3595             if (looking_at(buf, &i, "login:")) {
3596               if (!have_sent_ICS_logon) {
3597                 if(ICSInitScript())
3598                   have_sent_ICS_logon = 1;
3599                 else // no init script was found
3600                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3601               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3602                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3603               }
3604                 continue;
3605             }
3606
3607             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3608                 (looking_at(buf, &i, "\n<12> ") ||
3609                  looking_at(buf, &i, "<12> "))) {
3610                 loggedOn = TRUE;
3611                 if (oldi > next_out) {
3612                     SendToPlayer(&buf[next_out], oldi - next_out);
3613                 }
3614                 next_out = i;
3615                 started = STARTED_BOARD;
3616                 parse_pos = 0;
3617                 continue;
3618             }
3619
3620             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3621                 looking_at(buf, &i, "<b1> ")) {
3622                 if (oldi > next_out) {
3623                     SendToPlayer(&buf[next_out], oldi - next_out);
3624                 }
3625                 next_out = i;
3626                 started = STARTED_HOLDINGS;
3627                 parse_pos = 0;
3628                 continue;
3629             }
3630
3631             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3632                 loggedOn = TRUE;
3633                 /* Header for a move list -- first line */
3634
3635                 switch (ics_getting_history) {
3636                   case H_FALSE:
3637                     switch (gameMode) {
3638                       case IcsIdle:
3639                       case BeginningOfGame:
3640                         /* User typed "moves" or "oldmoves" while we
3641                            were idle.  Pretend we asked for these
3642                            moves and soak them up so user can step
3643                            through them and/or save them.
3644                            */
3645                         Reset(FALSE, TRUE);
3646                         gameMode = IcsObserving;
3647                         ModeHighlight();
3648                         ics_gamenum = -1;
3649                         ics_getting_history = H_GOT_UNREQ_HEADER;
3650                         break;
3651                       case EditGame: /*?*/
3652                       case EditPosition: /*?*/
3653                         /* Should above feature work in these modes too? */
3654                         /* For now it doesn't */
3655                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3656                         break;
3657                       default:
3658                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3659                         break;
3660                     }
3661                     break;
3662                   case H_REQUESTED:
3663                     /* Is this the right one? */
3664                     if (gameInfo.white && gameInfo.black &&
3665                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3666                         strcmp(gameInfo.black, star_match[2]) == 0) {
3667                         /* All is well */
3668                         ics_getting_history = H_GOT_REQ_HEADER;
3669                     }
3670                     break;
3671                   case H_GOT_REQ_HEADER:
3672                   case H_GOT_UNREQ_HEADER:
3673                   case H_GOT_UNWANTED_HEADER:
3674                   case H_GETTING_MOVES:
3675                     /* Should not happen */
3676                     DisplayError(_("Error gathering move list: two headers"), 0);
3677                     ics_getting_history = H_FALSE;
3678                     break;
3679                 }
3680
3681                 /* Save player ratings into gameInfo if needed */
3682                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3683                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3684                     (gameInfo.whiteRating == -1 ||
3685                      gameInfo.blackRating == -1)) {
3686
3687                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3688                     gameInfo.blackRating = string_to_rating(star_match[3]);
3689                     if (appData.debugMode)
3690                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3691                               gameInfo.whiteRating, gameInfo.blackRating);
3692                 }
3693                 continue;
3694             }
3695
3696             if (looking_at(buf, &i,
3697               "* * match, initial time: * minute*, increment: * second")) {
3698                 /* Header for a move list -- second line */
3699                 /* Initial board will follow if this is a wild game */
3700                 if (gameInfo.event != NULL) free(gameInfo.event);
3701                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3702                 gameInfo.event = StrSave(str);
3703                 /* [HGM] we switched variant. Translate boards if needed. */
3704                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3705                 continue;
3706             }
3707
3708             if (looking_at(buf, &i, "Move  ")) {
3709                 /* Beginning of a move list */
3710                 switch (ics_getting_history) {
3711                   case H_FALSE:
3712                     /* Normally should not happen */
3713                     /* Maybe user hit reset while we were parsing */
3714                     break;
3715                   case H_REQUESTED:
3716                     /* Happens if we are ignoring a move list that is not
3717                      * the one we just requested.  Common if the user
3718                      * tries to observe two games without turning off
3719                      * getMoveList */
3720                     break;
3721                   case H_GETTING_MOVES:
3722                     /* Should not happen */
3723                     DisplayError(_("Error gathering move list: nested"), 0);
3724                     ics_getting_history = H_FALSE;
3725                     break;
3726                   case H_GOT_REQ_HEADER:
3727                     ics_getting_history = H_GETTING_MOVES;
3728                     started = STARTED_MOVES;
3729                     parse_pos = 0;
3730                     if (oldi > next_out) {
3731                         SendToPlayer(&buf[next_out], oldi - next_out);
3732                     }
3733                     break;
3734                   case H_GOT_UNREQ_HEADER:
3735                     ics_getting_history = H_GETTING_MOVES;
3736                     started = STARTED_MOVES_NOHIDE;
3737                     parse_pos = 0;
3738                     break;
3739                   case H_GOT_UNWANTED_HEADER:
3740                     ics_getting_history = H_FALSE;
3741                     break;
3742                 }
3743                 continue;
3744             }
3745
3746             if (looking_at(buf, &i, "% ") ||
3747                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3748                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3749                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3750                     soughtPending = FALSE;
3751                     seekGraphUp = TRUE;
3752                     DrawSeekGraph();
3753                 }
3754                 if(suppressKibitz) next_out = i;
3755                 savingComment = FALSE;
3756                 suppressKibitz = 0;
3757                 switch (started) {
3758                   case STARTED_MOVES:
3759                   case STARTED_MOVES_NOHIDE:
3760                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3761                     parse[parse_pos + i - oldi] = NULLCHAR;
3762                     ParseGameHistory(parse);
3763 #if ZIPPY
3764                     if (appData.zippyPlay && first.initDone) {
3765                         FeedMovesToProgram(&first, forwardMostMove);
3766                         if (gameMode == IcsPlayingWhite) {
3767                             if (WhiteOnMove(forwardMostMove)) {
3768                                 if (first.sendTime) {
3769                                   if (first.useColors) {
3770                                     SendToProgram("black\n", &first);
3771                                   }
3772                                   SendTimeRemaining(&first, TRUE);
3773                                 }
3774                                 if (first.useColors) {
3775                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3776                                 }
3777                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3778                                 first.maybeThinking = TRUE;
3779                             } else {
3780                                 if (first.usePlayother) {
3781                                   if (first.sendTime) {
3782                                     SendTimeRemaining(&first, TRUE);
3783                                   }
3784                                   SendToProgram("playother\n", &first);
3785                                   firstMove = FALSE;
3786                                 } else {
3787                                   firstMove = TRUE;
3788                                 }
3789                             }
3790                         } else if (gameMode == IcsPlayingBlack) {
3791                             if (!WhiteOnMove(forwardMostMove)) {
3792                                 if (first.sendTime) {
3793                                   if (first.useColors) {
3794                                     SendToProgram("white\n", &first);
3795                                   }
3796                                   SendTimeRemaining(&first, FALSE);
3797                                 }
3798                                 if (first.useColors) {
3799                                   SendToProgram("black\n", &first);
3800                                 }
3801                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3802                                 first.maybeThinking = TRUE;
3803                             } else {
3804                                 if (first.usePlayother) {
3805                                   if (first.sendTime) {
3806                                     SendTimeRemaining(&first, FALSE);
3807                                   }
3808                                   SendToProgram("playother\n", &first);
3809                                   firstMove = FALSE;
3810                                 } else {
3811                                   firstMove = TRUE;
3812                                 }
3813                             }
3814                         }
3815                     }
3816 #endif
3817                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3818                         /* Moves came from oldmoves or moves command
3819                            while we weren't doing anything else.
3820                            */
3821                         currentMove = forwardMostMove;
3822                         ClearHighlights();/*!!could figure this out*/
3823                         flipView = appData.flipView;
3824                         DrawPosition(TRUE, boards[currentMove]);
3825                         DisplayBothClocks();
3826                         snprintf(str, MSG_SIZ, "%s %s %s",
3827                                 gameInfo.white, _("vs."),  gameInfo.black);
3828                         DisplayTitle(str);
3829                         gameMode = IcsIdle;
3830                     } else {
3831                         /* Moves were history of an active game */
3832                         if (gameInfo.resultDetails != NULL) {
3833                             free(gameInfo.resultDetails);
3834                             gameInfo.resultDetails = NULL;
3835                         }
3836                     }
3837                     HistorySet(parseList, backwardMostMove,
3838                                forwardMostMove, currentMove-1);
3839                     DisplayMove(currentMove - 1);
3840                     if (started == STARTED_MOVES) next_out = i;
3841                     started = STARTED_NONE;
3842                     ics_getting_history = H_FALSE;
3843                     break;
3844
3845                   case STARTED_OBSERVE:
3846                     started = STARTED_NONE;
3847                     SendToICS(ics_prefix);
3848                     SendToICS("refresh\n");
3849                     break;
3850
3851                   default:
3852                     break;
3853                 }
3854                 if(bookHit) { // [HGM] book: simulate book reply
3855                     static char bookMove[MSG_SIZ]; // a bit generous?
3856
3857                     programStats.nodes = programStats.depth = programStats.time =
3858                     programStats.score = programStats.got_only_move = 0;
3859                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3860
3861                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3862                     strcat(bookMove, bookHit);
3863                     HandleMachineMove(bookMove, &first);
3864                 }
3865                 continue;
3866             }
3867
3868             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3869                  started == STARTED_HOLDINGS ||
3870                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3871                 /* Accumulate characters in move list or board */
3872                 parse[parse_pos++] = buf[i];
3873             }
3874
3875             /* Start of game messages.  Mostly we detect start of game
3876                when the first board image arrives.  On some versions
3877                of the ICS, though, we need to do a "refresh" after starting
3878                to observe in order to get the current board right away. */
3879             if (looking_at(buf, &i, "Adding game * to observation list")) {
3880                 started = STARTED_OBSERVE;
3881                 continue;
3882             }
3883
3884             /* Handle auto-observe */
3885             if (appData.autoObserve &&
3886                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3887                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3888                 char *player;
3889                 /* Choose the player that was highlighted, if any. */
3890                 if (star_match[0][0] == '\033' ||
3891                     star_match[1][0] != '\033') {
3892                     player = star_match[0];
3893                 } else {
3894                     player = star_match[2];
3895                 }
3896                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3897                         ics_prefix, StripHighlightAndTitle(player));
3898                 SendToICS(str);
3899
3900                 /* Save ratings from notify string */
3901                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3902                 player1Rating = string_to_rating(star_match[1]);
3903                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3904                 player2Rating = string_to_rating(star_match[3]);
3905
3906                 if (appData.debugMode)
3907                   fprintf(debugFP,
3908                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3909                           player1Name, player1Rating,
3910                           player2Name, player2Rating);
3911
3912                 continue;
3913             }
3914
3915             /* Deal with automatic examine mode after a game,
3916                and with IcsObserving -> IcsExamining transition */
3917             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3918                 looking_at(buf, &i, "has made you an examiner of game *")) {
3919
3920                 int gamenum = atoi(star_match[0]);
3921                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3922                     gamenum == ics_gamenum) {
3923                     /* We were already playing or observing this game;
3924                        no need to refetch history */
3925                     gameMode = IcsExamining;
3926                     if (pausing) {
3927                         pauseExamForwardMostMove = forwardMostMove;
3928                     } else if (currentMove < forwardMostMove) {
3929                         ForwardInner(forwardMostMove);
3930                     }
3931                 } else {
3932                     /* I don't think this case really can happen */
3933                     SendToICS(ics_prefix);
3934                     SendToICS("refresh\n");
3935                 }
3936                 continue;
3937             }
3938
3939             /* Error messages */
3940 //          if (ics_user_moved) {
3941             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3942                 if (looking_at(buf, &i, "Illegal move") ||
3943                     looking_at(buf, &i, "Not a legal move") ||
3944                     looking_at(buf, &i, "Your king is in check") ||
3945                     looking_at(buf, &i, "It isn't your turn") ||
3946                     looking_at(buf, &i, "It is not your move")) {
3947                     /* Illegal move */
3948                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3949                         currentMove = forwardMostMove-1;
3950                         DisplayMove(currentMove - 1); /* before DMError */
3951                         DrawPosition(FALSE, boards[currentMove]);
3952                         SwitchClocks(forwardMostMove-1); // [HGM] race
3953                         DisplayBothClocks();
3954                     }
3955                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3956                     ics_user_moved = 0;
3957                     continue;
3958                 }
3959             }
3960
3961             if (looking_at(buf, &i, "still have time") ||
3962                 looking_at(buf, &i, "not out of time") ||
3963                 looking_at(buf, &i, "either player is out of time") ||
3964                 looking_at(buf, &i, "has timeseal; checking")) {
3965                 /* We must have called his flag a little too soon */
3966                 whiteFlag = blackFlag = FALSE;
3967                 continue;
3968             }
3969
3970             if (looking_at(buf, &i, "added * seconds to") ||
3971                 looking_at(buf, &i, "seconds were added to")) {
3972                 /* Update the clocks */
3973                 SendToICS(ics_prefix);
3974                 SendToICS("refresh\n");
3975                 continue;
3976             }
3977
3978             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3979                 ics_clock_paused = TRUE;
3980                 StopClocks();
3981                 continue;
3982             }
3983
3984             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3985                 ics_clock_paused = FALSE;
3986                 StartClocks();
3987                 continue;
3988             }
3989
3990             /* Grab player ratings from the Creating: message.
3991                Note we have to check for the special case when
3992                the ICS inserts things like [white] or [black]. */
3993             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3994                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3995                 /* star_matches:
3996                    0    player 1 name (not necessarily white)
3997                    1    player 1 rating
3998                    2    empty, white, or black (IGNORED)
3999                    3    player 2 name (not necessarily black)
4000                    4    player 2 rating
4001
4002                    The names/ratings are sorted out when the game
4003                    actually starts (below).
4004                 */
4005                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4006                 player1Rating = string_to_rating(star_match[1]);
4007                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4008                 player2Rating = string_to_rating(star_match[4]);
4009
4010                 if (appData.debugMode)
4011                   fprintf(debugFP,
4012                           "Ratings from 'Creating:' %s %d, %s %d\n",
4013                           player1Name, player1Rating,
4014                           player2Name, player2Rating);
4015
4016                 continue;
4017             }
4018
4019             /* Improved generic start/end-of-game messages */
4020             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4021                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4022                 /* If tkind == 0: */
4023                 /* star_match[0] is the game number */
4024                 /*           [1] is the white player's name */
4025                 /*           [2] is the black player's name */
4026                 /* For end-of-game: */
4027                 /*           [3] is the reason for the game end */
4028                 /*           [4] is a PGN end game-token, preceded by " " */
4029                 /* For start-of-game: */
4030                 /*           [3] begins with "Creating" or "Continuing" */
4031                 /*           [4] is " *" or empty (don't care). */
4032                 int gamenum = atoi(star_match[0]);
4033                 char *whitename, *blackname, *why, *endtoken;
4034                 ChessMove endtype = EndOfFile;
4035
4036                 if (tkind == 0) {
4037                   whitename = star_match[1];
4038                   blackname = star_match[2];
4039                   why = star_match[3];
4040                   endtoken = star_match[4];
4041                 } else {
4042                   whitename = star_match[1];
4043                   blackname = star_match[3];
4044                   why = star_match[5];
4045                   endtoken = star_match[6];
4046                 }
4047
4048                 /* Game start messages */
4049                 if (strncmp(why, "Creating ", 9) == 0 ||
4050                     strncmp(why, "Continuing ", 11) == 0) {
4051                     gs_gamenum = gamenum;
4052                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4053                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4054                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4055 #if ZIPPY
4056                     if (appData.zippyPlay) {
4057                         ZippyGameStart(whitename, blackname);
4058                     }
4059 #endif /*ZIPPY*/
4060                     partnerBoardValid = FALSE; // [HGM] bughouse
4061                     continue;
4062                 }
4063
4064                 /* Game end messages */
4065                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4066                     ics_gamenum != gamenum) {
4067                     continue;
4068                 }
4069                 while (endtoken[0] == ' ') endtoken++;
4070                 switch (endtoken[0]) {
4071                   case '*':
4072                   default:
4073                     endtype = GameUnfinished;
4074                     break;
4075                   case '0':
4076                     endtype = BlackWins;
4077                     break;
4078                   case '1':
4079                     if (endtoken[1] == '/')
4080                       endtype = GameIsDrawn;
4081                     else
4082                       endtype = WhiteWins;
4083                     break;
4084                 }
4085                 GameEnds(endtype, why, GE_ICS);
4086 #if ZIPPY
4087                 if (appData.zippyPlay && first.initDone) {
4088                     ZippyGameEnd(endtype, why);
4089                     if (first.pr == NoProc) {
4090                       /* Start the next process early so that we'll
4091                          be ready for the next challenge */
4092                       StartChessProgram(&first);
4093                     }
4094                     /* Send "new" early, in case this command takes
4095                        a long time to finish, so that we'll be ready
4096                        for the next challenge. */
4097                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4098                     Reset(TRUE, TRUE);
4099                 }
4100 #endif /*ZIPPY*/
4101                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4102                 continue;
4103             }
4104
4105             if (looking_at(buf, &i, "Removing game * from observation") ||
4106                 looking_at(buf, &i, "no longer observing game *") ||
4107                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4108                 if (gameMode == IcsObserving &&
4109                     atoi(star_match[0]) == ics_gamenum)
4110                   {
4111                       /* icsEngineAnalyze */
4112                       if (appData.icsEngineAnalyze) {
4113                             ExitAnalyzeMode();
4114                             ModeHighlight();
4115                       }
4116                       StopClocks();
4117                       gameMode = IcsIdle;
4118                       ics_gamenum = -1;
4119                       ics_user_moved = FALSE;
4120                   }
4121                 continue;
4122             }
4123
4124             if (looking_at(buf, &i, "no longer examining game *")) {
4125                 if (gameMode == IcsExamining &&
4126                     atoi(star_match[0]) == ics_gamenum)
4127                   {
4128                       gameMode = IcsIdle;
4129                       ics_gamenum = -1;
4130                       ics_user_moved = FALSE;
4131                   }
4132                 continue;
4133             }
4134
4135             /* Advance leftover_start past any newlines we find,
4136                so only partial lines can get reparsed */
4137             if (looking_at(buf, &i, "\n")) {
4138                 prevColor = curColor;
4139                 if (curColor != ColorNormal) {
4140                     if (oldi > next_out) {
4141                         SendToPlayer(&buf[next_out], oldi - next_out);
4142                         next_out = oldi;
4143                     }
4144                     Colorize(ColorNormal, FALSE);
4145                     curColor = ColorNormal;
4146                 }
4147                 if (started == STARTED_BOARD) {
4148                     started = STARTED_NONE;
4149                     parse[parse_pos] = NULLCHAR;
4150                     ParseBoard12(parse);
4151                     ics_user_moved = 0;
4152
4153                     /* Send premove here */
4154                     if (appData.premove) {
4155                       char str[MSG_SIZ];
4156                       if (currentMove == 0 &&
4157                           gameMode == IcsPlayingWhite &&
4158                           appData.premoveWhite) {
4159                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4160                         if (appData.debugMode)
4161                           fprintf(debugFP, "Sending premove:\n");
4162                         SendToICS(str);
4163                       } else if (currentMove == 1 &&
4164                                  gameMode == IcsPlayingBlack &&
4165                                  appData.premoveBlack) {
4166                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4167                         if (appData.debugMode)
4168                           fprintf(debugFP, "Sending premove:\n");
4169                         SendToICS(str);
4170                       } else if (gotPremove) {
4171                         int oldFMM = forwardMostMove;
4172                         gotPremove = 0;
4173                         ClearPremoveHighlights();
4174                         if (appData.debugMode)
4175                           fprintf(debugFP, "Sending premove:\n");
4176                           UserMoveEvent(premoveFromX, premoveFromY,
4177                                         premoveToX, premoveToY,
4178                                         premovePromoChar);
4179                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4180                           if(moveList[oldFMM-1][1] != '@')
4181                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4182                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4183                           else // (drop)
4184                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4185                         }
4186                       }
4187                     }
4188
4189                     /* Usually suppress following prompt */
4190                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4191                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4192                         if (looking_at(buf, &i, "*% ")) {
4193                             savingComment = FALSE;
4194                             suppressKibitz = 0;
4195                         }
4196                     }
4197                     next_out = i;
4198                 } else if (started == STARTED_HOLDINGS) {
4199                     int gamenum;
4200                     char new_piece[MSG_SIZ];
4201                     started = STARTED_NONE;
4202                     parse[parse_pos] = NULLCHAR;
4203                     if (appData.debugMode)
4204                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4205                                                         parse, currentMove);
4206                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4207                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4208                         if (gameInfo.variant == VariantNormal) {
4209                           /* [HGM] We seem to switch variant during a game!
4210                            * Presumably no holdings were displayed, so we have
4211                            * to move the position two files to the right to
4212                            * create room for them!
4213                            */
4214                           VariantClass newVariant;
4215                           switch(gameInfo.boardWidth) { // base guess on board width
4216                                 case 9:  newVariant = VariantShogi; break;
4217                                 case 10: newVariant = VariantGreat; break;
4218                                 default: newVariant = VariantCrazyhouse; break;
4219                           }
4220                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4221                           /* Get a move list just to see the header, which
4222                              will tell us whether this is really bug or zh */
4223                           if (ics_getting_history == H_FALSE) {
4224                             ics_getting_history = H_REQUESTED;
4225                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4226                             SendToICS(str);
4227                           }
4228                         }
4229                         new_piece[0] = NULLCHAR;
4230                         sscanf(parse, "game %d white [%s black [%s <- %s",
4231                                &gamenum, white_holding, black_holding,
4232                                new_piece);
4233                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4234                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4235                         /* [HGM] copy holdings to board holdings area */
4236                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4237                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4238                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4239 #if ZIPPY
4240                         if (appData.zippyPlay && first.initDone) {
4241                             ZippyHoldings(white_holding, black_holding,
4242                                           new_piece);
4243                         }
4244 #endif /*ZIPPY*/
4245                         if (tinyLayout || smallLayout) {
4246                             char wh[16], bh[16];
4247                             PackHolding(wh, white_holding);
4248                             PackHolding(bh, black_holding);
4249                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4250                                     gameInfo.white, gameInfo.black);
4251                         } else {
4252                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4253                                     gameInfo.white, white_holding, _("vs."),
4254                                     gameInfo.black, black_holding);
4255                         }
4256                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4257                         DrawPosition(FALSE, boards[currentMove]);
4258                         DisplayTitle(str);
4259                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4260                         sscanf(parse, "game %d white [%s black [%s <- %s",
4261                                &gamenum, white_holding, black_holding,
4262                                new_piece);
4263                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4264                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4265                         /* [HGM] copy holdings to partner-board holdings area */
4266                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4267                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4268                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4269                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4270                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4271                       }
4272                     }
4273                     /* Suppress following prompt */
4274                     if (looking_at(buf, &i, "*% ")) {
4275                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4276                         savingComment = FALSE;
4277                         suppressKibitz = 0;
4278                     }
4279                     next_out = i;
4280                 }
4281                 continue;
4282             }
4283
4284             i++;                /* skip unparsed character and loop back */
4285         }
4286
4287         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4288 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4289 //          SendToPlayer(&buf[next_out], i - next_out);
4290             started != STARTED_HOLDINGS && leftover_start > next_out) {
4291             SendToPlayer(&buf[next_out], leftover_start - next_out);
4292             next_out = i;
4293         }
4294
4295         leftover_len = buf_len - leftover_start;
4296         /* if buffer ends with something we couldn't parse,
4297            reparse it after appending the next read */
4298
4299     } else if (count == 0) {
4300         RemoveInputSource(isr);
4301         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4302     } else {
4303         DisplayFatalError(_("Error reading from ICS"), error, 1);
4304     }
4305 }
4306
4307
4308 /* Board style 12 looks like this:
4309
4310    <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
4311
4312  * The "<12> " is stripped before it gets to this routine.  The two
4313  * trailing 0's (flip state and clock ticking) are later addition, and
4314  * some chess servers may not have them, or may have only the first.
4315  * Additional trailing fields may be added in the future.
4316  */
4317
4318 #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"
4319
4320 #define RELATION_OBSERVING_PLAYED    0
4321 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4322 #define RELATION_PLAYING_MYMOVE      1
4323 #define RELATION_PLAYING_NOTMYMOVE  -1
4324 #define RELATION_EXAMINING           2
4325 #define RELATION_ISOLATED_BOARD     -3
4326 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4327
4328 void
4329 ParseBoard12 (char *string)
4330 {
4331 #if ZIPPY
4332     int i, takeback;
4333     char *bookHit = NULL; // [HGM] book
4334 #endif
4335     GameMode newGameMode;
4336     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4337     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4338     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4339     char to_play, board_chars[200];
4340     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4341     char black[32], white[32];
4342     Board board;
4343     int prevMove = currentMove;
4344     int ticking = 2;
4345     ChessMove moveType;
4346     int fromX, fromY, toX, toY;
4347     char promoChar;
4348     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4349     Boolean weird = FALSE, reqFlag = FALSE;
4350
4351     fromX = fromY = toX = toY = -1;
4352
4353     newGame = FALSE;
4354
4355     if (appData.debugMode)
4356       fprintf(debugFP, "Parsing board: %s\n", string);
4357
4358     move_str[0] = NULLCHAR;
4359     elapsed_time[0] = NULLCHAR;
4360     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4361         int  i = 0, j;
4362         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4363             if(string[i] == ' ') { ranks++; files = 0; }
4364             else files++;
4365             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4366             i++;
4367         }
4368         for(j = 0; j <i; j++) board_chars[j] = string[j];
4369         board_chars[i] = '\0';
4370         string += i + 1;
4371     }
4372     n = sscanf(string, PATTERN, &to_play, &double_push,
4373                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4374                &gamenum, white, black, &relation, &basetime, &increment,
4375                &white_stren, &black_stren, &white_time, &black_time,
4376                &moveNum, str, elapsed_time, move_str, &ics_flip,
4377                &ticking);
4378
4379     if (n < 21) {
4380         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4381         DisplayError(str, 0);
4382         return;
4383     }
4384
4385     /* Convert the move number to internal form */
4386     moveNum = (moveNum - 1) * 2;
4387     if (to_play == 'B') moveNum++;
4388     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4389       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4390                         0, 1);
4391       return;
4392     }
4393
4394     switch (relation) {
4395       case RELATION_OBSERVING_PLAYED:
4396       case RELATION_OBSERVING_STATIC:
4397         if (gamenum == -1) {
4398             /* Old ICC buglet */
4399             relation = RELATION_OBSERVING_STATIC;
4400         }
4401         newGameMode = IcsObserving;
4402         break;
4403       case RELATION_PLAYING_MYMOVE:
4404       case RELATION_PLAYING_NOTMYMOVE:
4405         newGameMode =
4406           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4407             IcsPlayingWhite : IcsPlayingBlack;
4408         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4409         break;
4410       case RELATION_EXAMINING:
4411         newGameMode = IcsExamining;
4412         break;
4413       case RELATION_ISOLATED_BOARD:
4414       default:
4415         /* Just display this board.  If user was doing something else,
4416            we will forget about it until the next board comes. */
4417         newGameMode = IcsIdle;
4418         break;
4419       case RELATION_STARTING_POSITION:
4420         newGameMode = gameMode;
4421         break;
4422     }
4423
4424     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4425         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4426          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4427       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4428       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4429       static int lastBgGame = -1;
4430       char *toSqr;
4431       for (k = 0; k < ranks; k++) {
4432         for (j = 0; j < files; j++)
4433           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4434         if(gameInfo.holdingsWidth > 1) {
4435              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4436              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4437         }
4438       }
4439       CopyBoard(partnerBoard, board);
4440       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4441         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4442         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4443       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4444       if(toSqr = strchr(str, '-')) {
4445         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4446         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4447       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4448       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4449       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4450       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4451       if(twoBoards) {
4452           DisplayWhiteClock(white_time*fac, to_play == 'W');
4453           DisplayBlackClock(black_time*fac, to_play != 'W');
4454           activePartner = to_play;
4455           if(gamenum != lastBgGame) {
4456               char buf[MSG_SIZ];
4457               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4458               DisplayTitle(buf);
4459           }
4460           lastBgGame = gamenum;
4461           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4462                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4463       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4464                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4465       if(!twoBoards) DisplayMessage(partnerStatus, "");
4466         partnerBoardValid = TRUE;
4467       return;
4468     }
4469
4470     if(appData.dualBoard && appData.bgObserve) {
4471         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4472             SendToICS(ics_prefix), SendToICS("pobserve\n");
4473         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4474             char buf[MSG_SIZ];
4475             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4476             SendToICS(buf);
4477         }
4478     }
4479
4480     /* Modify behavior for initial board display on move listing
4481        of wild games.
4482        */
4483     switch (ics_getting_history) {
4484       case H_FALSE:
4485       case H_REQUESTED:
4486         break;
4487       case H_GOT_REQ_HEADER:
4488       case H_GOT_UNREQ_HEADER:
4489         /* This is the initial position of the current game */
4490         gamenum = ics_gamenum;
4491         moveNum = 0;            /* old ICS bug workaround */
4492         if (to_play == 'B') {
4493           startedFromSetupPosition = TRUE;
4494           blackPlaysFirst = TRUE;
4495           moveNum = 1;
4496           if (forwardMostMove == 0) forwardMostMove = 1;
4497           if (backwardMostMove == 0) backwardMostMove = 1;
4498           if (currentMove == 0) currentMove = 1;
4499         }
4500         newGameMode = gameMode;
4501         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4502         break;
4503       case H_GOT_UNWANTED_HEADER:
4504         /* This is an initial board that we don't want */
4505         return;
4506       case H_GETTING_MOVES:
4507         /* Should not happen */
4508         DisplayError(_("Error gathering move list: extra board"), 0);
4509         ics_getting_history = H_FALSE;
4510         return;
4511     }
4512
4513    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4514                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4515                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4516      /* [HGM] We seem to have switched variant unexpectedly
4517       * Try to guess new variant from board size
4518       */
4519           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4520           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4521           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4522           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4523           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4524           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4525           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4526           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4527           /* Get a move list just to see the header, which
4528              will tell us whether this is really bug or zh */
4529           if (ics_getting_history == H_FALSE) {
4530             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4531             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4532             SendToICS(str);
4533           }
4534     }
4535
4536     /* Take action if this is the first board of a new game, or of a
4537        different game than is currently being displayed.  */
4538     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4539         relation == RELATION_ISOLATED_BOARD) {
4540
4541         /* Forget the old game and get the history (if any) of the new one */
4542         if (gameMode != BeginningOfGame) {
4543           Reset(TRUE, TRUE);
4544         }
4545         newGame = TRUE;
4546         if (appData.autoRaiseBoard) BoardToTop();
4547         prevMove = -3;
4548         if (gamenum == -1) {
4549             newGameMode = IcsIdle;
4550         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4551                    appData.getMoveList && !reqFlag) {
4552             /* Need to get game history */
4553             ics_getting_history = H_REQUESTED;
4554             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4555             SendToICS(str);
4556         }
4557
4558         /* Initially flip the board to have black on the bottom if playing
4559            black or if the ICS flip flag is set, but let the user change
4560            it with the Flip View button. */
4561         flipView = appData.autoFlipView ?
4562           (newGameMode == IcsPlayingBlack) || ics_flip :
4563           appData.flipView;
4564
4565         /* Done with values from previous mode; copy in new ones */
4566         gameMode = newGameMode;
4567         ModeHighlight();
4568         ics_gamenum = gamenum;
4569         if (gamenum == gs_gamenum) {
4570             int klen = strlen(gs_kind);
4571             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4572             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4573             gameInfo.event = StrSave(str);
4574         } else {
4575             gameInfo.event = StrSave("ICS game");
4576         }
4577         gameInfo.site = StrSave(appData.icsHost);
4578         gameInfo.date = PGNDate();
4579         gameInfo.round = StrSave("-");
4580         gameInfo.white = StrSave(white);
4581         gameInfo.black = StrSave(black);
4582         timeControl = basetime * 60 * 1000;
4583         timeControl_2 = 0;
4584         timeIncrement = increment * 1000;
4585         movesPerSession = 0;
4586         gameInfo.timeControl = TimeControlTagValue();
4587         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4588   if (appData.debugMode) {
4589     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4590     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4591     setbuf(debugFP, NULL);
4592   }
4593
4594         gameInfo.outOfBook = NULL;
4595
4596         /* Do we have the ratings? */
4597         if (strcmp(player1Name, white) == 0 &&
4598             strcmp(player2Name, black) == 0) {
4599             if (appData.debugMode)
4600               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4601                       player1Rating, player2Rating);
4602             gameInfo.whiteRating = player1Rating;
4603             gameInfo.blackRating = player2Rating;
4604         } else if (strcmp(player2Name, white) == 0 &&
4605                    strcmp(player1Name, black) == 0) {
4606             if (appData.debugMode)
4607               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4608                       player2Rating, player1Rating);
4609             gameInfo.whiteRating = player2Rating;
4610             gameInfo.blackRating = player1Rating;
4611         }
4612         player1Name[0] = player2Name[0] = NULLCHAR;
4613
4614         /* Silence shouts if requested */
4615         if (appData.quietPlay &&
4616             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4617             SendToICS(ics_prefix);
4618             SendToICS("set shout 0\n");
4619         }
4620     }
4621
4622     /* Deal with midgame name changes */
4623     if (!newGame) {
4624         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4625             if (gameInfo.white) free(gameInfo.white);
4626             gameInfo.white = StrSave(white);
4627         }
4628         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4629             if (gameInfo.black) free(gameInfo.black);
4630             gameInfo.black = StrSave(black);
4631         }
4632     }
4633
4634     /* Throw away game result if anything actually changes in examine mode */
4635     if (gameMode == IcsExamining && !newGame) {
4636         gameInfo.result = GameUnfinished;
4637         if (gameInfo.resultDetails != NULL) {
4638             free(gameInfo.resultDetails);
4639             gameInfo.resultDetails = NULL;
4640         }
4641     }
4642
4643     /* In pausing && IcsExamining mode, we ignore boards coming
4644        in if they are in a different variation than we are. */
4645     if (pauseExamInvalid) return;
4646     if (pausing && gameMode == IcsExamining) {
4647         if (moveNum <= pauseExamForwardMostMove) {
4648             pauseExamInvalid = TRUE;
4649             forwardMostMove = pauseExamForwardMostMove;
4650             return;
4651         }
4652     }
4653
4654   if (appData.debugMode) {
4655     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4656   }
4657     /* Parse the board */
4658     for (k = 0; k < ranks; k++) {
4659       for (j = 0; j < files; j++)
4660         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4661       if(gameInfo.holdingsWidth > 1) {
4662            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4663            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4664       }
4665     }
4666     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4667       board[5][BOARD_RGHT+1] = WhiteAngel;
4668       board[6][BOARD_RGHT+1] = WhiteMarshall;
4669       board[1][0] = BlackMarshall;
4670       board[2][0] = BlackAngel;
4671       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4672     }
4673     CopyBoard(boards[moveNum], board);
4674     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4675     if (moveNum == 0) {
4676         startedFromSetupPosition =
4677           !CompareBoards(board, initialPosition);
4678         if(startedFromSetupPosition)
4679             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4680     }
4681
4682     /* [HGM] Set castling rights. Take the outermost Rooks,
4683        to make it also work for FRC opening positions. Note that board12
4684        is really defective for later FRC positions, as it has no way to
4685        indicate which Rook can castle if they are on the same side of King.
4686        For the initial position we grant rights to the outermost Rooks,
4687        and remember thos rights, and we then copy them on positions
4688        later in an FRC game. This means WB might not recognize castlings with
4689        Rooks that have moved back to their original position as illegal,
4690        but in ICS mode that is not its job anyway.
4691     */
4692     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4693     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4694
4695         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4696             if(board[0][i] == WhiteRook) j = i;
4697         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4698         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4699             if(board[0][i] == WhiteRook) j = i;
4700         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4701         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4702             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4703         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4704         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4706         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4707
4708         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4709         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4710         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4711             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4712         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4713             if(board[BOARD_HEIGHT-1][k] == bKing)
4714                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4715         if(gameInfo.variant == VariantTwoKings) {
4716             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4717             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4718             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4719         }
4720     } else { int r;
4721         r = boards[moveNum][CASTLING][0] = initialRights[0];
4722         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4723         r = boards[moveNum][CASTLING][1] = initialRights[1];
4724         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4725         r = boards[moveNum][CASTLING][3] = initialRights[3];
4726         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4727         r = boards[moveNum][CASTLING][4] = initialRights[4];
4728         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4729         /* wildcastle kludge: always assume King has rights */
4730         r = boards[moveNum][CASTLING][2] = initialRights[2];
4731         r = boards[moveNum][CASTLING][5] = initialRights[5];
4732     }
4733     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4734     boards[moveNum][EP_STATUS] = EP_NONE;
4735     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4736     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4737     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4738
4739
4740     if (ics_getting_history == H_GOT_REQ_HEADER ||
4741         ics_getting_history == H_GOT_UNREQ_HEADER) {
4742         /* This was an initial position from a move list, not
4743            the current position */
4744         return;
4745     }
4746
4747     /* Update currentMove and known move number limits */
4748     newMove = newGame || moveNum > forwardMostMove;
4749
4750     if (newGame) {
4751         forwardMostMove = backwardMostMove = currentMove = moveNum;
4752         if (gameMode == IcsExamining && moveNum == 0) {
4753           /* Workaround for ICS limitation: we are not told the wild
4754              type when starting to examine a game.  But if we ask for
4755              the move list, the move list header will tell us */
4756             ics_getting_history = H_REQUESTED;
4757             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4758             SendToICS(str);
4759         }
4760     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4761                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4762 #if ZIPPY
4763         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4764         /* [HGM] applied this also to an engine that is silently watching        */
4765         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4766             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4767             gameInfo.variant == currentlyInitializedVariant) {
4768           takeback = forwardMostMove - moveNum;
4769           for (i = 0; i < takeback; i++) {
4770             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4771             SendToProgram("undo\n", &first);
4772           }
4773         }
4774 #endif
4775
4776         forwardMostMove = moveNum;
4777         if (!pausing || currentMove > forwardMostMove)
4778           currentMove = forwardMostMove;
4779     } else {
4780         /* New part of history that is not contiguous with old part */
4781         if (pausing && gameMode == IcsExamining) {
4782             pauseExamInvalid = TRUE;
4783             forwardMostMove = pauseExamForwardMostMove;
4784             return;
4785         }
4786         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4787 #if ZIPPY
4788             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4789                 // [HGM] when we will receive the move list we now request, it will be
4790                 // fed to the engine from the first move on. So if the engine is not
4791                 // in the initial position now, bring it there.
4792                 InitChessProgram(&first, 0);
4793             }
4794 #endif
4795             ics_getting_history = H_REQUESTED;
4796             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4797             SendToICS(str);
4798         }
4799         forwardMostMove = backwardMostMove = currentMove = moveNum;
4800     }
4801
4802     /* Update the clocks */
4803     if (strchr(elapsed_time, '.')) {
4804       /* Time is in ms */
4805       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4806       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4807     } else {
4808       /* Time is in seconds */
4809       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4810       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4811     }
4812
4813
4814 #if ZIPPY
4815     if (appData.zippyPlay && newGame &&
4816         gameMode != IcsObserving && gameMode != IcsIdle &&
4817         gameMode != IcsExamining)
4818       ZippyFirstBoard(moveNum, basetime, increment);
4819 #endif
4820
4821     /* Put the move on the move list, first converting
4822        to canonical algebraic form. */
4823     if (moveNum > 0) {
4824   if (appData.debugMode) {
4825     int f = forwardMostMove;
4826     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4827             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4828             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4829     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4830     fprintf(debugFP, "moveNum = %d\n", moveNum);
4831     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4832     setbuf(debugFP, NULL);
4833   }
4834         if (moveNum <= backwardMostMove) {
4835             /* We don't know what the board looked like before
4836                this move.  Punt. */
4837           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4838             strcat(parseList[moveNum - 1], " ");
4839             strcat(parseList[moveNum - 1], elapsed_time);
4840             moveList[moveNum - 1][0] = NULLCHAR;
4841         } else if (strcmp(move_str, "none") == 0) {
4842             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4843             /* Again, we don't know what the board looked like;
4844                this is really the start of the game. */
4845             parseList[moveNum - 1][0] = NULLCHAR;
4846             moveList[moveNum - 1][0] = NULLCHAR;
4847             backwardMostMove = moveNum;
4848             startedFromSetupPosition = TRUE;
4849             fromX = fromY = toX = toY = -1;
4850         } else {
4851           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4852           //                 So we parse the long-algebraic move string in stead of the SAN move
4853           int valid; char buf[MSG_SIZ], *prom;
4854
4855           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4856                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4857           // str looks something like "Q/a1-a2"; kill the slash
4858           if(str[1] == '/')
4859             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4860           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4861           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4862                 strcat(buf, prom); // long move lacks promo specification!
4863           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4864                 if(appData.debugMode)
4865                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4866                 safeStrCpy(move_str, buf, MSG_SIZ);
4867           }
4868           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4869                                 &fromX, &fromY, &toX, &toY, &promoChar)
4870                || ParseOneMove(buf, moveNum - 1, &moveType,
4871                                 &fromX, &fromY, &toX, &toY, &promoChar);
4872           // end of long SAN patch
4873           if (valid) {
4874             (void) CoordsToAlgebraic(boards[moveNum - 1],
4875                                      PosFlags(moveNum - 1),
4876                                      fromY, fromX, toY, toX, promoChar,
4877                                      parseList[moveNum-1]);
4878             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4879               case MT_NONE:
4880               case MT_STALEMATE:
4881               default:
4882                 break;
4883               case MT_CHECK:
4884                 if(!IS_SHOGI(gameInfo.variant))
4885                     strcat(parseList[moveNum - 1], "+");
4886                 break;
4887               case MT_CHECKMATE:
4888               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4889                 strcat(parseList[moveNum - 1], "#");
4890                 break;
4891             }
4892             strcat(parseList[moveNum - 1], " ");
4893             strcat(parseList[moveNum - 1], elapsed_time);
4894             /* currentMoveString is set as a side-effect of ParseOneMove */
4895             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4896             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4897             strcat(moveList[moveNum - 1], "\n");
4898
4899             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4900                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4901               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4902                 ChessSquare old, new = boards[moveNum][k][j];
4903                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4904                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4905                   if(old == new) continue;
4906                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4907                   else if(new == WhiteWazir || new == BlackWazir) {
4908                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4909                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4910                       else boards[moveNum][k][j] = old; // preserve type of Gold
4911                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4912                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4913               }
4914           } else {
4915             /* Move from ICS was illegal!?  Punt. */
4916             if (appData.debugMode) {
4917               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4918               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4919             }
4920             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4921             strcat(parseList[moveNum - 1], " ");
4922             strcat(parseList[moveNum - 1], elapsed_time);
4923             moveList[moveNum - 1][0] = NULLCHAR;
4924             fromX = fromY = toX = toY = -1;
4925           }
4926         }
4927   if (appData.debugMode) {
4928     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4929     setbuf(debugFP, NULL);
4930   }
4931
4932 #if ZIPPY
4933         /* Send move to chess program (BEFORE animating it). */
4934         if (appData.zippyPlay && !newGame && newMove &&
4935            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4936
4937             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4938                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4939                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4940                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4941                             move_str);
4942                     DisplayError(str, 0);
4943                 } else {
4944                     if (first.sendTime) {
4945                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4946                     }
4947                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4948                     if (firstMove && !bookHit) {
4949                         firstMove = FALSE;
4950                         if (first.useColors) {
4951                           SendToProgram(gameMode == IcsPlayingWhite ?
4952                                         "white\ngo\n" :
4953                                         "black\ngo\n", &first);
4954                         } else {
4955                           SendToProgram("go\n", &first);
4956                         }
4957                         first.maybeThinking = TRUE;
4958                     }
4959                 }
4960             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4961               if (moveList[moveNum - 1][0] == NULLCHAR) {
4962                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4963                 DisplayError(str, 0);
4964               } else {
4965                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4966                 SendMoveToProgram(moveNum - 1, &first);
4967               }
4968             }
4969         }
4970 #endif
4971     }
4972
4973     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4974         /* If move comes from a remote source, animate it.  If it
4975            isn't remote, it will have already been animated. */
4976         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4977             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4978         }
4979         if (!pausing && appData.highlightLastMove) {
4980             SetHighlights(fromX, fromY, toX, toY);
4981         }
4982     }
4983
4984     /* Start the clocks */
4985     whiteFlag = blackFlag = FALSE;
4986     appData.clockMode = !(basetime == 0 && increment == 0);
4987     if (ticking == 0) {
4988       ics_clock_paused = TRUE;
4989       StopClocks();
4990     } else if (ticking == 1) {
4991       ics_clock_paused = FALSE;
4992     }
4993     if (gameMode == IcsIdle ||
4994         relation == RELATION_OBSERVING_STATIC ||
4995         relation == RELATION_EXAMINING ||
4996         ics_clock_paused)
4997       DisplayBothClocks();
4998     else
4999       StartClocks();
5000
5001     /* Display opponents and material strengths */
5002     if (gameInfo.variant != VariantBughouse &&
5003         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5004         if (tinyLayout || smallLayout) {
5005             if(gameInfo.variant == VariantNormal)
5006               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5007                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5008                     basetime, increment);
5009             else
5010               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5011                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5012                     basetime, increment, (int) gameInfo.variant);
5013         } else {
5014             if(gameInfo.variant == VariantNormal)
5015               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5016                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5017                     basetime, increment);
5018             else
5019               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5020                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5021                     basetime, increment, VariantName(gameInfo.variant));
5022         }
5023         DisplayTitle(str);
5024   if (appData.debugMode) {
5025     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5026   }
5027     }
5028
5029
5030     /* Display the board */
5031     if (!pausing && !appData.noGUI) {
5032
5033       if (appData.premove)
5034           if (!gotPremove ||
5035              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5036              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5037               ClearPremoveHighlights();
5038
5039       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5040         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5041       DrawPosition(j, boards[currentMove]);
5042
5043       DisplayMove(moveNum - 1);
5044       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5045             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5046               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5047         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5048       }
5049     }
5050
5051     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5052 #if ZIPPY
5053     if(bookHit) { // [HGM] book: simulate book reply
5054         static char bookMove[MSG_SIZ]; // a bit generous?
5055
5056         programStats.nodes = programStats.depth = programStats.time =
5057         programStats.score = programStats.got_only_move = 0;
5058         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5059
5060         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5061         strcat(bookMove, bookHit);
5062         HandleMachineMove(bookMove, &first);
5063     }
5064 #endif
5065 }
5066
5067 void
5068 GetMoveListEvent ()
5069 {
5070     char buf[MSG_SIZ];
5071     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5072         ics_getting_history = H_REQUESTED;
5073         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5074         SendToICS(buf);
5075     }
5076 }
5077
5078 void
5079 SendToBoth (char *msg)
5080 {   // to make it easy to keep two engines in step in dual analysis
5081     SendToProgram(msg, &first);
5082     if(second.analyzing) SendToProgram(msg, &second);
5083 }
5084
5085 void
5086 AnalysisPeriodicEvent (int force)
5087 {
5088     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5089          && !force) || !appData.periodicUpdates)
5090       return;
5091
5092     /* Send . command to Crafty to collect stats */
5093     SendToBoth(".\n");
5094
5095     /* Don't send another until we get a response (this makes
5096        us stop sending to old Crafty's which don't understand
5097        the "." command (sending illegal cmds resets node count & time,
5098        which looks bad)) */
5099     programStats.ok_to_send = 0;
5100 }
5101
5102 void
5103 ics_update_width (int new_width)
5104 {
5105         ics_printf("set width %d\n", new_width);
5106 }
5107
5108 void
5109 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5110 {
5111     char buf[MSG_SIZ];
5112
5113     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5114         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5115             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5116             SendToProgram(buf, cps);
5117             return;
5118         }
5119         // null move in variant where engine does not understand it (for analysis purposes)
5120         SendBoard(cps, moveNum + 1); // send position after move in stead.
5121         return;
5122     }
5123     if (cps->useUsermove) {
5124       SendToProgram("usermove ", cps);
5125     }
5126     if (cps->useSAN) {
5127       char *space;
5128       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5129         int len = space - parseList[moveNum];
5130         memcpy(buf, parseList[moveNum], len);
5131         buf[len++] = '\n';
5132         buf[len] = NULLCHAR;
5133       } else {
5134         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5135       }
5136       SendToProgram(buf, cps);
5137     } else {
5138       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5139         AlphaRank(moveList[moveNum], 4);
5140         SendToProgram(moveList[moveNum], cps);
5141         AlphaRank(moveList[moveNum], 4); // and back
5142       } else
5143       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5144        * the engine. It would be nice to have a better way to identify castle
5145        * moves here. */
5146       if(appData.fischerCastling && cps->useOOCastle) {
5147         int fromX = moveList[moveNum][0] - AAA;
5148         int fromY = moveList[moveNum][1] - ONE;
5149         int toX = moveList[moveNum][2] - AAA;
5150         int toY = moveList[moveNum][3] - ONE;
5151         if((boards[moveNum][fromY][fromX] == WhiteKing
5152             && boards[moveNum][toY][toX] == WhiteRook)
5153            || (boards[moveNum][fromY][fromX] == BlackKing
5154                && boards[moveNum][toY][toX] == BlackRook)) {
5155           if(toX > fromX) SendToProgram("O-O\n", cps);
5156           else SendToProgram("O-O-O\n", cps);
5157         }
5158         else SendToProgram(moveList[moveNum], cps);
5159       } else
5160       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5161         char *m = moveList[moveNum];
5162         static char c[2];
5163         *c = m[7]; // promoChar
5164         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
5165           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5166                                                m[2], m[3] - '0',
5167                                                m[5], m[6] - '0',
5168                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5169         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5170           *c = m[9];
5171           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
5172                                                m[7], m[8] - '0',
5173                                                m[7], m[8] - '0',
5174                                                m[5], m[6] - '0',
5175                                                m[5], m[6] - '0',
5176                                                m[2], m[3] - '0', c);
5177         } else
5178           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5179                                                m[5], m[6] - '0',
5180                                                m[5], m[6] - '0',
5181                                                m[2], m[3] - '0', c);
5182           SendToProgram(buf, cps);
5183       } else
5184       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5185         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5186           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5187           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5188                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5189         } else
5190           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5191                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5192         SendToProgram(buf, cps);
5193       }
5194       else SendToProgram(moveList[moveNum], cps);
5195       /* End of additions by Tord */
5196     }
5197
5198     /* [HGM] setting up the opening has brought engine in force mode! */
5199     /*       Send 'go' if we are in a mode where machine should play. */
5200     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5201         (gameMode == TwoMachinesPlay   ||
5202 #if ZIPPY
5203          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5204 #endif
5205          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5206         SendToProgram("go\n", cps);
5207   if (appData.debugMode) {
5208     fprintf(debugFP, "(extra)\n");
5209   }
5210     }
5211     setboardSpoiledMachineBlack = 0;
5212 }
5213
5214 void
5215 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5216 {
5217     char user_move[MSG_SIZ];
5218     char suffix[4];
5219
5220     if(gameInfo.variant == VariantSChess && promoChar) {
5221         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5222         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5223     } else suffix[0] = NULLCHAR;
5224
5225     switch (moveType) {
5226       default:
5227         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5228                 (int)moveType, fromX, fromY, toX, toY);
5229         DisplayError(user_move + strlen("say "), 0);
5230         break;
5231       case WhiteKingSideCastle:
5232       case BlackKingSideCastle:
5233       case WhiteQueenSideCastleWild:
5234       case BlackQueenSideCastleWild:
5235       /* PUSH Fabien */
5236       case WhiteHSideCastleFR:
5237       case BlackHSideCastleFR:
5238       /* POP Fabien */
5239         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5240         break;
5241       case WhiteQueenSideCastle:
5242       case BlackQueenSideCastle:
5243       case WhiteKingSideCastleWild:
5244       case BlackKingSideCastleWild:
5245       /* PUSH Fabien */
5246       case WhiteASideCastleFR:
5247       case BlackASideCastleFR:
5248       /* POP Fabien */
5249         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5250         break;
5251       case WhiteNonPromotion:
5252       case BlackNonPromotion:
5253         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5254         break;
5255       case WhitePromotion:
5256       case BlackPromotion:
5257         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5258            gameInfo.variant == VariantMakruk)
5259           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5260                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5261                 PieceToChar(WhiteFerz));
5262         else if(gameInfo.variant == VariantGreat)
5263           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5264                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5265                 PieceToChar(WhiteMan));
5266         else
5267           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5268                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5269                 promoChar);
5270         break;
5271       case WhiteDrop:
5272       case BlackDrop:
5273       drop:
5274         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5275                  ToUpper(PieceToChar((ChessSquare) fromX)),
5276                  AAA + toX, ONE + toY);
5277         break;
5278       case IllegalMove:  /* could be a variant we don't quite understand */
5279         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5280       case NormalMove:
5281       case WhiteCapturesEnPassant:
5282       case BlackCapturesEnPassant:
5283         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5284                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5285         break;
5286     }
5287     SendToICS(user_move);
5288     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5289         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5290 }
5291
5292 void
5293 UploadGameEvent ()
5294 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5295     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5296     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5297     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5298       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5299       return;
5300     }
5301     if(gameMode != IcsExamining) { // is this ever not the case?
5302         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5303
5304         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5305           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5306         } else { // on FICS we must first go to general examine mode
5307           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5308         }
5309         if(gameInfo.variant != VariantNormal) {
5310             // try figure out wild number, as xboard names are not always valid on ICS
5311             for(i=1; i<=36; i++) {
5312               snprintf(buf, MSG_SIZ, "wild/%d", i);
5313                 if(StringToVariant(buf) == gameInfo.variant) break;
5314             }
5315             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5316             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5317             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5318         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5319         SendToICS(ics_prefix);
5320         SendToICS(buf);
5321         if(startedFromSetupPosition || backwardMostMove != 0) {
5322           fen = PositionToFEN(backwardMostMove, NULL, 1);
5323           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5324             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5325             SendToICS(buf);
5326           } else { // FICS: everything has to set by separate bsetup commands
5327             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5328             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5329             SendToICS(buf);
5330             if(!WhiteOnMove(backwardMostMove)) {
5331                 SendToICS("bsetup tomove black\n");
5332             }
5333             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5334             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5335             SendToICS(buf);
5336             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5337             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5338             SendToICS(buf);
5339             i = boards[backwardMostMove][EP_STATUS];
5340             if(i >= 0) { // set e.p.
5341               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5342                 SendToICS(buf);
5343             }
5344             bsetup++;
5345           }
5346         }
5347       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5348             SendToICS("bsetup done\n"); // switch to normal examining.
5349     }
5350     for(i = backwardMostMove; i<last; i++) {
5351         char buf[20];
5352         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5353         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5354             int len = strlen(moveList[i]);
5355             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5356             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5357         }
5358         SendToICS(buf);
5359     }
5360     SendToICS(ics_prefix);
5361     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5362 }
5363
5364 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5365 int legNr = 1;
5366
5367 void
5368 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5369 {
5370     if (rf == DROP_RANK) {
5371       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5372       sprintf(move, "%c@%c%c\n",
5373                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5374     } else {
5375         if (promoChar == 'x' || promoChar == NULLCHAR) {
5376           sprintf(move, "%c%c%c%c\n",
5377                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5378           if(killX >= 0 && killY >= 0) {
5379             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5380             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5381           }
5382         } else {
5383             sprintf(move, "%c%c%c%c%c\n",
5384                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5385           if(killX >= 0 && killY >= 0) {
5386             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5387             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5388           }
5389         }
5390     }
5391 }
5392
5393 void
5394 ProcessICSInitScript (FILE *f)
5395 {
5396     char buf[MSG_SIZ];
5397
5398     while (fgets(buf, MSG_SIZ, f)) {
5399         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5400     }
5401
5402     fclose(f);
5403 }
5404
5405
5406 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5407 int dragging;
5408 static ClickType lastClickType;
5409
5410 int
5411 PieceInString (char *s, ChessSquare piece)
5412 {
5413   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5414   while((p = strchr(s, ID))) {
5415     if(!suffix || p[1] == suffix) return TRUE;
5416     s = p;
5417   }
5418   return FALSE;
5419 }
5420
5421 int
5422 Partner (ChessSquare *p)
5423 { // change piece into promotion partner if one shogi-promotes to the other
5424   ChessSquare partner = promoPartner[*p];
5425   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5426   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5427   *p = partner;
5428   return 1;
5429 }
5430
5431 void
5432 Sweep (int step)
5433 {
5434     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5435     static int toggleFlag;
5436     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5437     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5438     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5439     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5440     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5441     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5442     do {
5443         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5444         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5445         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5446         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5447         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5448         if(!step) step = -1;
5449     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5450             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5451             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5452             promoSweep == pawn ||
5453             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5454             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5455     if(toX >= 0) {
5456         int victim = boards[currentMove][toY][toX];
5457         boards[currentMove][toY][toX] = promoSweep;
5458         DrawPosition(FALSE, boards[currentMove]);
5459         boards[currentMove][toY][toX] = victim;
5460     } else
5461     ChangeDragPiece(promoSweep);
5462 }
5463
5464 int
5465 PromoScroll (int x, int y)
5466 {
5467   int step = 0;
5468
5469   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5470   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5471   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5472   if(!step) return FALSE;
5473   lastX = x; lastY = y;
5474   if((promoSweep < BlackPawn) == flipView) step = -step;
5475   if(step > 0) selectFlag = 1;
5476   if(!selectFlag) Sweep(step);
5477   return FALSE;
5478 }
5479
5480 void
5481 NextPiece (int step)
5482 {
5483     ChessSquare piece = boards[currentMove][toY][toX];
5484     do {
5485         pieceSweep -= step;
5486         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5487         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5488         if(!step) step = -1;
5489     } while(PieceToChar(pieceSweep) == '.');
5490     boards[currentMove][toY][toX] = pieceSweep;
5491     DrawPosition(FALSE, boards[currentMove]);
5492     boards[currentMove][toY][toX] = piece;
5493 }
5494 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5495 void
5496 AlphaRank (char *move, int n)
5497 {
5498 //    char *p = move, c; int x, y;
5499
5500     if (appData.debugMode) {
5501         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5502     }
5503
5504     if(move[1]=='*' &&
5505        move[2]>='0' && move[2]<='9' &&
5506        move[3]>='a' && move[3]<='x'    ) {
5507         move[1] = '@';
5508         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5509         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5510     } else
5511     if(move[0]>='0' && move[0]<='9' &&
5512        move[1]>='a' && move[1]<='x' &&
5513        move[2]>='0' && move[2]<='9' &&
5514        move[3]>='a' && move[3]<='x'    ) {
5515         /* input move, Shogi -> normal */
5516         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5517         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5518         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5519         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5520     } else
5521     if(move[1]=='@' &&
5522        move[3]>='0' && move[3]<='9' &&
5523        move[2]>='a' && move[2]<='x'    ) {
5524         move[1] = '*';
5525         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5526         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5527     } else
5528     if(
5529        move[0]>='a' && move[0]<='x' &&
5530        move[3]>='0' && move[3]<='9' &&
5531        move[2]>='a' && move[2]<='x'    ) {
5532          /* output move, normal -> Shogi */
5533         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5534         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5535         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5536         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5537         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5538     }
5539     if (appData.debugMode) {
5540         fprintf(debugFP, "   out = '%s'\n", move);
5541     }
5542 }
5543
5544 char yy_textstr[8000];
5545
5546 /* Parser for moves from gnuchess, ICS, or user typein box */
5547 Boolean
5548 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5549 {
5550     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5551
5552     switch (*moveType) {
5553       case WhitePromotion:
5554       case BlackPromotion:
5555       case WhiteNonPromotion:
5556       case BlackNonPromotion:
5557       case NormalMove:
5558       case FirstLeg:
5559       case WhiteCapturesEnPassant:
5560       case BlackCapturesEnPassant:
5561       case WhiteKingSideCastle:
5562       case WhiteQueenSideCastle:
5563       case BlackKingSideCastle:
5564       case BlackQueenSideCastle:
5565       case WhiteKingSideCastleWild:
5566       case WhiteQueenSideCastleWild:
5567       case BlackKingSideCastleWild:
5568       case BlackQueenSideCastleWild:
5569       /* Code added by Tord: */
5570       case WhiteHSideCastleFR:
5571       case WhiteASideCastleFR:
5572       case BlackHSideCastleFR:
5573       case BlackASideCastleFR:
5574       /* End of code added by Tord */
5575       case IllegalMove:         /* bug or odd chess variant */
5576         if(currentMoveString[1] == '@') { // illegal drop
5577           *fromX = WhiteOnMove(moveNum) ?
5578             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5579             (int) CharToPiece(ToLower(currentMoveString[0]));
5580           goto drop;
5581         }
5582         *fromX = currentMoveString[0] - AAA;
5583         *fromY = currentMoveString[1] - ONE;
5584         *toX = currentMoveString[2] - AAA;
5585         *toY = currentMoveString[3] - ONE;
5586         *promoChar = currentMoveString[4];
5587         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5588         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5589             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5590     if (appData.debugMode) {
5591         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5592     }
5593             *fromX = *fromY = *toX = *toY = 0;
5594             return FALSE;
5595         }
5596         if (appData.testLegality) {
5597           return (*moveType != IllegalMove);
5598         } else {
5599           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5600                          // [HGM] lion: if this is a double move we are less critical
5601                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5602         }
5603
5604       case WhiteDrop:
5605       case BlackDrop:
5606         *fromX = *moveType == WhiteDrop ?
5607           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5608           (int) CharToPiece(ToLower(currentMoveString[0]));
5609       drop:
5610         *fromY = DROP_RANK;
5611         *toX = currentMoveString[2] - AAA;
5612         *toY = currentMoveString[3] - ONE;
5613         *promoChar = NULLCHAR;
5614         return TRUE;
5615
5616       case AmbiguousMove:
5617       case ImpossibleMove:
5618       case EndOfFile:
5619       case ElapsedTime:
5620       case Comment:
5621       case PGNTag:
5622       case NAG:
5623       case WhiteWins:
5624       case BlackWins:
5625       case GameIsDrawn:
5626       default:
5627     if (appData.debugMode) {
5628         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5629     }
5630         /* bug? */
5631         *fromX = *fromY = *toX = *toY = 0;
5632         *promoChar = NULLCHAR;
5633         return FALSE;
5634     }
5635 }
5636
5637 Boolean pushed = FALSE;
5638 char *lastParseAttempt;
5639
5640 void
5641 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5642 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5643   int fromX, fromY, toX, toY; char promoChar;
5644   ChessMove moveType;
5645   Boolean valid;
5646   int nr = 0;
5647
5648   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5649   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5650     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5651     pushed = TRUE;
5652   }
5653   endPV = forwardMostMove;
5654   do {
5655     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5656     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5657     lastParseAttempt = pv;
5658     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5659     if(!valid && nr == 0 &&
5660        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5661         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5662         // Hande case where played move is different from leading PV move
5663         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5664         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5665         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5666         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5667           endPV += 2; // if position different, keep this
5668           moveList[endPV-1][0] = fromX + AAA;
5669           moveList[endPV-1][1] = fromY + ONE;
5670           moveList[endPV-1][2] = toX + AAA;
5671           moveList[endPV-1][3] = toY + ONE;
5672           parseList[endPV-1][0] = NULLCHAR;
5673           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5674         }
5675       }
5676     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5677     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5678     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5679     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5680         valid++; // allow comments in PV
5681         continue;
5682     }
5683     nr++;
5684     if(endPV+1 > framePtr) break; // no space, truncate
5685     if(!valid) break;
5686     endPV++;
5687     CopyBoard(boards[endPV], boards[endPV-1]);
5688     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5689     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5690     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5691     CoordsToAlgebraic(boards[endPV - 1],
5692                              PosFlags(endPV - 1),
5693                              fromY, fromX, toY, toX, promoChar,
5694                              parseList[endPV - 1]);
5695   } while(valid);
5696   if(atEnd == 2) return; // used hidden, for PV conversion
5697   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5698   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5699   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5700                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5701   DrawPosition(TRUE, boards[currentMove]);
5702 }
5703
5704 int
5705 MultiPV (ChessProgramState *cps, int kind)
5706 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5707         int i;
5708         for(i=0; i<cps->nrOptions; i++) {
5709             char *s = cps->option[i].name;
5710             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5711             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5712                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5713         }
5714         return -1;
5715 }
5716
5717 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5718 static int multi, pv_margin;
5719 static ChessProgramState *activeCps;
5720
5721 Boolean
5722 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5723 {
5724         int startPV, lineStart, origIndex = index;
5725         char *p, buf2[MSG_SIZ];
5726         ChessProgramState *cps = (pane ? &second : &first);
5727
5728         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5729         lastX = x; lastY = y;
5730         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5731         lineStart = startPV = index;
5732         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5733         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5734         index = startPV;
5735         do{ while(buf[index] && buf[index] != '\n') index++;
5736         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5737         buf[index] = 0;
5738         if(lineStart == 0 && gameMode == AnalyzeMode) {
5739             int n = 0;
5740             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5741             if(n == 0) { // click not on "fewer" or "more"
5742                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5743                     pv_margin = cps->option[multi].value;
5744                     activeCps = cps; // non-null signals margin adjustment
5745                 }
5746             } else if((multi = MultiPV(cps, 1)) >= 0) {
5747                 n += cps->option[multi].value; if(n < 1) n = 1;
5748                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5749                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5750                 cps->option[multi].value = n;
5751                 *start = *end = 0;
5752                 return FALSE;
5753             }
5754         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5755                 ExcludeClick(origIndex - lineStart);
5756                 return FALSE;
5757         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5758                 Collapse(origIndex - lineStart);
5759                 return FALSE;
5760         }
5761         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5762         *start = startPV; *end = index-1;
5763         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5764         return TRUE;
5765 }
5766
5767 char *
5768 PvToSAN (char *pv)
5769 {
5770         static char buf[10*MSG_SIZ];
5771         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5772         *buf = NULLCHAR;
5773         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5774         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5775         for(i = forwardMostMove; i<endPV; i++){
5776             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5777             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5778             k += strlen(buf+k);
5779         }
5780         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5781         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5782         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5783         endPV = savedEnd;
5784         return buf;
5785 }
5786
5787 Boolean
5788 LoadPV (int x, int y)
5789 { // called on right mouse click to load PV
5790   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5791   lastX = x; lastY = y;
5792   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5793   extendGame = FALSE;
5794   return TRUE;
5795 }
5796
5797 void
5798 UnLoadPV ()
5799 {
5800   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5801   if(activeCps) {
5802     if(pv_margin != activeCps->option[multi].value) {
5803       char buf[MSG_SIZ];
5804       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5805       SendToProgram(buf, activeCps);
5806       activeCps->option[multi].value = pv_margin;
5807     }
5808     activeCps = NULL;
5809     return;
5810   }
5811   if(endPV < 0) return;
5812   if(appData.autoCopyPV) CopyFENToClipboard();
5813   endPV = -1;
5814   if(extendGame && currentMove > forwardMostMove) {
5815         Boolean saveAnimate = appData.animate;
5816         if(pushed) {
5817             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5818                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5819             } else storedGames--; // abandon shelved tail of original game
5820         }
5821         pushed = FALSE;
5822         forwardMostMove = currentMove;
5823         currentMove = oldFMM;
5824         appData.animate = FALSE;
5825         ToNrEvent(forwardMostMove);
5826         appData.animate = saveAnimate;
5827   }
5828   currentMove = forwardMostMove;
5829   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5830   ClearPremoveHighlights();
5831   DrawPosition(TRUE, boards[currentMove]);
5832 }
5833
5834 void
5835 MovePV (int x, int y, int h)
5836 { // step through PV based on mouse coordinates (called on mouse move)
5837   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5838
5839   if(activeCps) { // adjusting engine's multi-pv margin
5840     if(x > lastX) pv_margin++; else
5841     if(x < lastX) pv_margin -= (pv_margin > 0);
5842     if(x != lastX) {
5843       char buf[MSG_SIZ];
5844       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5845       DisplayMessage(buf, "");
5846     }
5847     lastX = x;
5848     return;
5849   }
5850   // we must somehow check if right button is still down (might be released off board!)
5851   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5852   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5853   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5854   if(!step) return;
5855   lastX = x; lastY = y;
5856
5857   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5858   if(endPV < 0) return;
5859   if(y < margin) step = 1; else
5860   if(y > h - margin) step = -1;
5861   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5862   currentMove += step;
5863   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5864   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5865                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5866   DrawPosition(FALSE, boards[currentMove]);
5867 }
5868
5869
5870 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5871 // All positions will have equal probability, but the current method will not provide a unique
5872 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5873 #define DARK 1
5874 #define LITE 2
5875 #define ANY 3
5876
5877 int squaresLeft[4];
5878 int piecesLeft[(int)BlackPawn];
5879 int seed, nrOfShuffles;
5880
5881 void
5882 GetPositionNumber ()
5883 {       // sets global variable seed
5884         int i;
5885
5886         seed = appData.defaultFrcPosition;
5887         if(seed < 0) { // randomize based on time for negative FRC position numbers
5888                 for(i=0; i<50; i++) seed += random();
5889                 seed = random() ^ random() >> 8 ^ random() << 8;
5890                 if(seed<0) seed = -seed;
5891         }
5892 }
5893
5894 int
5895 put (Board board, int pieceType, int rank, int n, int shade)
5896 // put the piece on the (n-1)-th empty squares of the given shade
5897 {
5898         int i;
5899
5900         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5901                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5902                         board[rank][i] = (ChessSquare) pieceType;
5903                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5904                         squaresLeft[ANY]--;
5905                         piecesLeft[pieceType]--;
5906                         return i;
5907                 }
5908         }
5909         return -1;
5910 }
5911
5912
5913 void
5914 AddOnePiece (Board board, int pieceType, int rank, int shade)
5915 // calculate where the next piece goes, (any empty square), and put it there
5916 {
5917         int i;
5918
5919         i = seed % squaresLeft[shade];
5920         nrOfShuffles *= squaresLeft[shade];
5921         seed /= squaresLeft[shade];
5922         put(board, pieceType, rank, i, shade);
5923 }
5924
5925 void
5926 AddTwoPieces (Board board, int pieceType, int rank)
5927 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5928 {
5929         int i, n=squaresLeft[ANY], j=n-1, k;
5930
5931         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5932         i = seed % k;  // pick one
5933         nrOfShuffles *= k;
5934         seed /= k;
5935         while(i >= j) i -= j--;
5936         j = n - 1 - j; i += j;
5937         put(board, pieceType, rank, j, ANY);
5938         put(board, pieceType, rank, i, ANY);
5939 }
5940
5941 void
5942 SetUpShuffle (Board board, int number)
5943 {
5944         int i, p, first=1;
5945
5946         GetPositionNumber(); nrOfShuffles = 1;
5947
5948         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5949         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5950         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5951
5952         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5953
5954         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5955             p = (int) board[0][i];
5956             if(p < (int) BlackPawn) piecesLeft[p] ++;
5957             board[0][i] = EmptySquare;
5958         }
5959
5960         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5961             // shuffles restricted to allow normal castling put KRR first
5962             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5963                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5964             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5965                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5966             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5967                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5968             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5969                 put(board, WhiteRook, 0, 0, ANY);
5970             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5971         }
5972
5973         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5974             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5975             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5976                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5977                 while(piecesLeft[p] >= 2) {
5978                     AddOnePiece(board, p, 0, LITE);
5979                     AddOnePiece(board, p, 0, DARK);
5980                 }
5981                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5982             }
5983
5984         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5985             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5986             // but we leave King and Rooks for last, to possibly obey FRC restriction
5987             if(p == (int)WhiteRook) continue;
5988             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5989             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5990         }
5991
5992         // now everything is placed, except perhaps King (Unicorn) and Rooks
5993
5994         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5995             // Last King gets castling rights
5996             while(piecesLeft[(int)WhiteUnicorn]) {
5997                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5998                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5999             }
6000
6001             while(piecesLeft[(int)WhiteKing]) {
6002                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6003                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6004             }
6005
6006
6007         } else {
6008             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6009             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6010         }
6011
6012         // Only Rooks can be left; simply place them all
6013         while(piecesLeft[(int)WhiteRook]) {
6014                 i = put(board, WhiteRook, 0, 0, ANY);
6015                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6016                         if(first) {
6017                                 first=0;
6018                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6019                         }
6020                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6021                 }
6022         }
6023         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6024             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6025         }
6026
6027         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6028 }
6029
6030 int
6031 ptclen (const char *s, char *escapes)
6032 {
6033     int n = 0;
6034     if(!*escapes) return strlen(s);
6035     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6036     return n;
6037 }
6038
6039 int
6040 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6041 /* [HGM] moved here from winboard.c because of its general usefulness */
6042 /*       Basically a safe strcpy that uses the last character as King */
6043 {
6044     int result = FALSE; int NrPieces;
6045     unsigned char partner[EmptySquare];
6046
6047     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6048                     && NrPieces >= 12 && !(NrPieces&1)) {
6049         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6050
6051         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6052         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6053             char *p, c=0;
6054             if(map[j] == '/') offs = WhitePBishop - i, j++;
6055             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6056             table[i+offs] = map[j++];
6057             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6058             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6059             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6060         }
6061         table[(int) WhiteKing]  = map[j++];
6062         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6063             char *p, c=0;
6064             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6065             i = WHITE_TO_BLACK ii;
6066             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6067             table[i+offs] = map[j++];
6068             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6069             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6070             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6071         }
6072         table[(int) BlackKing]  = map[j++];
6073
6074
6075         if(*escapes) { // set up promotion pairing
6076             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6077             // pieceToChar entirely filled, so we can look up specified partners
6078             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6079                 int c = table[i];
6080                 if(c == '^' || c == '-') { // has specified partner
6081                     int p;
6082                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6083                     if(c == '^') table[i] = '+';
6084                     if(p < EmptySquare) {
6085                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6086                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6087                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6088                     }
6089                 } else if(c == '*') {
6090                     table[i] = partner[i];
6091                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6092                 }
6093             }
6094         }
6095
6096         result = TRUE;
6097     }
6098
6099     return result;
6100 }
6101
6102 int
6103 SetCharTable (unsigned char *table, const char * map)
6104 {
6105     return SetCharTableEsc(table, map, "");
6106 }
6107
6108 void
6109 Prelude (Board board)
6110 {       // [HGM] superchess: random selection of exo-pieces
6111         int i, j, k; ChessSquare p;
6112         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6113
6114         GetPositionNumber(); // use FRC position number
6115
6116         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6117             SetCharTable(pieceToChar, appData.pieceToCharTable);
6118             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6119                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6120         }
6121
6122         j = seed%4;                 seed /= 4;
6123         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6124         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6125         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6126         j = seed%3 + (seed%3 >= j); seed /= 3;
6127         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6128         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6129         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6130         j = seed%3;                 seed /= 3;
6131         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6132         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6133         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6134         j = seed%2 + (seed%2 >= j); seed /= 2;
6135         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6136         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6137         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6138         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6139         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6140         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6141         put(board, exoPieces[0],    0, 0, ANY);
6142         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6143 }
6144
6145 void
6146 InitPosition (int redraw)
6147 {
6148     ChessSquare (* pieces)[BOARD_FILES];
6149     int i, j, pawnRow=1, pieceRows=1, overrule,
6150     oldx = gameInfo.boardWidth,
6151     oldy = gameInfo.boardHeight,
6152     oldh = gameInfo.holdingsWidth;
6153     static int oldv;
6154
6155     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6156
6157     /* [AS] Initialize pv info list [HGM] and game status */
6158     {
6159         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6160             pvInfoList[i].depth = 0;
6161             boards[i][EP_STATUS] = EP_NONE;
6162             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6163         }
6164
6165         initialRulePlies = 0; /* 50-move counter start */
6166
6167         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6168         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6169     }
6170
6171
6172     /* [HGM] logic here is completely changed. In stead of full positions */
6173     /* the initialized data only consist of the two backranks. The switch */
6174     /* selects which one we will use, which is than copied to the Board   */
6175     /* initialPosition, which for the rest is initialized by Pawns and    */
6176     /* empty squares. This initial position is then copied to boards[0],  */
6177     /* possibly after shuffling, so that it remains available.            */
6178
6179     gameInfo.holdingsWidth = 0; /* default board sizes */
6180     gameInfo.boardWidth    = 8;
6181     gameInfo.boardHeight   = 8;
6182     gameInfo.holdingsSize  = 0;
6183     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6184     for(i=0; i<BOARD_FILES-6; i++)
6185       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6186     initialPosition[EP_STATUS] = EP_NONE;
6187     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6188     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6189     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6190          SetCharTable(pieceNickName, appData.pieceNickNames);
6191     else SetCharTable(pieceNickName, "............");
6192     pieces = FIDEArray;
6193
6194     switch (gameInfo.variant) {
6195     case VariantFischeRandom:
6196       shuffleOpenings = TRUE;
6197       appData.fischerCastling = TRUE;
6198     default:
6199       break;
6200     case VariantShatranj:
6201       pieces = ShatranjArray;
6202       nrCastlingRights = 0;
6203       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6204       break;
6205     case VariantMakruk:
6206       pieces = makrukArray;
6207       nrCastlingRights = 0;
6208       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6209       break;
6210     case VariantASEAN:
6211       pieces = aseanArray;
6212       nrCastlingRights = 0;
6213       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6214       break;
6215     case VariantTwoKings:
6216       pieces = twoKingsArray;
6217       break;
6218     case VariantGrand:
6219       pieces = GrandArray;
6220       nrCastlingRights = 0;
6221       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6222       gameInfo.boardWidth = 10;
6223       gameInfo.boardHeight = 10;
6224       gameInfo.holdingsSize = 7;
6225       break;
6226     case VariantCapaRandom:
6227       shuffleOpenings = TRUE;
6228       appData.fischerCastling = TRUE;
6229     case VariantCapablanca:
6230       pieces = CapablancaArray;
6231       gameInfo.boardWidth = 10;
6232       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6233       break;
6234     case VariantGothic:
6235       pieces = GothicArray;
6236       gameInfo.boardWidth = 10;
6237       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6238       break;
6239     case VariantSChess:
6240       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6241       gameInfo.holdingsSize = 7;
6242       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6243       break;
6244     case VariantJanus:
6245       pieces = JanusArray;
6246       gameInfo.boardWidth = 10;
6247       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6248       nrCastlingRights = 6;
6249         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6250         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6251         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6252         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6253         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6254         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6255       break;
6256     case VariantFalcon:
6257       pieces = FalconArray;
6258       gameInfo.boardWidth = 10;
6259       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6260       break;
6261     case VariantXiangqi:
6262       pieces = XiangqiArray;
6263       gameInfo.boardWidth  = 9;
6264       gameInfo.boardHeight = 10;
6265       nrCastlingRights = 0;
6266       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6267       break;
6268     case VariantShogi:
6269       pieces = ShogiArray;
6270       gameInfo.boardWidth  = 9;
6271       gameInfo.boardHeight = 9;
6272       gameInfo.holdingsSize = 7;
6273       nrCastlingRights = 0;
6274       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6275       break;
6276     case VariantChu:
6277       pieces = ChuArray; pieceRows = 3;
6278       gameInfo.boardWidth  = 12;
6279       gameInfo.boardHeight = 12;
6280       nrCastlingRights = 0;
6281       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6282                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6283       break;
6284     case VariantCourier:
6285       pieces = CourierArray;
6286       gameInfo.boardWidth  = 12;
6287       nrCastlingRights = 0;
6288       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6289       break;
6290     case VariantKnightmate:
6291       pieces = KnightmateArray;
6292       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6293       break;
6294     case VariantSpartan:
6295       pieces = SpartanArray;
6296       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6297       break;
6298     case VariantLion:
6299       pieces = lionArray;
6300       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6301       break;
6302     case VariantChuChess:
6303       pieces = ChuChessArray;
6304       gameInfo.boardWidth = 10;
6305       gameInfo.boardHeight = 10;
6306       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6307       break;
6308     case VariantFairy:
6309       pieces = fairyArray;
6310       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6311       break;
6312     case VariantGreat:
6313       pieces = GreatArray;
6314       gameInfo.boardWidth = 10;
6315       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6316       gameInfo.holdingsSize = 8;
6317       break;
6318     case VariantSuper:
6319       pieces = FIDEArray;
6320       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6321       gameInfo.holdingsSize = 8;
6322       startedFromSetupPosition = TRUE;
6323       break;
6324     case VariantCrazyhouse:
6325     case VariantBughouse:
6326       pieces = FIDEArray;
6327       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6328       gameInfo.holdingsSize = 5;
6329       break;
6330     case VariantWildCastle:
6331       pieces = FIDEArray;
6332       /* !!?shuffle with kings guaranteed to be on d or e file */
6333       shuffleOpenings = 1;
6334       break;
6335     case VariantNoCastle:
6336       pieces = FIDEArray;
6337       nrCastlingRights = 0;
6338       /* !!?unconstrained back-rank shuffle */
6339       shuffleOpenings = 1;
6340       break;
6341     }
6342
6343     overrule = 0;
6344     if(appData.NrFiles >= 0) {
6345         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6346         gameInfo.boardWidth = appData.NrFiles;
6347     }
6348     if(appData.NrRanks >= 0) {
6349         gameInfo.boardHeight = appData.NrRanks;
6350     }
6351     if(appData.holdingsSize >= 0) {
6352         i = appData.holdingsSize;
6353         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6354         gameInfo.holdingsSize = i;
6355     }
6356     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6357     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6358         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6359
6360     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6361     if(pawnRow < 1) pawnRow = 1;
6362     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6363        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6364     if(gameInfo.variant == VariantChu) pawnRow = 3;
6365
6366     /* User pieceToChar list overrules defaults */
6367     if(appData.pieceToCharTable != NULL)
6368         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6369
6370     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6371
6372         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6373             s = (ChessSquare) 0; /* account holding counts in guard band */
6374         for( i=0; i<BOARD_HEIGHT; i++ )
6375             initialPosition[i][j] = s;
6376
6377         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6378         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6379         initialPosition[pawnRow][j] = WhitePawn;
6380         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6381         if(gameInfo.variant == VariantXiangqi) {
6382             if(j&1) {
6383                 initialPosition[pawnRow][j] =
6384                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6385                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6386                    initialPosition[2][j] = WhiteCannon;
6387                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6388                 }
6389             }
6390         }
6391         if(gameInfo.variant == VariantChu) {
6392              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6393                initialPosition[pawnRow+1][j] = WhiteCobra,
6394                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6395              for(i=1; i<pieceRows; i++) {
6396                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6397                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6398              }
6399         }
6400         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6401             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6402                initialPosition[0][j] = WhiteRook;
6403                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6404             }
6405         }
6406         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6407     }
6408     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6409     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6410
6411             j=BOARD_LEFT+1;
6412             initialPosition[1][j] = WhiteBishop;
6413             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6414             j=BOARD_RGHT-2;
6415             initialPosition[1][j] = WhiteRook;
6416             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6417     }
6418
6419     if( nrCastlingRights == -1) {
6420         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6421         /*       This sets default castling rights from none to normal corners   */
6422         /* Variants with other castling rights must set them themselves above    */
6423         nrCastlingRights = 6;
6424
6425         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6426         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6427         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6428         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6429         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6430         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6431      }
6432
6433      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6434      if(gameInfo.variant == VariantGreat) { // promotion commoners
6435         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6436         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6437         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6438         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6439      }
6440      if( gameInfo.variant == VariantSChess ) {
6441       initialPosition[1][0] = BlackMarshall;
6442       initialPosition[2][0] = BlackAngel;
6443       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6444       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6445       initialPosition[1][1] = initialPosition[2][1] =
6446       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6447      }
6448   if (appData.debugMode) {
6449     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6450   }
6451     if(shuffleOpenings) {
6452         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6453         startedFromSetupPosition = TRUE;
6454     }
6455     if(startedFromPositionFile) {
6456       /* [HGM] loadPos: use PositionFile for every new game */
6457       CopyBoard(initialPosition, filePosition);
6458       for(i=0; i<nrCastlingRights; i++)
6459           initialRights[i] = filePosition[CASTLING][i];
6460       startedFromSetupPosition = TRUE;
6461     }
6462     if(*appData.men) LoadPieceDesc(appData.men);
6463
6464     CopyBoard(boards[0], initialPosition);
6465
6466     if(oldx != gameInfo.boardWidth ||
6467        oldy != gameInfo.boardHeight ||
6468        oldv != gameInfo.variant ||
6469        oldh != gameInfo.holdingsWidth
6470                                          )
6471             InitDrawingSizes(-2 ,0);
6472
6473     oldv = gameInfo.variant;
6474     if (redraw)
6475       DrawPosition(TRUE, boards[currentMove]);
6476 }
6477
6478 void
6479 SendBoard (ChessProgramState *cps, int moveNum)
6480 {
6481     char message[MSG_SIZ];
6482
6483     if (cps->useSetboard) {
6484       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6485       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6486       SendToProgram(message, cps);
6487       free(fen);
6488
6489     } else {
6490       ChessSquare *bp;
6491       int i, j, left=0, right=BOARD_WIDTH;
6492       /* Kludge to set black to move, avoiding the troublesome and now
6493        * deprecated "black" command.
6494        */
6495       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6496         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6497
6498       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6499
6500       SendToProgram("edit\n", cps);
6501       SendToProgram("#\n", cps);
6502       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6503         bp = &boards[moveNum][i][left];
6504         for (j = left; j < right; j++, bp++) {
6505           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6506           if ((int) *bp < (int) BlackPawn) {
6507             if(j == BOARD_RGHT+1)
6508                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6509             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6510             if(message[0] == '+' || message[0] == '~') {
6511               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6512                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6513                         AAA + j, ONE + i - '0');
6514             }
6515             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6516                 message[1] = BOARD_RGHT   - 1 - j + '1';
6517                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6518             }
6519             SendToProgram(message, cps);
6520           }
6521         }
6522       }
6523
6524       SendToProgram("c\n", cps);
6525       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6526         bp = &boards[moveNum][i][left];
6527         for (j = left; j < right; j++, bp++) {
6528           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6529           if (((int) *bp != (int) EmptySquare)
6530               && ((int) *bp >= (int) BlackPawn)) {
6531             if(j == BOARD_LEFT-2)
6532                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6533             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6534                     AAA + j, ONE + i - '0');
6535             if(message[0] == '+' || message[0] == '~') {
6536               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6537                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6538                         AAA + j, ONE + i - '0');
6539             }
6540             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6541                 message[1] = BOARD_RGHT   - 1 - j + '1';
6542                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6543             }
6544             SendToProgram(message, cps);
6545           }
6546         }
6547       }
6548
6549       SendToProgram(".\n", cps);
6550     }
6551     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6552 }
6553
6554 char exclusionHeader[MSG_SIZ];
6555 int exCnt, excludePtr;
6556 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6557 static Exclusion excluTab[200];
6558 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6559
6560 static void
6561 WriteMap (int s)
6562 {
6563     int j;
6564     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6565     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6566 }
6567
6568 static void
6569 ClearMap ()
6570 {
6571     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6572     excludePtr = 24; exCnt = 0;
6573     WriteMap(0);
6574 }
6575
6576 static void
6577 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6578 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6579     char buf[2*MOVE_LEN], *p;
6580     Exclusion *e = excluTab;
6581     int i;
6582     for(i=0; i<exCnt; i++)
6583         if(e[i].ff == fromX && e[i].fr == fromY &&
6584            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6585     if(i == exCnt) { // was not in exclude list; add it
6586         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6587         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6588             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6589             return; // abort
6590         }
6591         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6592         excludePtr++; e[i].mark = excludePtr++;
6593         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6594         exCnt++;
6595     }
6596     exclusionHeader[e[i].mark] = state;
6597 }
6598
6599 static int
6600 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6601 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6602     char buf[MSG_SIZ];
6603     int j, k;
6604     ChessMove moveType;
6605     if((signed char)promoChar == -1) { // kludge to indicate best move
6606         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6607             return 1; // if unparsable, abort
6608     }
6609     // update exclusion map (resolving toggle by consulting existing state)
6610     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6611     j = k%8; k >>= 3;
6612     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6613     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6614          excludeMap[k] |=   1<<j;
6615     else excludeMap[k] &= ~(1<<j);
6616     // update header
6617     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6618     // inform engine
6619     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6620     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6621     SendToBoth(buf);
6622     return (state == '+');
6623 }
6624
6625 static void
6626 ExcludeClick (int index)
6627 {
6628     int i, j;
6629     Exclusion *e = excluTab;
6630     if(index < 25) { // none, best or tail clicked
6631         if(index < 13) { // none: include all
6632             WriteMap(0); // clear map
6633             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6634             SendToBoth("include all\n"); // and inform engine
6635         } else if(index > 18) { // tail
6636             if(exclusionHeader[19] == '-') { // tail was excluded
6637                 SendToBoth("include all\n");
6638                 WriteMap(0); // clear map completely
6639                 // now re-exclude selected moves
6640                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6641                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6642             } else { // tail was included or in mixed state
6643                 SendToBoth("exclude all\n");
6644                 WriteMap(0xFF); // fill map completely
6645                 // now re-include selected moves
6646                 j = 0; // count them
6647                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6648                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6649                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6650             }
6651         } else { // best
6652             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6653         }
6654     } else {
6655         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6656             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6657             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6658             break;
6659         }
6660     }
6661 }
6662
6663 ChessSquare
6664 DefaultPromoChoice (int white)
6665 {
6666     ChessSquare result;
6667     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6668        gameInfo.variant == VariantMakruk)
6669         result = WhiteFerz; // no choice
6670     else if(gameInfo.variant == VariantASEAN)
6671         result = WhiteRook; // no choice
6672     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6673         result= WhiteKing; // in Suicide Q is the last thing we want
6674     else if(gameInfo.variant == VariantSpartan)
6675         result = white ? WhiteQueen : WhiteAngel;
6676     else result = WhiteQueen;
6677     if(!white) result = WHITE_TO_BLACK result;
6678     return result;
6679 }
6680
6681 static int autoQueen; // [HGM] oneclick
6682
6683 int
6684 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6685 {
6686     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6687     /* [HGM] add Shogi promotions */
6688     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6689     ChessSquare piece, partner;
6690     ChessMove moveType;
6691     Boolean premove;
6692
6693     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6694     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6695
6696     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6697       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6698         return FALSE;
6699
6700     piece = boards[currentMove][fromY][fromX];
6701     if(gameInfo.variant == VariantChu) {
6702         promotionZoneSize = BOARD_HEIGHT/3;
6703         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6704     } else if(gameInfo.variant == VariantShogi) {
6705         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6706         highestPromotingPiece = (int)WhiteAlfil;
6707     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6708         promotionZoneSize = 3;
6709     }
6710
6711     // Treat Lance as Pawn when it is not representing Amazon or Lance
6712     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6713         if(piece == WhiteLance) piece = WhitePawn; else
6714         if(piece == BlackLance) piece = BlackPawn;
6715     }
6716
6717     // next weed out all moves that do not touch the promotion zone at all
6718     if((int)piece >= BlackPawn) {
6719         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6720              return FALSE;
6721         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6722         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6723     } else {
6724         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6725            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6726         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6727              return FALSE;
6728     }
6729
6730     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6731
6732     // weed out mandatory Shogi promotions
6733     if(gameInfo.variant == VariantShogi) {
6734         if(piece >= BlackPawn) {
6735             if(toY == 0 && piece == BlackPawn ||
6736                toY == 0 && piece == BlackQueen ||
6737                toY <= 1 && piece == BlackKnight) {
6738                 *promoChoice = '+';
6739                 return FALSE;
6740             }
6741         } else {
6742             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6743                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6744                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6745                 *promoChoice = '+';
6746                 return FALSE;
6747             }
6748         }
6749     }
6750
6751     // weed out obviously illegal Pawn moves
6752     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6753         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6754         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6755         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6756         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6757         // note we are not allowed to test for valid (non-)capture, due to premove
6758     }
6759
6760     // we either have a choice what to promote to, or (in Shogi) whether to promote
6761     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6762        gameInfo.variant == VariantMakruk) {
6763         ChessSquare p=BlackFerz;  // no choice
6764         while(p < EmptySquare) {  //but make sure we use piece that exists
6765             *promoChoice = PieceToChar(p++);
6766             if(*promoChoice != '.') break;
6767         }
6768         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6769     }
6770     // no sense asking what we must promote to if it is going to explode...
6771     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6772         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6773         return FALSE;
6774     }
6775     // give caller the default choice even if we will not make it
6776     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6777     partner = piece; // pieces can promote if the pieceToCharTable says so
6778     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6779     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6780     if(        sweepSelect && gameInfo.variant != VariantGreat
6781                            && gameInfo.variant != VariantGrand
6782                            && gameInfo.variant != VariantSuper) return FALSE;
6783     if(autoQueen) return FALSE; // predetermined
6784
6785     // suppress promotion popup on illegal moves that are not premoves
6786     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6787               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6788     if(appData.testLegality && !premove) {
6789         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6790                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6791         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6792         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6793             return FALSE;
6794     }
6795
6796     return TRUE;
6797 }
6798
6799 int
6800 InPalace (int row, int column)
6801 {   /* [HGM] for Xiangqi */
6802     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6803          column < (BOARD_WIDTH + 4)/2 &&
6804          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6805     return FALSE;
6806 }
6807
6808 int
6809 PieceForSquare (int x, int y)
6810 {
6811   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6812      return -1;
6813   else
6814      return boards[currentMove][y][x];
6815 }
6816
6817 int
6818 OKToStartUserMove (int x, int y)
6819 {
6820     ChessSquare from_piece;
6821     int white_piece;
6822
6823     if (matchMode) return FALSE;
6824     if (gameMode == EditPosition) return TRUE;
6825
6826     if (x >= 0 && y >= 0)
6827       from_piece = boards[currentMove][y][x];
6828     else
6829       from_piece = EmptySquare;
6830
6831     if (from_piece == EmptySquare) return FALSE;
6832
6833     white_piece = (int)from_piece >= (int)WhitePawn &&
6834       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6835
6836     switch (gameMode) {
6837       case AnalyzeFile:
6838       case TwoMachinesPlay:
6839       case EndOfGame:
6840         return FALSE;
6841
6842       case IcsObserving:
6843       case IcsIdle:
6844         return FALSE;
6845
6846       case MachinePlaysWhite:
6847       case IcsPlayingBlack:
6848         if (appData.zippyPlay) return FALSE;
6849         if (white_piece) {
6850             DisplayMoveError(_("You are playing Black"));
6851             return FALSE;
6852         }
6853         break;
6854
6855       case MachinePlaysBlack:
6856       case IcsPlayingWhite:
6857         if (appData.zippyPlay) return FALSE;
6858         if (!white_piece) {
6859             DisplayMoveError(_("You are playing White"));
6860             return FALSE;
6861         }
6862         break;
6863
6864       case PlayFromGameFile:
6865             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6866       case EditGame:
6867       case AnalyzeMode:
6868         if (!white_piece && WhiteOnMove(currentMove)) {
6869             DisplayMoveError(_("It is White's turn"));
6870             return FALSE;
6871         }
6872         if (white_piece && !WhiteOnMove(currentMove)) {
6873             DisplayMoveError(_("It is Black's turn"));
6874             return FALSE;
6875         }
6876         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6877             /* Editing correspondence game history */
6878             /* Could disallow this or prompt for confirmation */
6879             cmailOldMove = -1;
6880         }
6881         break;
6882
6883       case BeginningOfGame:
6884         if (appData.icsActive) return FALSE;
6885         if (!appData.noChessProgram) {
6886             if (!white_piece) {
6887                 DisplayMoveError(_("You are playing White"));
6888                 return FALSE;
6889             }
6890         }
6891         break;
6892
6893       case Training:
6894         if (!white_piece && WhiteOnMove(currentMove)) {
6895             DisplayMoveError(_("It is White's turn"));
6896             return FALSE;
6897         }
6898         if (white_piece && !WhiteOnMove(currentMove)) {
6899             DisplayMoveError(_("It is Black's turn"));
6900             return FALSE;
6901         }
6902         break;
6903
6904       default:
6905       case IcsExamining:
6906         break;
6907     }
6908     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6909         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6910         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6911         && gameMode != AnalyzeFile && gameMode != Training) {
6912         DisplayMoveError(_("Displayed position is not current"));
6913         return FALSE;
6914     }
6915     return TRUE;
6916 }
6917
6918 Boolean
6919 OnlyMove (int *x, int *y, Boolean captures)
6920 {
6921     DisambiguateClosure cl;
6922     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6923     switch(gameMode) {
6924       case MachinePlaysBlack:
6925       case IcsPlayingWhite:
6926       case BeginningOfGame:
6927         if(!WhiteOnMove(currentMove)) return FALSE;
6928         break;
6929       case MachinePlaysWhite:
6930       case IcsPlayingBlack:
6931         if(WhiteOnMove(currentMove)) return FALSE;
6932         break;
6933       case EditGame:
6934         break;
6935       default:
6936         return FALSE;
6937     }
6938     cl.pieceIn = EmptySquare;
6939     cl.rfIn = *y;
6940     cl.ffIn = *x;
6941     cl.rtIn = -1;
6942     cl.ftIn = -1;
6943     cl.promoCharIn = NULLCHAR;
6944     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6945     if( cl.kind == NormalMove ||
6946         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6947         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6948         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6949       fromX = cl.ff;
6950       fromY = cl.rf;
6951       *x = cl.ft;
6952       *y = cl.rt;
6953       return TRUE;
6954     }
6955     if(cl.kind != ImpossibleMove) return FALSE;
6956     cl.pieceIn = EmptySquare;
6957     cl.rfIn = -1;
6958     cl.ffIn = -1;
6959     cl.rtIn = *y;
6960     cl.ftIn = *x;
6961     cl.promoCharIn = NULLCHAR;
6962     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6963     if( cl.kind == NormalMove ||
6964         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6965         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6966         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6967       fromX = cl.ff;
6968       fromY = cl.rf;
6969       *x = cl.ft;
6970       *y = cl.rt;
6971       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6972       return TRUE;
6973     }
6974     return FALSE;
6975 }
6976
6977 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6978 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6979 int lastLoadGameUseList = FALSE;
6980 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6981 ChessMove lastLoadGameStart = EndOfFile;
6982 int doubleClick;
6983 Boolean addToBookFlag;
6984
6985 void
6986 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6987 {
6988     ChessMove moveType;
6989     ChessSquare pup;
6990     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6991
6992     /* Check if the user is playing in turn.  This is complicated because we
6993        let the user "pick up" a piece before it is his turn.  So the piece he
6994        tried to pick up may have been captured by the time he puts it down!
6995        Therefore we use the color the user is supposed to be playing in this
6996        test, not the color of the piece that is currently on the starting
6997        square---except in EditGame mode, where the user is playing both
6998        sides; fortunately there the capture race can't happen.  (It can
6999        now happen in IcsExamining mode, but that's just too bad.  The user
7000        will get a somewhat confusing message in that case.)
7001        */
7002
7003     switch (gameMode) {
7004       case AnalyzeFile:
7005       case TwoMachinesPlay:
7006       case EndOfGame:
7007       case IcsObserving:
7008       case IcsIdle:
7009         /* We switched into a game mode where moves are not accepted,
7010            perhaps while the mouse button was down. */
7011         return;
7012
7013       case MachinePlaysWhite:
7014         /* User is moving for Black */
7015         if (WhiteOnMove(currentMove)) {
7016             DisplayMoveError(_("It is White's turn"));
7017             return;
7018         }
7019         break;
7020
7021       case MachinePlaysBlack:
7022         /* User is moving for White */
7023         if (!WhiteOnMove(currentMove)) {
7024             DisplayMoveError(_("It is Black's turn"));
7025             return;
7026         }
7027         break;
7028
7029       case PlayFromGameFile:
7030             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7031       case EditGame:
7032       case IcsExamining:
7033       case BeginningOfGame:
7034       case AnalyzeMode:
7035       case Training:
7036         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7037         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7038             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7039             /* User is moving for Black */
7040             if (WhiteOnMove(currentMove)) {
7041                 DisplayMoveError(_("It is White's turn"));
7042                 return;
7043             }
7044         } else {
7045             /* User is moving for White */
7046             if (!WhiteOnMove(currentMove)) {
7047                 DisplayMoveError(_("It is Black's turn"));
7048                 return;
7049             }
7050         }
7051         break;
7052
7053       case IcsPlayingBlack:
7054         /* User is moving for Black */
7055         if (WhiteOnMove(currentMove)) {
7056             if (!appData.premove) {
7057                 DisplayMoveError(_("It is White's turn"));
7058             } else if (toX >= 0 && toY >= 0) {
7059                 premoveToX = toX;
7060                 premoveToY = toY;
7061                 premoveFromX = fromX;
7062                 premoveFromY = fromY;
7063                 premovePromoChar = promoChar;
7064                 gotPremove = 1;
7065                 if (appData.debugMode)
7066                     fprintf(debugFP, "Got premove: fromX %d,"
7067                             "fromY %d, toX %d, toY %d\n",
7068                             fromX, fromY, toX, toY);
7069             }
7070             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7071             return;
7072         }
7073         break;
7074
7075       case IcsPlayingWhite:
7076         /* User is moving for White */
7077         if (!WhiteOnMove(currentMove)) {
7078             if (!appData.premove) {
7079                 DisplayMoveError(_("It is Black's turn"));
7080             } else if (toX >= 0 && toY >= 0) {
7081                 premoveToX = toX;
7082                 premoveToY = toY;
7083                 premoveFromX = fromX;
7084                 premoveFromY = fromY;
7085                 premovePromoChar = promoChar;
7086                 gotPremove = 1;
7087                 if (appData.debugMode)
7088                     fprintf(debugFP, "Got premove: fromX %d,"
7089                             "fromY %d, toX %d, toY %d\n",
7090                             fromX, fromY, toX, toY);
7091             }
7092             DrawPosition(TRUE, boards[currentMove]);
7093             return;
7094         }
7095         break;
7096
7097       default:
7098         break;
7099
7100       case EditPosition:
7101         /* EditPosition, empty square, or different color piece;
7102            click-click move is possible */
7103         if (toX == -2 || toY == -2) {
7104             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7105             DrawPosition(FALSE, boards[currentMove]);
7106             return;
7107         } else if (toX >= 0 && toY >= 0) {
7108             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7109                 ChessSquare p = boards[0][rf][ff];
7110                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7111                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7112             }
7113             boards[0][toY][toX] = boards[0][fromY][fromX];
7114             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7115                 if(boards[0][fromY][0] != EmptySquare) {
7116                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7117                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7118                 }
7119             } else
7120             if(fromX == BOARD_RGHT+1) {
7121                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7122                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7123                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7124                 }
7125             } else
7126             boards[0][fromY][fromX] = gatingPiece;
7127             ClearHighlights();
7128             DrawPosition(FALSE, boards[currentMove]);
7129             return;
7130         }
7131         return;
7132     }
7133
7134     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7135     pup = boards[currentMove][toY][toX];
7136
7137     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7138     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7139          if( pup != EmptySquare ) return;
7140          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7141            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7142                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7143            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7144            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7145            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7146            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7147          fromY = DROP_RANK;
7148     }
7149
7150     /* [HGM] always test for legality, to get promotion info */
7151     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7152                                          fromY, fromX, toY, toX, promoChar);
7153
7154     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7155
7156     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7157
7158     /* [HGM] but possibly ignore an IllegalMove result */
7159     if (appData.testLegality) {
7160         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7161             DisplayMoveError(_("Illegal move"));
7162             return;
7163         }
7164     }
7165
7166     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7167         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7168              ClearPremoveHighlights(); // was included
7169         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7170         return;
7171     }
7172
7173     if(addToBookFlag) { // adding moves to book
7174         char buf[MSG_SIZ], move[MSG_SIZ];
7175         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7176         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7177                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7178         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7179         AddBookMove(buf);
7180         addToBookFlag = FALSE;
7181         ClearHighlights();
7182         return;
7183     }
7184
7185     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7186 }
7187
7188 /* Common tail of UserMoveEvent and DropMenuEvent */
7189 int
7190 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7191 {
7192     char *bookHit = 0;
7193
7194     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7195         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7196         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7197         if(WhiteOnMove(currentMove)) {
7198             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7199         } else {
7200             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7201         }
7202     }
7203
7204     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7205        move type in caller when we know the move is a legal promotion */
7206     if(moveType == NormalMove && promoChar)
7207         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7208
7209     /* [HGM] <popupFix> The following if has been moved here from
7210        UserMoveEvent(). Because it seemed to belong here (why not allow
7211        piece drops in training games?), and because it can only be
7212        performed after it is known to what we promote. */
7213     if (gameMode == Training) {
7214       /* compare the move played on the board to the next move in the
7215        * game. If they match, display the move and the opponent's response.
7216        * If they don't match, display an error message.
7217        */
7218       int saveAnimate;
7219       Board testBoard;
7220       CopyBoard(testBoard, boards[currentMove]);
7221       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7222
7223       if (CompareBoards(testBoard, boards[currentMove+1])) {
7224         ForwardInner(currentMove+1);
7225
7226         /* Autoplay the opponent's response.
7227          * if appData.animate was TRUE when Training mode was entered,
7228          * the response will be animated.
7229          */
7230         saveAnimate = appData.animate;
7231         appData.animate = animateTraining;
7232         ForwardInner(currentMove+1);
7233         appData.animate = saveAnimate;
7234
7235         /* check for the end of the game */
7236         if (currentMove >= forwardMostMove) {
7237           gameMode = PlayFromGameFile;
7238           ModeHighlight();
7239           SetTrainingModeOff();
7240           DisplayInformation(_("End of game"));
7241         }
7242       } else {
7243         DisplayError(_("Incorrect move"), 0);
7244       }
7245       return 1;
7246     }
7247
7248   /* Ok, now we know that the move is good, so we can kill
7249      the previous line in Analysis Mode */
7250   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7251                                 && currentMove < forwardMostMove) {
7252     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7253     else forwardMostMove = currentMove;
7254   }
7255
7256   ClearMap();
7257
7258   /* If we need the chess program but it's dead, restart it */
7259   ResurrectChessProgram();
7260
7261   /* A user move restarts a paused game*/
7262   if (pausing)
7263     PauseEvent();
7264
7265   thinkOutput[0] = NULLCHAR;
7266
7267   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7268
7269   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7270     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7271     return 1;
7272   }
7273
7274   if (gameMode == BeginningOfGame) {
7275     if (appData.noChessProgram) {
7276       gameMode = EditGame;
7277       SetGameInfo();
7278     } else {
7279       char buf[MSG_SIZ];
7280       gameMode = MachinePlaysBlack;
7281       StartClocks();
7282       SetGameInfo();
7283       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7284       DisplayTitle(buf);
7285       if (first.sendName) {
7286         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7287         SendToProgram(buf, &first);
7288       }
7289       StartClocks();
7290     }
7291     ModeHighlight();
7292   }
7293
7294   /* Relay move to ICS or chess engine */
7295   if (appData.icsActive) {
7296     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7297         gameMode == IcsExamining) {
7298       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7299         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7300         SendToICS("draw ");
7301         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7302       }
7303       // also send plain move, in case ICS does not understand atomic claims
7304       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7305       ics_user_moved = 1;
7306     }
7307   } else {
7308     if (first.sendTime && (gameMode == BeginningOfGame ||
7309                            gameMode == MachinePlaysWhite ||
7310                            gameMode == MachinePlaysBlack)) {
7311       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7312     }
7313     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7314          // [HGM] book: if program might be playing, let it use book
7315         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7316         first.maybeThinking = TRUE;
7317     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7318         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7319         SendBoard(&first, currentMove+1);
7320         if(second.analyzing) {
7321             if(!second.useSetboard) SendToProgram("undo\n", &second);
7322             SendBoard(&second, currentMove+1);
7323         }
7324     } else {
7325         SendMoveToProgram(forwardMostMove-1, &first);
7326         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7327     }
7328     if (currentMove == cmailOldMove + 1) {
7329       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7330     }
7331   }
7332
7333   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7334
7335   switch (gameMode) {
7336   case EditGame:
7337     if(appData.testLegality)
7338     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7339     case MT_NONE:
7340     case MT_CHECK:
7341       break;
7342     case MT_CHECKMATE:
7343     case MT_STAINMATE:
7344       if (WhiteOnMove(currentMove)) {
7345         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7346       } else {
7347         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7348       }
7349       break;
7350     case MT_STALEMATE:
7351       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7352       break;
7353     }
7354     break;
7355
7356   case MachinePlaysBlack:
7357   case MachinePlaysWhite:
7358     /* disable certain menu options while machine is thinking */
7359     SetMachineThinkingEnables();
7360     break;
7361
7362   default:
7363     break;
7364   }
7365
7366   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7367   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7368
7369   if(bookHit) { // [HGM] book: simulate book reply
7370         static char bookMove[MSG_SIZ]; // a bit generous?
7371
7372         programStats.nodes = programStats.depth = programStats.time =
7373         programStats.score = programStats.got_only_move = 0;
7374         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7375
7376         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7377         strcat(bookMove, bookHit);
7378         HandleMachineMove(bookMove, &first);
7379   }
7380   return 1;
7381 }
7382
7383 void
7384 MarkByFEN(char *fen)
7385 {
7386         int r, f;
7387         if(!appData.markers || !appData.highlightDragging) return;
7388         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7389         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7390         while(*fen) {
7391             int s = 0;
7392             marker[r][f] = 0;
7393             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7394             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7395             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7396             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7397             if(*fen == 'T') marker[r][f++] = 0; else
7398             if(*fen == 'Y') marker[r][f++] = 1; else
7399             if(*fen == 'G') marker[r][f++] = 3; else
7400             if(*fen == 'B') marker[r][f++] = 4; else
7401             if(*fen == 'C') marker[r][f++] = 5; else
7402             if(*fen == 'M') marker[r][f++] = 6; else
7403             if(*fen == 'W') marker[r][f++] = 7; else
7404             if(*fen == 'D') marker[r][f++] = 8; else
7405             if(*fen == 'R') marker[r][f++] = 2; else {
7406                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7407               f += s; fen -= s>0;
7408             }
7409             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7410             if(r < 0) break;
7411             fen++;
7412         }
7413         DrawPosition(TRUE, NULL);
7414 }
7415
7416 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7417
7418 void
7419 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7420 {
7421     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7422     Markers *m = (Markers *) closure;
7423     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7424                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7425         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7426                          || kind == WhiteCapturesEnPassant
7427                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7428     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7429 }
7430
7431 static int hoverSavedValid;
7432
7433 void
7434 MarkTargetSquares (int clear)
7435 {
7436   int x, y, sum=0;
7437   if(clear) { // no reason to ever suppress clearing
7438     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7439     hoverSavedValid = 0;
7440     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7441   } else {
7442     int capt = 0;
7443     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7444        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7445     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7446     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7447       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7448       if(capt)
7449       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7450     }
7451   }
7452   DrawPosition(FALSE, NULL);
7453 }
7454
7455 int
7456 Explode (Board board, int fromX, int fromY, int toX, int toY)
7457 {
7458     if(gameInfo.variant == VariantAtomic &&
7459        (board[toY][toX] != EmptySquare ||                     // capture?
7460         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7461                          board[fromY][fromX] == BlackPawn   )
7462       )) {
7463         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7464         return TRUE;
7465     }
7466     return FALSE;
7467 }
7468
7469 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7470
7471 int
7472 CanPromote (ChessSquare piece, int y)
7473 {
7474         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7475         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7476         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7477         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7478            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7479           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7480            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7481         return (piece == BlackPawn && y <= zone ||
7482                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7483                 piece == BlackLance && y <= zone ||
7484                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7485 }
7486
7487 void
7488 HoverEvent (int xPix, int yPix, int x, int y)
7489 {
7490         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7491         int r, f;
7492         if(!first.highlight) return;
7493         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7494         if(x == oldX && y == oldY) return; // only do something if we enter new square
7495         oldFromX = fromX; oldFromY = fromY;
7496         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7497           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7498             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7499           hoverSavedValid = 1;
7500         } else if(oldX != x || oldY != y) {
7501           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7502           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7503           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7504             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7505           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7506             char buf[MSG_SIZ];
7507             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7508             SendToProgram(buf, &first);
7509           }
7510           oldX = x; oldY = y;
7511 //        SetHighlights(fromX, fromY, x, y);
7512         }
7513 }
7514
7515 void ReportClick(char *action, int x, int y)
7516 {
7517         char buf[MSG_SIZ]; // Inform engine of what user does
7518         int r, f;
7519         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7520           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7521             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7522         if(!first.highlight || gameMode == EditPosition) return;
7523         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7524         SendToProgram(buf, &first);
7525 }
7526
7527 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7528
7529 void
7530 LeftClick (ClickType clickType, int xPix, int yPix)
7531 {
7532     int x, y;
7533     Boolean saveAnimate;
7534     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7535     char promoChoice = NULLCHAR;
7536     ChessSquare piece;
7537     static TimeMark lastClickTime, prevClickTime;
7538
7539     if(flashing) return;
7540
7541     x = EventToSquare(xPix, BOARD_WIDTH);
7542     y = EventToSquare(yPix, BOARD_HEIGHT);
7543     if (!flipView && y >= 0) {
7544         y = BOARD_HEIGHT - 1 - y;
7545     }
7546     if (flipView && x >= 0) {
7547         x = BOARD_WIDTH - 1 - x;
7548     }
7549
7550     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7551         static int dummy;
7552         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7553         right = TRUE;
7554         return;
7555     }
7556
7557     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7558
7559     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7560
7561     if (clickType == Press) ErrorPopDown();
7562     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7563
7564     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7565         defaultPromoChoice = promoSweep;
7566         promoSweep = EmptySquare;   // terminate sweep
7567         promoDefaultAltered = TRUE;
7568         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7569     }
7570
7571     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7572         if(clickType == Release) return; // ignore upclick of click-click destination
7573         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7574         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7575         if(gameInfo.holdingsWidth &&
7576                 (WhiteOnMove(currentMove)
7577                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7578                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7579             // click in right holdings, for determining promotion piece
7580             ChessSquare p = boards[currentMove][y][x];
7581             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7582             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7583             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7584                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7585                 fromX = fromY = -1;
7586                 return;
7587             }
7588         }
7589         DrawPosition(FALSE, boards[currentMove]);
7590         return;
7591     }
7592
7593     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7594     if(clickType == Press
7595             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7596               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7597               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7598         return;
7599
7600     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7601         // could be static click on premove from-square: abort premove
7602         gotPremove = 0;
7603         ClearPremoveHighlights();
7604     }
7605
7606     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7607         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7608
7609     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7610         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7611                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7612         defaultPromoChoice = DefaultPromoChoice(side);
7613     }
7614
7615     autoQueen = appData.alwaysPromoteToQueen;
7616
7617     if (fromX == -1) {
7618       int originalY = y;
7619       gatingPiece = EmptySquare;
7620       if (clickType != Press) {
7621         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7622             DragPieceEnd(xPix, yPix); dragging = 0;
7623             DrawPosition(FALSE, NULL);
7624         }
7625         return;
7626       }
7627       doubleClick = FALSE;
7628       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7629         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7630       }
7631       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7632       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7633          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7634          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7635             /* First square */
7636             if (OKToStartUserMove(fromX, fromY)) {
7637                 second = 0;
7638                 ReportClick("lift", x, y);
7639                 MarkTargetSquares(0);
7640                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7641                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7642                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7643                     promoSweep = defaultPromoChoice;
7644                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7645                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7646                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7647                 }
7648                 if (appData.highlightDragging) {
7649                     SetHighlights(fromX, fromY, -1, -1);
7650                 } else {
7651                     ClearHighlights();
7652                 }
7653             } else fromX = fromY = -1;
7654             return;
7655         }
7656     }
7657
7658     /* fromX != -1 */
7659     if (clickType == Press && gameMode != EditPosition) {
7660         ChessSquare fromP;
7661         ChessSquare toP;
7662         int frc;
7663
7664         // ignore off-board to clicks
7665         if(y < 0 || x < 0) return;
7666
7667         /* Check if clicking again on the same color piece */
7668         fromP = boards[currentMove][fromY][fromX];
7669         toP = boards[currentMove][y][x];
7670         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7671         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7672             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7673            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7674              WhitePawn <= toP && toP <= WhiteKing &&
7675              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7676              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7677             (BlackPawn <= fromP && fromP <= BlackKing &&
7678              BlackPawn <= toP && toP <= BlackKing &&
7679              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7680              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7681             /* Clicked again on same color piece -- changed his mind */
7682             second = (x == fromX && y == fromY);
7683             killX = killY = kill2X = kill2Y = -1;
7684             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7685                 second = FALSE; // first double-click rather than scond click
7686                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7687             }
7688             promoDefaultAltered = FALSE;
7689            if(!second) MarkTargetSquares(1);
7690            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7691             if (appData.highlightDragging) {
7692                 SetHighlights(x, y, -1, -1);
7693             } else {
7694                 ClearHighlights();
7695             }
7696             if (OKToStartUserMove(x, y)) {
7697                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7698                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7699                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7700                  gatingPiece = boards[currentMove][fromY][fromX];
7701                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7702                 fromX = x;
7703                 fromY = y; dragging = 1;
7704                 if(!second) ReportClick("lift", x, y);
7705                 MarkTargetSquares(0);
7706                 DragPieceBegin(xPix, yPix, FALSE);
7707                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7708                     promoSweep = defaultPromoChoice;
7709                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7710                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7711                 }
7712             }
7713            }
7714            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7715            second = FALSE;
7716         }
7717         // ignore clicks on holdings
7718         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7719     }
7720
7721     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7722         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7723         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7724         return;
7725     }
7726
7727     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7728         DragPieceEnd(xPix, yPix); dragging = 0;
7729         if(clearFlag) {
7730             // a deferred attempt to click-click move an empty square on top of a piece
7731             boards[currentMove][y][x] = EmptySquare;
7732             ClearHighlights();
7733             DrawPosition(FALSE, boards[currentMove]);
7734             fromX = fromY = -1; clearFlag = 0;
7735             return;
7736         }
7737         if (appData.animateDragging) {
7738             /* Undo animation damage if any */
7739             DrawPosition(FALSE, NULL);
7740         }
7741         if (second) {
7742             /* Second up/down in same square; just abort move */
7743             second = 0;
7744             fromX = fromY = -1;
7745             gatingPiece = EmptySquare;
7746             ClearHighlights();
7747             gotPremove = 0;
7748             ClearPremoveHighlights();
7749             MarkTargetSquares(-1);
7750             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7751         } else {
7752             /* First upclick in same square; start click-click mode */
7753             SetHighlights(x, y, -1, -1);
7754         }
7755         return;
7756     }
7757
7758     clearFlag = 0;
7759
7760     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7761        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7762         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7763         DisplayMessage(_("only marked squares are legal"),"");
7764         DrawPosition(TRUE, NULL);
7765         return; // ignore to-click
7766     }
7767
7768     /* we now have a different from- and (possibly off-board) to-square */
7769     /* Completed move */
7770     if(!sweepSelecting) {
7771         toX = x;
7772         toY = y;
7773     }
7774
7775     piece = boards[currentMove][fromY][fromX];
7776
7777     saveAnimate = appData.animate;
7778     if (clickType == Press) {
7779         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7780         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7781             // must be Edit Position mode with empty-square selected
7782             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7783             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7784             return;
7785         }
7786         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7787             return;
7788         }
7789         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7790             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7791         } else
7792         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7793         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7794           if(appData.sweepSelect) {
7795             promoSweep = defaultPromoChoice;
7796             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7797             selectFlag = 0; lastX = xPix; lastY = yPix;
7798             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7799             saveFlash = appData.flashCount; appData.flashCount = 0;
7800             Sweep(0); // Pawn that is going to promote: preview promotion piece
7801             sweepSelecting = 1;
7802             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7803             MarkTargetSquares(1);
7804           }
7805           return; // promo popup appears on up-click
7806         }
7807         /* Finish clickclick move */
7808         if (appData.animate || appData.highlightLastMove) {
7809             SetHighlights(fromX, fromY, toX, toY);
7810         } else {
7811             ClearHighlights();
7812         }
7813         MarkTargetSquares(1);
7814     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7815         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7816         *promoRestrict = 0; appData.flashCount = saveFlash;
7817         if (appData.animate || appData.highlightLastMove) {
7818             SetHighlights(fromX, fromY, toX, toY);
7819         } else {
7820             ClearHighlights();
7821         }
7822         MarkTargetSquares(1);
7823     } else {
7824 #if 0
7825 // [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
7826         /* Finish drag move */
7827         if (appData.highlightLastMove) {
7828             SetHighlights(fromX, fromY, toX, toY);
7829         } else {
7830             ClearHighlights();
7831         }
7832 #endif
7833         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7834           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7835         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7836         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7837           dragging *= 2;            // flag button-less dragging if we are dragging
7838           MarkTargetSquares(1);
7839           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7840           else {
7841             kill2X = killX; kill2Y = killY;
7842             killX = x; killY = y;     // remember this square as intermediate
7843             ReportClick("put", x, y); // and inform engine
7844             ReportClick("lift", x, y);
7845             MarkTargetSquares(0);
7846             return;
7847           }
7848         }
7849         DragPieceEnd(xPix, yPix); dragging = 0;
7850         /* Don't animate move and drag both */
7851         appData.animate = FALSE;
7852         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7853     }
7854
7855     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7856     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7857         ChessSquare piece = boards[currentMove][fromY][fromX];
7858         if(gameMode == EditPosition && piece != EmptySquare &&
7859            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7860             int n;
7861
7862             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7863                 n = PieceToNumber(piece - (int)BlackPawn);
7864                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7865                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7866                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7867             } else
7868             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7869                 n = PieceToNumber(piece);
7870                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7871                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7872                 boards[currentMove][n][BOARD_WIDTH-2]++;
7873             }
7874             boards[currentMove][fromY][fromX] = EmptySquare;
7875         }
7876         ClearHighlights();
7877         fromX = fromY = -1;
7878         MarkTargetSquares(1);
7879         DrawPosition(TRUE, boards[currentMove]);
7880         return;
7881     }
7882
7883     // off-board moves should not be highlighted
7884     if(x < 0 || y < 0) ClearHighlights();
7885     else ReportClick("put", x, y);
7886
7887     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7888
7889     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7890
7891     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7892         SetHighlights(fromX, fromY, toX, toY);
7893         MarkTargetSquares(1);
7894         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7895             // [HGM] super: promotion to captured piece selected from holdings
7896             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7897             promotionChoice = TRUE;
7898             // kludge follows to temporarily execute move on display, without promoting yet
7899             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7900             boards[currentMove][toY][toX] = p;
7901             DrawPosition(FALSE, boards[currentMove]);
7902             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7903             boards[currentMove][toY][toX] = q;
7904             DisplayMessage("Click in holdings to choose piece", "");
7905             return;
7906         }
7907         PromotionPopUp(promoChoice);
7908     } else {
7909         int oldMove = currentMove;
7910         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7911         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7912         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7913         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7914         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7915            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7916             DrawPosition(TRUE, boards[currentMove]);
7917         fromX = fromY = -1;
7918         flashing = 0;
7919     }
7920     appData.animate = saveAnimate;
7921     if (appData.animate || appData.animateDragging) {
7922         /* Undo animation damage if needed */
7923 //      DrawPosition(FALSE, NULL);
7924     }
7925 }
7926
7927 int
7928 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7929 {   // front-end-free part taken out of PieceMenuPopup
7930     int whichMenu; int xSqr, ySqr;
7931
7932     if(seekGraphUp) { // [HGM] seekgraph
7933         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7934         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7935         return -2;
7936     }
7937
7938     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7939          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7940         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7941         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7942         if(action == Press)   {
7943             originalFlip = flipView;
7944             flipView = !flipView; // temporarily flip board to see game from partners perspective
7945             DrawPosition(TRUE, partnerBoard);
7946             DisplayMessage(partnerStatus, "");
7947             partnerUp = TRUE;
7948         } else if(action == Release) {
7949             flipView = originalFlip;
7950             DrawPosition(TRUE, boards[currentMove]);
7951             partnerUp = FALSE;
7952         }
7953         return -2;
7954     }
7955
7956     xSqr = EventToSquare(x, BOARD_WIDTH);
7957     ySqr = EventToSquare(y, BOARD_HEIGHT);
7958     if (action == Release) {
7959         if(pieceSweep != EmptySquare) {
7960             EditPositionMenuEvent(pieceSweep, toX, toY);
7961             pieceSweep = EmptySquare;
7962         } else UnLoadPV(); // [HGM] pv
7963     }
7964     if (action != Press) return -2; // return code to be ignored
7965     switch (gameMode) {
7966       case IcsExamining:
7967         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7968       case EditPosition:
7969         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7970         if (xSqr < 0 || ySqr < 0) return -1;
7971         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7972         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7973         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7974         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7975         NextPiece(0);
7976         return 2; // grab
7977       case IcsObserving:
7978         if(!appData.icsEngineAnalyze) return -1;
7979       case IcsPlayingWhite:
7980       case IcsPlayingBlack:
7981         if(!appData.zippyPlay) goto noZip;
7982       case AnalyzeMode:
7983       case AnalyzeFile:
7984       case MachinePlaysWhite:
7985       case MachinePlaysBlack:
7986       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7987         if (!appData.dropMenu) {
7988           LoadPV(x, y);
7989           return 2; // flag front-end to grab mouse events
7990         }
7991         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7992            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7993       case EditGame:
7994       noZip:
7995         if (xSqr < 0 || ySqr < 0) return -1;
7996         if (!appData.dropMenu || appData.testLegality &&
7997             gameInfo.variant != VariantBughouse &&
7998             gameInfo.variant != VariantCrazyhouse) return -1;
7999         whichMenu = 1; // drop menu
8000         break;
8001       default:
8002         return -1;
8003     }
8004
8005     if (((*fromX = xSqr) < 0) ||
8006         ((*fromY = ySqr) < 0)) {
8007         *fromX = *fromY = -1;
8008         return -1;
8009     }
8010     if (flipView)
8011       *fromX = BOARD_WIDTH - 1 - *fromX;
8012     else
8013       *fromY = BOARD_HEIGHT - 1 - *fromY;
8014
8015     return whichMenu;
8016 }
8017
8018 void
8019 Wheel (int dir, int x, int y)
8020 {
8021     if(gameMode == EditPosition) {
8022         int xSqr = EventToSquare(x, BOARD_WIDTH);
8023         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8024         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8025         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8026         do {
8027             boards[currentMove][ySqr][xSqr] += dir;
8028             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8029             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8030         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8031         DrawPosition(FALSE, boards[currentMove]);
8032     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8033 }
8034
8035 void
8036 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8037 {
8038 //    char * hint = lastHint;
8039     FrontEndProgramStats stats;
8040
8041     stats.which = cps == &first ? 0 : 1;
8042     stats.depth = cpstats->depth;
8043     stats.nodes = cpstats->nodes;
8044     stats.score = cpstats->score;
8045     stats.time = cpstats->time;
8046     stats.pv = cpstats->movelist;
8047     stats.hint = lastHint;
8048     stats.an_move_index = 0;
8049     stats.an_move_count = 0;
8050
8051     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8052         stats.hint = cpstats->move_name;
8053         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8054         stats.an_move_count = cpstats->nr_moves;
8055     }
8056
8057     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
8058
8059     SetProgramStats( &stats );
8060 }
8061
8062 void
8063 ClearEngineOutputPane (int which)
8064 {
8065     static FrontEndProgramStats dummyStats;
8066     dummyStats.which = which;
8067     dummyStats.pv = "#";
8068     SetProgramStats( &dummyStats );
8069 }
8070
8071 #define MAXPLAYERS 500
8072
8073 char *
8074 TourneyStandings (int display)
8075 {
8076     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8077     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8078     char result, *p, *names[MAXPLAYERS];
8079
8080     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8081         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8082     names[0] = p = strdup(appData.participants);
8083     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8084
8085     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8086
8087     while(result = appData.results[nr]) {
8088         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8089         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8090         wScore = bScore = 0;
8091         switch(result) {
8092           case '+': wScore = 2; break;
8093           case '-': bScore = 2; break;
8094           case '=': wScore = bScore = 1; break;
8095           case ' ':
8096           case '*': return strdup("busy"); // tourney not finished
8097         }
8098         score[w] += wScore;
8099         score[b] += bScore;
8100         games[w]++;
8101         games[b]++;
8102         nr++;
8103     }
8104     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8105     for(w=0; w<nPlayers; w++) {
8106         bScore = -1;
8107         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8108         ranking[w] = b; points[w] = bScore; score[b] = -2;
8109     }
8110     p = malloc(nPlayers*34+1);
8111     for(w=0; w<nPlayers && w<display; w++)
8112         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8113     free(names[0]);
8114     return p;
8115 }
8116
8117 void
8118 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8119 {       // count all piece types
8120         int p, f, r;
8121         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8122         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8123         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8124                 p = board[r][f];
8125                 pCnt[p]++;
8126                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8127                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8128                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8129                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8130                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8131                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8132         }
8133 }
8134
8135 int
8136 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8137 {
8138         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8139         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8140
8141         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8142         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8143         if(myPawns == 2 && nMine == 3) // KPP
8144             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8145         if(myPawns == 1 && nMine == 2) // KP
8146             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8147         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8148             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8149         if(myPawns) return FALSE;
8150         if(pCnt[WhiteRook+side])
8151             return pCnt[BlackRook-side] ||
8152                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8153                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8154                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8155         if(pCnt[WhiteCannon+side]) {
8156             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8157             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8158         }
8159         if(pCnt[WhiteKnight+side])
8160             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8161         return FALSE;
8162 }
8163
8164 int
8165 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8166 {
8167         VariantClass v = gameInfo.variant;
8168
8169         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8170         if(v == VariantShatranj) return TRUE; // always winnable through baring
8171         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8172         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8173
8174         if(v == VariantXiangqi) {
8175                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8176
8177                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8178                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8179                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8180                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8181                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8182                 if(stale) // we have at least one last-rank P plus perhaps C
8183                     return majors // KPKX
8184                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8185                 else // KCA*E*
8186                     return pCnt[WhiteFerz+side] // KCAK
8187                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8188                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8189                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8190
8191         } else if(v == VariantKnightmate) {
8192                 if(nMine == 1) return FALSE;
8193                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8194         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8195                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8196
8197                 if(nMine == 1) return FALSE; // bare King
8198                 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
8199                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8200                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8201                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8202                 if(pCnt[WhiteKnight+side])
8203                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8204                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8205                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8206                 if(nBishops)
8207                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8208                 if(pCnt[WhiteAlfil+side])
8209                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8210                 if(pCnt[WhiteWazir+side])
8211                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8212         }
8213
8214         return TRUE;
8215 }
8216
8217 int
8218 CompareWithRights (Board b1, Board b2)
8219 {
8220     int rights = 0;
8221     if(!CompareBoards(b1, b2)) return FALSE;
8222     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8223     /* compare castling rights */
8224     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8225            rights++; /* King lost rights, while rook still had them */
8226     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8227         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8228            rights++; /* but at least one rook lost them */
8229     }
8230     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8231            rights++;
8232     if( b1[CASTLING][5] != NoRights ) {
8233         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8234            rights++;
8235     }
8236     return rights == 0;
8237 }
8238
8239 int
8240 Adjudicate (ChessProgramState *cps)
8241 {       // [HGM] some adjudications useful with buggy engines
8242         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8243         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8244         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8245         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8246         int k, drop, count = 0; static int bare = 1;
8247         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8248         Boolean canAdjudicate = !appData.icsActive;
8249
8250         // most tests only when we understand the game, i.e. legality-checking on
8251             if( appData.testLegality )
8252             {   /* [HGM] Some more adjudications for obstinate engines */
8253                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8254                 static int moveCount = 6;
8255                 ChessMove result;
8256                 char *reason = NULL;
8257
8258                 /* Count what is on board. */
8259                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8260
8261                 /* Some material-based adjudications that have to be made before stalemate test */
8262                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8263                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8264                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8265                      if(canAdjudicate && appData.checkMates) {
8266                          if(engineOpponent)
8267                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8268                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8269                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8270                          return 1;
8271                      }
8272                 }
8273
8274                 /* Bare King in Shatranj (loses) or Losers (wins) */
8275                 if( nrW == 1 || nrB == 1) {
8276                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8277                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8278                      if(canAdjudicate && appData.checkMates) {
8279                          if(engineOpponent)
8280                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8281                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8282                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8283                          return 1;
8284                      }
8285                   } else
8286                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8287                   {    /* bare King */
8288                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8289                         if(canAdjudicate && appData.checkMates) {
8290                             /* but only adjudicate if adjudication enabled */
8291                             if(engineOpponent)
8292                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8293                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8294                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8295                             return 1;
8296                         }
8297                   }
8298                 } else bare = 1;
8299
8300
8301             // don't wait for engine to announce game end if we can judge ourselves
8302             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8303               case MT_CHECK:
8304                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8305                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8306                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8307                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8308                             checkCnt++;
8309                         if(checkCnt >= 2) {
8310                             reason = "Xboard adjudication: 3rd check";
8311                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8312                             break;
8313                         }
8314                     }
8315                 }
8316               case MT_NONE:
8317               default:
8318                 break;
8319               case MT_STEALMATE:
8320               case MT_STALEMATE:
8321               case MT_STAINMATE:
8322                 reason = "Xboard adjudication: Stalemate";
8323                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8324                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8325                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8326                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8327                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8328                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8329                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8330                                                                         EP_CHECKMATE : EP_WINS);
8331                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8332                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8333                 }
8334                 break;
8335               case MT_CHECKMATE:
8336                 reason = "Xboard adjudication: Checkmate";
8337                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8338                 if(gameInfo.variant == VariantShogi) {
8339                     if(forwardMostMove > backwardMostMove
8340                        && moveList[forwardMostMove-1][1] == '@'
8341                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8342                         reason = "XBoard adjudication: pawn-drop mate";
8343                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8344                     }
8345                 }
8346                 break;
8347             }
8348
8349                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8350                     case EP_STALEMATE:
8351                         result = GameIsDrawn; break;
8352                     case EP_CHECKMATE:
8353                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8354                     case EP_WINS:
8355                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8356                     default:
8357                         result = EndOfFile;
8358                 }
8359                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8360                     if(engineOpponent)
8361                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8362                     GameEnds( result, reason, GE_XBOARD );
8363                     return 1;
8364                 }
8365
8366                 /* Next absolutely insufficient mating material. */
8367                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8368                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8369                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8370
8371                      /* always flag draws, for judging claims */
8372                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8373
8374                      if(canAdjudicate && appData.materialDraws) {
8375                          /* but only adjudicate them if adjudication enabled */
8376                          if(engineOpponent) {
8377                            SendToProgram("force\n", engineOpponent); // suppress reply
8378                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8379                          }
8380                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8381                          return 1;
8382                      }
8383                 }
8384
8385                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8386                 if(gameInfo.variant == VariantXiangqi ?
8387                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8388                  : nrW + nrB == 4 &&
8389                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8390                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8391                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8392                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8393                    ) ) {
8394                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8395                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8396                           if(engineOpponent) {
8397                             SendToProgram("force\n", engineOpponent); // suppress reply
8398                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8399                           }
8400                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8401                           return 1;
8402                      }
8403                 } else moveCount = 6;
8404             }
8405
8406         // Repetition draws and 50-move rule can be applied independently of legality testing
8407
8408                 /* Check for rep-draws */
8409                 count = 0;
8410                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8411                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8412                 for(k = forwardMostMove-2;
8413                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8414                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8415                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8416                     k-=2)
8417                 {   int rights=0;
8418                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8419                         /* compare castling rights */
8420                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8421                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8422                                 rights++; /* King lost rights, while rook still had them */
8423                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8424                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8425                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8426                                    rights++; /* but at least one rook lost them */
8427                         }
8428                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8429                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8430                                 rights++;
8431                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8432                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8433                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8434                                    rights++;
8435                         }
8436                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8437                             && appData.drawRepeats > 1) {
8438                              /* adjudicate after user-specified nr of repeats */
8439                              int result = GameIsDrawn;
8440                              char *details = "XBoard adjudication: repetition draw";
8441                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8442                                 // [HGM] xiangqi: check for forbidden perpetuals
8443                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8444                                 for(m=forwardMostMove; m>k; m-=2) {
8445                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8446                                         ourPerpetual = 0; // the current mover did not always check
8447                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8448                                         hisPerpetual = 0; // the opponent did not always check
8449                                 }
8450                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8451                                                                         ourPerpetual, hisPerpetual);
8452                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8453                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8454                                     details = "Xboard adjudication: perpetual checking";
8455                                 } else
8456                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8457                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8458                                 } else
8459                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8460                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8461                                         result = BlackWins;
8462                                         details = "Xboard adjudication: repetition";
8463                                     }
8464                                 } else // it must be XQ
8465                                 // Now check for perpetual chases
8466                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8467                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8468                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8469                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8470                                         static char resdet[MSG_SIZ];
8471                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8472                                         details = resdet;
8473                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8474                                     } else
8475                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8476                                         break; // Abort repetition-checking loop.
8477                                 }
8478                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8479                              }
8480                              if(engineOpponent) {
8481                                SendToProgram("force\n", engineOpponent); // suppress reply
8482                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8483                              }
8484                              GameEnds( result, details, GE_XBOARD );
8485                              return 1;
8486                         }
8487                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8488                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8489                     }
8490                 }
8491
8492                 /* Now we test for 50-move draws. Determine ply count */
8493                 count = forwardMostMove;
8494                 /* look for last irreversble move */
8495                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8496                     count--;
8497                 /* if we hit starting position, add initial plies */
8498                 if( count == backwardMostMove )
8499                     count -= initialRulePlies;
8500                 count = forwardMostMove - count;
8501                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8502                         // adjust reversible move counter for checks in Xiangqi
8503                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8504                         if(i < backwardMostMove) i = backwardMostMove;
8505                         while(i <= forwardMostMove) {
8506                                 lastCheck = inCheck; // check evasion does not count
8507                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8508                                 if(inCheck || lastCheck) count--; // check does not count
8509                                 i++;
8510                         }
8511                 }
8512                 if( count >= 100)
8513                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8514                          /* this is used to judge if draw claims are legal */
8515                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8516                          if(engineOpponent) {
8517                            SendToProgram("force\n", engineOpponent); // suppress reply
8518                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8519                          }
8520                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8521                          return 1;
8522                 }
8523
8524                 /* if draw offer is pending, treat it as a draw claim
8525                  * when draw condition present, to allow engines a way to
8526                  * claim draws before making their move to avoid a race
8527                  * condition occurring after their move
8528                  */
8529                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8530                          char *p = NULL;
8531                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8532                              p = "Draw claim: 50-move rule";
8533                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8534                              p = "Draw claim: 3-fold repetition";
8535                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8536                              p = "Draw claim: insufficient mating material";
8537                          if( p != NULL && canAdjudicate) {
8538                              if(engineOpponent) {
8539                                SendToProgram("force\n", engineOpponent); // suppress reply
8540                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8541                              }
8542                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8543                              return 1;
8544                          }
8545                 }
8546
8547                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8548                     if(engineOpponent) {
8549                       SendToProgram("force\n", engineOpponent); // suppress reply
8550                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8551                     }
8552                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8553                     return 1;
8554                 }
8555         return 0;
8556 }
8557
8558 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8559 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8560 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8561
8562 static int
8563 BitbaseProbe ()
8564 {
8565     int pieces[10], squares[10], cnt=0, r, f, res;
8566     static int loaded;
8567     static PPROBE_EGBB probeBB;
8568     if(!appData.testLegality) return 10;
8569     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8570     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8571     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8572     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8573         ChessSquare piece = boards[forwardMostMove][r][f];
8574         int black = (piece >= BlackPawn);
8575         int type = piece - black*BlackPawn;
8576         if(piece == EmptySquare) continue;
8577         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8578         if(type == WhiteKing) type = WhiteQueen + 1;
8579         type = egbbCode[type];
8580         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8581         pieces[cnt] = type + black*6;
8582         if(++cnt > 5) return 11;
8583     }
8584     pieces[cnt] = squares[cnt] = 0;
8585     // probe EGBB
8586     if(loaded == 2) return 13; // loading failed before
8587     if(loaded == 0) {
8588         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8589         HMODULE lib;
8590         PLOAD_EGBB loadBB;
8591         loaded = 2; // prepare for failure
8592         if(!path) return 13; // no egbb installed
8593         strncpy(buf, path + 8, MSG_SIZ);
8594         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8595         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8596         lib = LoadLibrary(buf);
8597         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8598         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8599         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8600         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8601         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8602         loaded = 1; // success!
8603     }
8604     res = probeBB(forwardMostMove & 1, pieces, squares);
8605     return res > 0 ? 1 : res < 0 ? -1 : 0;
8606 }
8607
8608 char *
8609 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8610 {   // [HGM] book: this routine intercepts moves to simulate book replies
8611     char *bookHit = NULL;
8612
8613     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8614         char buf[MSG_SIZ];
8615         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8616         SendToProgram(buf, cps);
8617     }
8618     //first determine if the incoming move brings opponent into his book
8619     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8620         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8621     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8622     if(bookHit != NULL && !cps->bookSuspend) {
8623         // make sure opponent is not going to reply after receiving move to book position
8624         SendToProgram("force\n", cps);
8625         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8626     }
8627     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8628     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8629     // now arrange restart after book miss
8630     if(bookHit) {
8631         // after a book hit we never send 'go', and the code after the call to this routine
8632         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8633         char buf[MSG_SIZ], *move = bookHit;
8634         if(cps->useSAN) {
8635             int fromX, fromY, toX, toY;
8636             char promoChar;
8637             ChessMove moveType;
8638             move = buf + 30;
8639             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8640                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8641                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8642                                     PosFlags(forwardMostMove),
8643                                     fromY, fromX, toY, toX, promoChar, move);
8644             } else {
8645                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8646                 bookHit = NULL;
8647             }
8648         }
8649         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8650         SendToProgram(buf, cps);
8651         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8652     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8653         SendToProgram("go\n", cps);
8654         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8655     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8656         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8657             SendToProgram("go\n", cps);
8658         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8659     }
8660     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8661 }
8662
8663 int
8664 LoadError (char *errmess, ChessProgramState *cps)
8665 {   // unloads engine and switches back to -ncp mode if it was first
8666     if(cps->initDone) return FALSE;
8667     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8668     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8669     cps->pr = NoProc;
8670     if(cps == &first) {
8671         appData.noChessProgram = TRUE;
8672         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8673         gameMode = BeginningOfGame; ModeHighlight();
8674         SetNCPMode();
8675     }
8676     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8677     DisplayMessage("", ""); // erase waiting message
8678     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8679     return TRUE;
8680 }
8681
8682 char *savedMessage;
8683 ChessProgramState *savedState;
8684 void
8685 DeferredBookMove (void)
8686 {
8687         if(savedState->lastPing != savedState->lastPong)
8688                     ScheduleDelayedEvent(DeferredBookMove, 10);
8689         else
8690         HandleMachineMove(savedMessage, savedState);
8691 }
8692
8693 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8694 static ChessProgramState *stalledEngine;
8695 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8696
8697 void
8698 HandleMachineMove (char *message, ChessProgramState *cps)
8699 {
8700     static char firstLeg[20], legs;
8701     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8702     char realname[MSG_SIZ];
8703     int fromX, fromY, toX, toY;
8704     ChessMove moveType;
8705     char promoChar, roar;
8706     char *p, *pv=buf1;
8707     int oldError;
8708     char *bookHit;
8709
8710     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8711         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8712         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8713             DisplayError(_("Invalid pairing from pairing engine"), 0);
8714             return;
8715         }
8716         pairingReceived = 1;
8717         NextMatchGame();
8718         return; // Skim the pairing messages here.
8719     }
8720
8721     oldError = cps->userError; cps->userError = 0;
8722
8723 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8724     /*
8725      * Kludge to ignore BEL characters
8726      */
8727     while (*message == '\007') message++;
8728
8729     /*
8730      * [HGM] engine debug message: ignore lines starting with '#' character
8731      */
8732     if(cps->debug && *message == '#') return;
8733
8734     /*
8735      * Look for book output
8736      */
8737     if (cps == &first && bookRequested) {
8738         if (message[0] == '\t' || message[0] == ' ') {
8739             /* Part of the book output is here; append it */
8740             strcat(bookOutput, message);
8741             strcat(bookOutput, "  \n");
8742             return;
8743         } else if (bookOutput[0] != NULLCHAR) {
8744             /* All of book output has arrived; display it */
8745             char *p = bookOutput;
8746             while (*p != NULLCHAR) {
8747                 if (*p == '\t') *p = ' ';
8748                 p++;
8749             }
8750             DisplayInformation(bookOutput);
8751             bookRequested = FALSE;
8752             /* Fall through to parse the current output */
8753         }
8754     }
8755
8756     /*
8757      * Look for machine move.
8758      */
8759     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8760         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8761     {
8762         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8763             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8764             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8765             stalledEngine = cps;
8766             if(appData.ponderNextMove) { // bring opponent out of ponder
8767                 if(gameMode == TwoMachinesPlay) {
8768                     if(cps->other->pause)
8769                         PauseEngine(cps->other);
8770                     else
8771                         SendToProgram("easy\n", cps->other);
8772                 }
8773             }
8774             StopClocks();
8775             return;
8776         }
8777
8778       if(cps->usePing) {
8779
8780         /* This method is only useful on engines that support ping */
8781         if(abortEngineThink) {
8782             if (appData.debugMode) {
8783                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8784             }
8785             SendToProgram("undo\n", cps);
8786             return;
8787         }
8788
8789         if (cps->lastPing != cps->lastPong) {
8790             /* Extra move from before last new; ignore */
8791             if (appData.debugMode) {
8792                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8793             }
8794           return;
8795         }
8796
8797       } else {
8798
8799         int machineWhite = FALSE;
8800
8801         switch (gameMode) {
8802           case BeginningOfGame:
8803             /* Extra move from before last reset; ignore */
8804             if (appData.debugMode) {
8805                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8806             }
8807             return;
8808
8809           case EndOfGame:
8810           case IcsIdle:
8811           default:
8812             /* Extra move after we tried to stop.  The mode test is
8813                not a reliable way of detecting this problem, but it's
8814                the best we can do on engines that don't support ping.
8815             */
8816             if (appData.debugMode) {
8817                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8818                         cps->which, gameMode);
8819             }
8820             SendToProgram("undo\n", cps);
8821             return;
8822
8823           case MachinePlaysWhite:
8824           case IcsPlayingWhite:
8825             machineWhite = TRUE;
8826             break;
8827
8828           case MachinePlaysBlack:
8829           case IcsPlayingBlack:
8830             machineWhite = FALSE;
8831             break;
8832
8833           case TwoMachinesPlay:
8834             machineWhite = (cps->twoMachinesColor[0] == 'w');
8835             break;
8836         }
8837         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8838             if (appData.debugMode) {
8839                 fprintf(debugFP,
8840                         "Ignoring move out of turn by %s, gameMode %d"
8841                         ", forwardMost %d\n",
8842                         cps->which, gameMode, forwardMostMove);
8843             }
8844             return;
8845         }
8846       }
8847
8848         if(cps->alphaRank) AlphaRank(machineMove, 4);
8849
8850         // [HGM] lion: (some very limited) support for Alien protocol
8851         killX = killY = kill2X = kill2Y = -1;
8852         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8853             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8854             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8855             return;
8856         }
8857         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8858             char *q = strchr(p+1, ',');            // second comma?
8859             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8860             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8861             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8862         }
8863         if(firstLeg[0]) { // there was a previous leg;
8864             // only support case where same piece makes two step
8865             char buf[20], *p = machineMove+1, *q = buf+1, f;
8866             safeStrCpy(buf, machineMove, 20);
8867             while(isdigit(*q)) q++; // find start of to-square
8868             safeStrCpy(machineMove, firstLeg, 20);
8869             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8870             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
8871             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)
8872             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8873             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8874             firstLeg[0] = NULLCHAR; legs = 0;
8875         }
8876
8877         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8878                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8879             /* Machine move could not be parsed; ignore it. */
8880           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8881                     machineMove, _(cps->which));
8882             DisplayMoveError(buf1);
8883             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8884                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8885             if (gameMode == TwoMachinesPlay) {
8886               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8887                        buf1, GE_XBOARD);
8888             }
8889             return;
8890         }
8891
8892         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8893         /* So we have to redo legality test with true e.p. status here,  */
8894         /* to make sure an illegal e.p. capture does not slip through,   */
8895         /* to cause a forfeit on a justified illegal-move complaint      */
8896         /* of the opponent.                                              */
8897         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8898            ChessMove moveType;
8899            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8900                              fromY, fromX, toY, toX, promoChar);
8901             if(moveType == IllegalMove) {
8902               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8903                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8904                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8905                            buf1, GE_XBOARD);
8906                 return;
8907            } else if(!appData.fischerCastling)
8908            /* [HGM] Kludge to handle engines that send FRC-style castling
8909               when they shouldn't (like TSCP-Gothic) */
8910            switch(moveType) {
8911              case WhiteASideCastleFR:
8912              case BlackASideCastleFR:
8913                toX+=2;
8914                currentMoveString[2]++;
8915                break;
8916              case WhiteHSideCastleFR:
8917              case BlackHSideCastleFR:
8918                toX--;
8919                currentMoveString[2]--;
8920                break;
8921              default: ; // nothing to do, but suppresses warning of pedantic compilers
8922            }
8923         }
8924         hintRequested = FALSE;
8925         lastHint[0] = NULLCHAR;
8926         bookRequested = FALSE;
8927         /* Program may be pondering now */
8928         cps->maybeThinking = TRUE;
8929         if (cps->sendTime == 2) cps->sendTime = 1;
8930         if (cps->offeredDraw) cps->offeredDraw--;
8931
8932         /* [AS] Save move info*/
8933         pvInfoList[ forwardMostMove ].score = programStats.score;
8934         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8935         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8936
8937         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8938
8939         /* Test suites abort the 'game' after one move */
8940         if(*appData.finger) {
8941            static FILE *f;
8942            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8943            if(!f) f = fopen(appData.finger, "w");
8944            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8945            else { DisplayFatalError("Bad output file", errno, 0); return; }
8946            free(fen);
8947            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8948         }
8949         if(appData.epd) {
8950            if(solvingTime >= 0) {
8951               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8952               totalTime += solvingTime; first.matchWins++;
8953            } else {
8954               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8955               second.matchWins++;
8956            }
8957            OutputKibitz(2, buf1);
8958            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8959         }
8960
8961         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8962         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8963             int count = 0;
8964
8965             while( count < adjudicateLossPlies ) {
8966                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8967
8968                 if( count & 1 ) {
8969                     score = -score; /* Flip score for winning side */
8970                 }
8971
8972                 if( score > appData.adjudicateLossThreshold ) {
8973                     break;
8974                 }
8975
8976                 count++;
8977             }
8978
8979             if( count >= adjudicateLossPlies ) {
8980                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8981
8982                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8983                     "Xboard adjudication",
8984                     GE_XBOARD );
8985
8986                 return;
8987             }
8988         }
8989
8990         if(Adjudicate(cps)) {
8991             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8992             return; // [HGM] adjudicate: for all automatic game ends
8993         }
8994
8995 #if ZIPPY
8996         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8997             first.initDone) {
8998           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8999                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9000                 SendToICS("draw ");
9001                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9002           }
9003           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9004           ics_user_moved = 1;
9005           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9006                 char buf[3*MSG_SIZ];
9007
9008                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9009                         programStats.score / 100.,
9010                         programStats.depth,
9011                         programStats.time / 100.,
9012                         (unsigned int)programStats.nodes,
9013                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9014                         programStats.movelist);
9015                 SendToICS(buf);
9016           }
9017         }
9018 #endif
9019
9020         /* [AS] Clear stats for next move */
9021         ClearProgramStats();
9022         thinkOutput[0] = NULLCHAR;
9023         hiddenThinkOutputState = 0;
9024
9025         bookHit = NULL;
9026         if (gameMode == TwoMachinesPlay) {
9027             /* [HGM] relaying draw offers moved to after reception of move */
9028             /* and interpreting offer as claim if it brings draw condition */
9029             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9030                 SendToProgram("draw\n", cps->other);
9031             }
9032             if (cps->other->sendTime) {
9033                 SendTimeRemaining(cps->other,
9034                                   cps->other->twoMachinesColor[0] == 'w');
9035             }
9036             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9037             if (firstMove && !bookHit) {
9038                 firstMove = FALSE;
9039                 if (cps->other->useColors) {
9040                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9041                 }
9042                 SendToProgram("go\n", cps->other);
9043             }
9044             cps->other->maybeThinking = TRUE;
9045         }
9046
9047         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9048
9049         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9050
9051         if (!pausing && appData.ringBellAfterMoves) {
9052             if(!roar) RingBell();
9053         }
9054
9055         /*
9056          * Reenable menu items that were disabled while
9057          * machine was thinking
9058          */
9059         if (gameMode != TwoMachinesPlay)
9060             SetUserThinkingEnables();
9061
9062         // [HGM] book: after book hit opponent has received move and is now in force mode
9063         // force the book reply into it, and then fake that it outputted this move by jumping
9064         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9065         if(bookHit) {
9066                 static char bookMove[MSG_SIZ]; // a bit generous?
9067
9068                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9069                 strcat(bookMove, bookHit);
9070                 message = bookMove;
9071                 cps = cps->other;
9072                 programStats.nodes = programStats.depth = programStats.time =
9073                 programStats.score = programStats.got_only_move = 0;
9074                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9075
9076                 if(cps->lastPing != cps->lastPong) {
9077                     savedMessage = message; // args for deferred call
9078                     savedState = cps;
9079                     ScheduleDelayedEvent(DeferredBookMove, 10);
9080                     return;
9081                 }
9082                 goto FakeBookMove;
9083         }
9084
9085         return;
9086     }
9087
9088     /* Set special modes for chess engines.  Later something general
9089      *  could be added here; for now there is just one kludge feature,
9090      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9091      *  when "xboard" is given as an interactive command.
9092      */
9093     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9094         cps->useSigint = FALSE;
9095         cps->useSigterm = FALSE;
9096     }
9097     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9098       ParseFeatures(message+8, cps);
9099       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9100     }
9101
9102     if (!strncmp(message, "setup ", 6) && 
9103         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9104           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9105                                         ) { // [HGM] allow first engine to define opening position
9106       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9107       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9108       *buf = NULLCHAR;
9109       if(sscanf(message, "setup (%s", buf) == 1) {
9110         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9111         ASSIGN(appData.pieceToCharTable, buf);
9112       }
9113       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9114       if(dummy >= 3) {
9115         while(message[s] && message[s++] != ' ');
9116         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9117            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9118             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9119             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9120           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9121           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9122           startedFromSetupPosition = FALSE;
9123         }
9124       }
9125       if(startedFromSetupPosition) return;
9126       ParseFEN(boards[0], &dummy, message+s, FALSE);
9127       DrawPosition(TRUE, boards[0]);
9128       CopyBoard(initialPosition, boards[0]);
9129       startedFromSetupPosition = TRUE;
9130       return;
9131     }
9132     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9133       ChessSquare piece = WhitePawn;
9134       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9135       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9136       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9137       piece += CharToPiece(ID & 255) - WhitePawn;
9138       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9139       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9140       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9141       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9142       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9143       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9144                                                && gameInfo.variant != VariantGreat
9145                                                && gameInfo.variant != VariantFairy    ) return;
9146       if(piece < EmptySquare) {
9147         pieceDefs = TRUE;
9148         ASSIGN(pieceDesc[piece], buf1);
9149         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9150       }
9151       return;
9152     }
9153     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9154       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9155       Sweep(0);
9156       return;
9157     }
9158     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9159      * want this, I was asked to put it in, and obliged.
9160      */
9161     if (!strncmp(message, "setboard ", 9)) {
9162         Board initial_position;
9163
9164         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9165
9166         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9167             DisplayError(_("Bad FEN received from engine"), 0);
9168             return ;
9169         } else {
9170            Reset(TRUE, FALSE);
9171            CopyBoard(boards[0], initial_position);
9172            initialRulePlies = FENrulePlies;
9173            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9174            else gameMode = MachinePlaysBlack;
9175            DrawPosition(FALSE, boards[currentMove]);
9176         }
9177         return;
9178     }
9179
9180     /*
9181      * Look for communication commands
9182      */
9183     if (!strncmp(message, "telluser ", 9)) {
9184         if(message[9] == '\\' && message[10] == '\\')
9185             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9186         PlayTellSound();
9187         DisplayNote(message + 9);
9188         return;
9189     }
9190     if (!strncmp(message, "tellusererror ", 14)) {
9191         cps->userError = 1;
9192         if(message[14] == '\\' && message[15] == '\\')
9193             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9194         PlayTellSound();
9195         DisplayError(message + 14, 0);
9196         return;
9197     }
9198     if (!strncmp(message, "tellopponent ", 13)) {
9199       if (appData.icsActive) {
9200         if (loggedOn) {
9201           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9202           SendToICS(buf1);
9203         }
9204       } else {
9205         DisplayNote(message + 13);
9206       }
9207       return;
9208     }
9209     if (!strncmp(message, "tellothers ", 11)) {
9210       if (appData.icsActive) {
9211         if (loggedOn) {
9212           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9213           SendToICS(buf1);
9214         }
9215       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9216       return;
9217     }
9218     if (!strncmp(message, "tellall ", 8)) {
9219       if (appData.icsActive) {
9220         if (loggedOn) {
9221           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9222           SendToICS(buf1);
9223         }
9224       } else {
9225         DisplayNote(message + 8);
9226       }
9227       return;
9228     }
9229     if (strncmp(message, "warning", 7) == 0) {
9230         /* Undocumented feature, use tellusererror in new code */
9231         DisplayError(message, 0);
9232         return;
9233     }
9234     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9235         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9236         strcat(realname, " query");
9237         AskQuestion(realname, buf2, buf1, cps->pr);
9238         return;
9239     }
9240     /* Commands from the engine directly to ICS.  We don't allow these to be
9241      *  sent until we are logged on. Crafty kibitzes have been known to
9242      *  interfere with the login process.
9243      */
9244     if (loggedOn) {
9245         if (!strncmp(message, "tellics ", 8)) {
9246             SendToICS(message + 8);
9247             SendToICS("\n");
9248             return;
9249         }
9250         if (!strncmp(message, "tellicsnoalias ", 15)) {
9251             SendToICS(ics_prefix);
9252             SendToICS(message + 15);
9253             SendToICS("\n");
9254             return;
9255         }
9256         /* The following are for backward compatibility only */
9257         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9258             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9259             SendToICS(ics_prefix);
9260             SendToICS(message);
9261             SendToICS("\n");
9262             return;
9263         }
9264     }
9265     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9266         if(initPing == cps->lastPong) {
9267             if(gameInfo.variant == VariantUnknown) {
9268                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9269                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9270                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9271             }
9272             initPing = -1;
9273         }
9274         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9275             abortEngineThink = FALSE;
9276             DisplayMessage("", "");
9277             ThawUI();
9278         }
9279         return;
9280     }
9281     if(!strncmp(message, "highlight ", 10)) {
9282         if(appData.testLegality && !*engineVariant && appData.markers) return;
9283         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9284         return;
9285     }
9286     if(!strncmp(message, "click ", 6)) {
9287         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9288         if(appData.testLegality || !appData.oneClick) return;
9289         sscanf(message+6, "%c%d%c", &f, &y, &c);
9290         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9291         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9292         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9293         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9294         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9295         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9296             LeftClick(Release, lastLeftX, lastLeftY);
9297         controlKey  = (c == ',');
9298         LeftClick(Press, x, y);
9299         LeftClick(Release, x, y);
9300         first.highlight = f;
9301         return;
9302     }
9303     /*
9304      * If the move is illegal, cancel it and redraw the board.
9305      * Also deal with other error cases.  Matching is rather loose
9306      * here to accommodate engines written before the spec.
9307      */
9308     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9309         strncmp(message, "Error", 5) == 0) {
9310         if (StrStr(message, "name") ||
9311             StrStr(message, "rating") || StrStr(message, "?") ||
9312             StrStr(message, "result") || StrStr(message, "board") ||
9313             StrStr(message, "bk") || StrStr(message, "computer") ||
9314             StrStr(message, "variant") || StrStr(message, "hint") ||
9315             StrStr(message, "random") || StrStr(message, "depth") ||
9316             StrStr(message, "accepted")) {
9317             return;
9318         }
9319         if (StrStr(message, "protover")) {
9320           /* Program is responding to input, so it's apparently done
9321              initializing, and this error message indicates it is
9322              protocol version 1.  So we don't need to wait any longer
9323              for it to initialize and send feature commands. */
9324           FeatureDone(cps, 1);
9325           cps->protocolVersion = 1;
9326           return;
9327         }
9328         cps->maybeThinking = FALSE;
9329
9330         if (StrStr(message, "draw")) {
9331             /* Program doesn't have "draw" command */
9332             cps->sendDrawOffers = 0;
9333             return;
9334         }
9335         if (cps->sendTime != 1 &&
9336             (StrStr(message, "time") || StrStr(message, "otim"))) {
9337           /* Program apparently doesn't have "time" or "otim" command */
9338           cps->sendTime = 0;
9339           return;
9340         }
9341         if (StrStr(message, "analyze")) {
9342             cps->analysisSupport = FALSE;
9343             cps->analyzing = FALSE;
9344 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9345             EditGameEvent(); // [HGM] try to preserve loaded game
9346             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9347             DisplayError(buf2, 0);
9348             return;
9349         }
9350         if (StrStr(message, "(no matching move)st")) {
9351           /* Special kludge for GNU Chess 4 only */
9352           cps->stKludge = TRUE;
9353           SendTimeControl(cps, movesPerSession, timeControl,
9354                           timeIncrement, appData.searchDepth,
9355                           searchTime);
9356           return;
9357         }
9358         if (StrStr(message, "(no matching move)sd")) {
9359           /* Special kludge for GNU Chess 4 only */
9360           cps->sdKludge = TRUE;
9361           SendTimeControl(cps, movesPerSession, timeControl,
9362                           timeIncrement, appData.searchDepth,
9363                           searchTime);
9364           return;
9365         }
9366         if (!StrStr(message, "llegal")) {
9367             return;
9368         }
9369         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9370             gameMode == IcsIdle) return;
9371         if (forwardMostMove <= backwardMostMove) return;
9372         if (pausing) PauseEvent();
9373       if(appData.forceIllegal) {
9374             // [HGM] illegal: machine refused move; force position after move into it
9375           SendToProgram("force\n", cps);
9376           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9377                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9378                 // when black is to move, while there might be nothing on a2 or black
9379                 // might already have the move. So send the board as if white has the move.
9380                 // But first we must change the stm of the engine, as it refused the last move
9381                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9382                 if(WhiteOnMove(forwardMostMove)) {
9383                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9384                     SendBoard(cps, forwardMostMove); // kludgeless board
9385                 } else {
9386                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9387                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9388                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9389                 }
9390           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9391             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9392                  gameMode == TwoMachinesPlay)
9393               SendToProgram("go\n", cps);
9394             return;
9395       } else
9396         if (gameMode == PlayFromGameFile) {
9397             /* Stop reading this game file */
9398             gameMode = EditGame;
9399             ModeHighlight();
9400         }
9401         /* [HGM] illegal-move claim should forfeit game when Xboard */
9402         /* only passes fully legal moves                            */
9403         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9404             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9405                                 "False illegal-move claim", GE_XBOARD );
9406             return; // do not take back move we tested as valid
9407         }
9408         currentMove = forwardMostMove-1;
9409         DisplayMove(currentMove-1); /* before DisplayMoveError */
9410         SwitchClocks(forwardMostMove-1); // [HGM] race
9411         DisplayBothClocks();
9412         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9413                 parseList[currentMove], _(cps->which));
9414         DisplayMoveError(buf1);
9415         DrawPosition(FALSE, boards[currentMove]);
9416
9417         SetUserThinkingEnables();
9418         return;
9419     }
9420     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9421         /* Program has a broken "time" command that
9422            outputs a string not ending in newline.
9423            Don't use it. */
9424         cps->sendTime = 0;
9425     }
9426     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9427         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9428             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9429     }
9430
9431     /*
9432      * If chess program startup fails, exit with an error message.
9433      * Attempts to recover here are futile. [HGM] Well, we try anyway
9434      */
9435     if ((StrStr(message, "unknown host") != NULL)
9436         || (StrStr(message, "No remote directory") != NULL)
9437         || (StrStr(message, "not found") != NULL)
9438         || (StrStr(message, "No such file") != NULL)
9439         || (StrStr(message, "can't alloc") != NULL)
9440         || (StrStr(message, "Permission denied") != NULL)) {
9441
9442         cps->maybeThinking = FALSE;
9443         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9444                 _(cps->which), cps->program, cps->host, message);
9445         RemoveInputSource(cps->isr);
9446         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9447             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9448             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9449         }
9450         return;
9451     }
9452
9453     /*
9454      * Look for hint output
9455      */
9456     if (sscanf(message, "Hint: %s", buf1) == 1) {
9457         if (cps == &first && hintRequested) {
9458             hintRequested = FALSE;
9459             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9460                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9461                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9462                                     PosFlags(forwardMostMove),
9463                                     fromY, fromX, toY, toX, promoChar, buf1);
9464                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9465                 DisplayInformation(buf2);
9466             } else {
9467                 /* Hint move could not be parsed!? */
9468               snprintf(buf2, sizeof(buf2),
9469                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9470                         buf1, _(cps->which));
9471                 DisplayError(buf2, 0);
9472             }
9473         } else {
9474           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9475         }
9476         return;
9477     }
9478
9479     /*
9480      * Ignore other messages if game is not in progress
9481      */
9482     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9483         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9484
9485     /*
9486      * look for win, lose, draw, or draw offer
9487      */
9488     if (strncmp(message, "1-0", 3) == 0) {
9489         char *p, *q, *r = "";
9490         p = strchr(message, '{');
9491         if (p) {
9492             q = strchr(p, '}');
9493             if (q) {
9494                 *q = NULLCHAR;
9495                 r = p + 1;
9496             }
9497         }
9498         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9499         return;
9500     } else if (strncmp(message, "0-1", 3) == 0) {
9501         char *p, *q, *r = "";
9502         p = strchr(message, '{');
9503         if (p) {
9504             q = strchr(p, '}');
9505             if (q) {
9506                 *q = NULLCHAR;
9507                 r = p + 1;
9508             }
9509         }
9510         /* Kludge for Arasan 4.1 bug */
9511         if (strcmp(r, "Black resigns") == 0) {
9512             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9513             return;
9514         }
9515         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9516         return;
9517     } else if (strncmp(message, "1/2", 3) == 0) {
9518         char *p, *q, *r = "";
9519         p = strchr(message, '{');
9520         if (p) {
9521             q = strchr(p, '}');
9522             if (q) {
9523                 *q = NULLCHAR;
9524                 r = p + 1;
9525             }
9526         }
9527
9528         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9529         return;
9530
9531     } else if (strncmp(message, "White resign", 12) == 0) {
9532         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9533         return;
9534     } else if (strncmp(message, "Black resign", 12) == 0) {
9535         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9536         return;
9537     } else if (strncmp(message, "White matches", 13) == 0 ||
9538                strncmp(message, "Black matches", 13) == 0   ) {
9539         /* [HGM] ignore GNUShogi noises */
9540         return;
9541     } else if (strncmp(message, "White", 5) == 0 &&
9542                message[5] != '(' &&
9543                StrStr(message, "Black") == NULL) {
9544         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9545         return;
9546     } else if (strncmp(message, "Black", 5) == 0 &&
9547                message[5] != '(') {
9548         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9549         return;
9550     } else if (strcmp(message, "resign") == 0 ||
9551                strcmp(message, "computer resigns") == 0) {
9552         switch (gameMode) {
9553           case MachinePlaysBlack:
9554           case IcsPlayingBlack:
9555             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9556             break;
9557           case MachinePlaysWhite:
9558           case IcsPlayingWhite:
9559             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9560             break;
9561           case TwoMachinesPlay:
9562             if (cps->twoMachinesColor[0] == 'w')
9563               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9564             else
9565               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9566             break;
9567           default:
9568             /* can't happen */
9569             break;
9570         }
9571         return;
9572     } else if (strncmp(message, "opponent mates", 14) == 0) {
9573         switch (gameMode) {
9574           case MachinePlaysBlack:
9575           case IcsPlayingBlack:
9576             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9577             break;
9578           case MachinePlaysWhite:
9579           case IcsPlayingWhite:
9580             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9581             break;
9582           case TwoMachinesPlay:
9583             if (cps->twoMachinesColor[0] == 'w')
9584               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9585             else
9586               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9587             break;
9588           default:
9589             /* can't happen */
9590             break;
9591         }
9592         return;
9593     } else if (strncmp(message, "computer mates", 14) == 0) {
9594         switch (gameMode) {
9595           case MachinePlaysBlack:
9596           case IcsPlayingBlack:
9597             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9598             break;
9599           case MachinePlaysWhite:
9600           case IcsPlayingWhite:
9601             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9602             break;
9603           case TwoMachinesPlay:
9604             if (cps->twoMachinesColor[0] == 'w')
9605               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9606             else
9607               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9608             break;
9609           default:
9610             /* can't happen */
9611             break;
9612         }
9613         return;
9614     } else if (strncmp(message, "checkmate", 9) == 0) {
9615         if (WhiteOnMove(forwardMostMove)) {
9616             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9617         } else {
9618             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9619         }
9620         return;
9621     } else if (strstr(message, "Draw") != NULL ||
9622                strstr(message, "game is a draw") != NULL) {
9623         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9624         return;
9625     } else if (strstr(message, "offer") != NULL &&
9626                strstr(message, "draw") != NULL) {
9627 #if ZIPPY
9628         if (appData.zippyPlay && first.initDone) {
9629             /* Relay offer to ICS */
9630             SendToICS(ics_prefix);
9631             SendToICS("draw\n");
9632         }
9633 #endif
9634         cps->offeredDraw = 2; /* valid until this engine moves twice */
9635         if (gameMode == TwoMachinesPlay) {
9636             if (cps->other->offeredDraw) {
9637                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9638             /* [HGM] in two-machine mode we delay relaying draw offer      */
9639             /* until after we also have move, to see if it is really claim */
9640             }
9641         } else if (gameMode == MachinePlaysWhite ||
9642                    gameMode == MachinePlaysBlack) {
9643           if (userOfferedDraw) {
9644             DisplayInformation(_("Machine accepts your draw offer"));
9645             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9646           } else {
9647             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9648           }
9649         }
9650     }
9651
9652
9653     /*
9654      * Look for thinking output
9655      */
9656     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9657           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9658                                 ) {
9659         int plylev, mvleft, mvtot, curscore, time;
9660         char mvname[MOVE_LEN];
9661         u64 nodes; // [DM]
9662         char plyext;
9663         int ignore = FALSE;
9664         int prefixHint = FALSE;
9665         mvname[0] = NULLCHAR;
9666
9667         switch (gameMode) {
9668           case MachinePlaysBlack:
9669           case IcsPlayingBlack:
9670             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9671             break;
9672           case MachinePlaysWhite:
9673           case IcsPlayingWhite:
9674             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9675             break;
9676           case AnalyzeMode:
9677           case AnalyzeFile:
9678             break;
9679           case IcsObserving: /* [DM] icsEngineAnalyze */
9680             if (!appData.icsEngineAnalyze) ignore = TRUE;
9681             break;
9682           case TwoMachinesPlay:
9683             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9684                 ignore = TRUE;
9685             }
9686             break;
9687           default:
9688             ignore = TRUE;
9689             break;
9690         }
9691
9692         if (!ignore) {
9693             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9694             buf1[0] = NULLCHAR;
9695             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9696                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9697                 char score_buf[MSG_SIZ];
9698
9699                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9700                     nodes += u64Const(0x100000000);
9701
9702                 if (plyext != ' ' && plyext != '\t') {
9703                     time *= 100;
9704                 }
9705
9706                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9707                 if( cps->scoreIsAbsolute &&
9708                     ( gameMode == MachinePlaysBlack ||
9709                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9710                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9711                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9712                      !WhiteOnMove(currentMove)
9713                     ) )
9714                 {
9715                     curscore = -curscore;
9716                 }
9717
9718                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9719
9720                 if(*bestMove) { // rememer time best EPD move was first found
9721                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9722                     ChessMove mt;
9723                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9724                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9725                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9726                 }
9727
9728                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9729                         char buf[MSG_SIZ];
9730                         FILE *f;
9731                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9732                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9733                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9734                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9735                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9736                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9737                                 fclose(f);
9738                         }
9739                         else
9740                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9741                           DisplayError(_("failed writing PV"), 0);
9742                 }
9743
9744                 tempStats.depth = plylev;
9745                 tempStats.nodes = nodes;
9746                 tempStats.time = time;
9747                 tempStats.score = curscore;
9748                 tempStats.got_only_move = 0;
9749
9750                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9751                         int ticklen;
9752
9753                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9754                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9755                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9756                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9757                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9758                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9759                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9760                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9761                 }
9762
9763                 /* Buffer overflow protection */
9764                 if (pv[0] != NULLCHAR) {
9765                     if (strlen(pv) >= sizeof(tempStats.movelist)
9766                         && appData.debugMode) {
9767                         fprintf(debugFP,
9768                                 "PV is too long; using the first %u bytes.\n",
9769                                 (unsigned) sizeof(tempStats.movelist) - 1);
9770                     }
9771
9772                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9773                 } else {
9774                     sprintf(tempStats.movelist, " no PV\n");
9775                 }
9776
9777                 if (tempStats.seen_stat) {
9778                     tempStats.ok_to_send = 1;
9779                 }
9780
9781                 if (strchr(tempStats.movelist, '(') != NULL) {
9782                     tempStats.line_is_book = 1;
9783                     tempStats.nr_moves = 0;
9784                     tempStats.moves_left = 0;
9785                 } else {
9786                     tempStats.line_is_book = 0;
9787                 }
9788
9789                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9790                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9791
9792                 SendProgramStatsToFrontend( cps, &tempStats );
9793
9794                 /*
9795                     [AS] Protect the thinkOutput buffer from overflow... this
9796                     is only useful if buf1 hasn't overflowed first!
9797                 */
9798                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9799                 if(curscore >= MATE_SCORE) 
9800                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9801                 else if(curscore <= -MATE_SCORE) 
9802                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9803                 else
9804                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9805                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9806                          plylev,
9807                          (gameMode == TwoMachinesPlay ?
9808                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9809                          score_buf,
9810                          prefixHint ? lastHint : "",
9811                          prefixHint ? " " : "" );
9812
9813                 if( buf1[0] != NULLCHAR ) {
9814                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9815
9816                     if( strlen(pv) > max_len ) {
9817                         if( appData.debugMode) {
9818                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9819                         }
9820                         pv[max_len+1] = '\0';
9821                     }
9822
9823                     strcat( thinkOutput, pv);
9824                 }
9825
9826                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9827                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9828                     DisplayMove(currentMove - 1);
9829                 }
9830                 return;
9831
9832             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9833                 /* crafty (9.25+) says "(only move) <move>"
9834                  * if there is only 1 legal move
9835                  */
9836                 sscanf(p, "(only move) %s", buf1);
9837                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9838                 sprintf(programStats.movelist, "%s (only move)", buf1);
9839                 programStats.depth = 1;
9840                 programStats.nr_moves = 1;
9841                 programStats.moves_left = 1;
9842                 programStats.nodes = 1;
9843                 programStats.time = 1;
9844                 programStats.got_only_move = 1;
9845
9846                 /* Not really, but we also use this member to
9847                    mean "line isn't going to change" (Crafty
9848                    isn't searching, so stats won't change) */
9849                 programStats.line_is_book = 1;
9850
9851                 SendProgramStatsToFrontend( cps, &programStats );
9852
9853                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9854                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9855                     DisplayMove(currentMove - 1);
9856                 }
9857                 return;
9858             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9859                               &time, &nodes, &plylev, &mvleft,
9860                               &mvtot, mvname) >= 5) {
9861                 /* The stat01: line is from Crafty (9.29+) in response
9862                    to the "." command */
9863                 programStats.seen_stat = 1;
9864                 cps->maybeThinking = TRUE;
9865
9866                 if (programStats.got_only_move || !appData.periodicUpdates)
9867                   return;
9868
9869                 programStats.depth = plylev;
9870                 programStats.time = time;
9871                 programStats.nodes = nodes;
9872                 programStats.moves_left = mvleft;
9873                 programStats.nr_moves = mvtot;
9874                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9875                 programStats.ok_to_send = 1;
9876                 programStats.movelist[0] = '\0';
9877
9878                 SendProgramStatsToFrontend( cps, &programStats );
9879
9880                 return;
9881
9882             } else if (strncmp(message,"++",2) == 0) {
9883                 /* Crafty 9.29+ outputs this */
9884                 programStats.got_fail = 2;
9885                 return;
9886
9887             } else if (strncmp(message,"--",2) == 0) {
9888                 /* Crafty 9.29+ outputs this */
9889                 programStats.got_fail = 1;
9890                 return;
9891
9892             } else if (thinkOutput[0] != NULLCHAR &&
9893                        strncmp(message, "    ", 4) == 0) {
9894                 unsigned message_len;
9895
9896                 p = message;
9897                 while (*p && *p == ' ') p++;
9898
9899                 message_len = strlen( p );
9900
9901                 /* [AS] Avoid buffer overflow */
9902                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9903                     strcat(thinkOutput, " ");
9904                     strcat(thinkOutput, p);
9905                 }
9906
9907                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9908                     strcat(programStats.movelist, " ");
9909                     strcat(programStats.movelist, p);
9910                 }
9911
9912                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9913                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9914                     DisplayMove(currentMove - 1);
9915                 }
9916                 return;
9917             }
9918         }
9919         else {
9920             buf1[0] = NULLCHAR;
9921
9922             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9923                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9924             {
9925                 ChessProgramStats cpstats;
9926
9927                 if (plyext != ' ' && plyext != '\t') {
9928                     time *= 100;
9929                 }
9930
9931                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9932                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9933                     curscore = -curscore;
9934                 }
9935
9936                 cpstats.depth = plylev;
9937                 cpstats.nodes = nodes;
9938                 cpstats.time = time;
9939                 cpstats.score = curscore;
9940                 cpstats.got_only_move = 0;
9941                 cpstats.movelist[0] = '\0';
9942
9943                 if (buf1[0] != NULLCHAR) {
9944                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9945                 }
9946
9947                 cpstats.ok_to_send = 0;
9948                 cpstats.line_is_book = 0;
9949                 cpstats.nr_moves = 0;
9950                 cpstats.moves_left = 0;
9951
9952                 SendProgramStatsToFrontend( cps, &cpstats );
9953             }
9954         }
9955     }
9956 }
9957
9958
9959 /* Parse a game score from the character string "game", and
9960    record it as the history of the current game.  The game
9961    score is NOT assumed to start from the standard position.
9962    The display is not updated in any way.
9963    */
9964 void
9965 ParseGameHistory (char *game)
9966 {
9967     ChessMove moveType;
9968     int fromX, fromY, toX, toY, boardIndex;
9969     char promoChar;
9970     char *p, *q;
9971     char buf[MSG_SIZ];
9972
9973     if (appData.debugMode)
9974       fprintf(debugFP, "Parsing game history: %s\n", game);
9975
9976     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9977     gameInfo.site = StrSave(appData.icsHost);
9978     gameInfo.date = PGNDate();
9979     gameInfo.round = StrSave("-");
9980
9981     /* Parse out names of players */
9982     while (*game == ' ') game++;
9983     p = buf;
9984     while (*game != ' ') *p++ = *game++;
9985     *p = NULLCHAR;
9986     gameInfo.white = StrSave(buf);
9987     while (*game == ' ') game++;
9988     p = buf;
9989     while (*game != ' ' && *game != '\n') *p++ = *game++;
9990     *p = NULLCHAR;
9991     gameInfo.black = StrSave(buf);
9992
9993     /* Parse moves */
9994     boardIndex = blackPlaysFirst ? 1 : 0;
9995     yynewstr(game);
9996     for (;;) {
9997         yyboardindex = boardIndex;
9998         moveType = (ChessMove) Myylex();
9999         switch (moveType) {
10000           case IllegalMove:             /* maybe suicide chess, etc. */
10001   if (appData.debugMode) {
10002     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10003     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10004     setbuf(debugFP, NULL);
10005   }
10006           case WhitePromotion:
10007           case BlackPromotion:
10008           case WhiteNonPromotion:
10009           case BlackNonPromotion:
10010           case NormalMove:
10011           case FirstLeg:
10012           case WhiteCapturesEnPassant:
10013           case BlackCapturesEnPassant:
10014           case WhiteKingSideCastle:
10015           case WhiteQueenSideCastle:
10016           case BlackKingSideCastle:
10017           case BlackQueenSideCastle:
10018           case WhiteKingSideCastleWild:
10019           case WhiteQueenSideCastleWild:
10020           case BlackKingSideCastleWild:
10021           case BlackQueenSideCastleWild:
10022           /* PUSH Fabien */
10023           case WhiteHSideCastleFR:
10024           case WhiteASideCastleFR:
10025           case BlackHSideCastleFR:
10026           case BlackASideCastleFR:
10027           /* POP Fabien */
10028             fromX = currentMoveString[0] - AAA;
10029             fromY = currentMoveString[1] - ONE;
10030             toX = currentMoveString[2] - AAA;
10031             toY = currentMoveString[3] - ONE;
10032             promoChar = currentMoveString[4];
10033             break;
10034           case WhiteDrop:
10035           case BlackDrop:
10036             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10037             fromX = moveType == WhiteDrop ?
10038               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10039             (int) CharToPiece(ToLower(currentMoveString[0]));
10040             fromY = DROP_RANK;
10041             toX = currentMoveString[2] - AAA;
10042             toY = currentMoveString[3] - ONE;
10043             promoChar = NULLCHAR;
10044             break;
10045           case AmbiguousMove:
10046             /* bug? */
10047             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10048   if (appData.debugMode) {
10049     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10050     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10051     setbuf(debugFP, NULL);
10052   }
10053             DisplayError(buf, 0);
10054             return;
10055           case ImpossibleMove:
10056             /* bug? */
10057             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10058   if (appData.debugMode) {
10059     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10060     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10061     setbuf(debugFP, NULL);
10062   }
10063             DisplayError(buf, 0);
10064             return;
10065           case EndOfFile:
10066             if (boardIndex < backwardMostMove) {
10067                 /* Oops, gap.  How did that happen? */
10068                 DisplayError(_("Gap in move list"), 0);
10069                 return;
10070             }
10071             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10072             if (boardIndex > forwardMostMove) {
10073                 forwardMostMove = boardIndex;
10074             }
10075             return;
10076           case ElapsedTime:
10077             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10078                 strcat(parseList[boardIndex-1], " ");
10079                 strcat(parseList[boardIndex-1], yy_text);
10080             }
10081             continue;
10082           case Comment:
10083           case PGNTag:
10084           case NAG:
10085           default:
10086             /* ignore */
10087             continue;
10088           case WhiteWins:
10089           case BlackWins:
10090           case GameIsDrawn:
10091           case GameUnfinished:
10092             if (gameMode == IcsExamining) {
10093                 if (boardIndex < backwardMostMove) {
10094                     /* Oops, gap.  How did that happen? */
10095                     return;
10096                 }
10097                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10098                 return;
10099             }
10100             gameInfo.result = moveType;
10101             p = strchr(yy_text, '{');
10102             if (p == NULL) p = strchr(yy_text, '(');
10103             if (p == NULL) {
10104                 p = yy_text;
10105                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10106             } else {
10107                 q = strchr(p, *p == '{' ? '}' : ')');
10108                 if (q != NULL) *q = NULLCHAR;
10109                 p++;
10110             }
10111             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10112             gameInfo.resultDetails = StrSave(p);
10113             continue;
10114         }
10115         if (boardIndex >= forwardMostMove &&
10116             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10117             backwardMostMove = blackPlaysFirst ? 1 : 0;
10118             return;
10119         }
10120         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10121                                  fromY, fromX, toY, toX, promoChar,
10122                                  parseList[boardIndex]);
10123         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10124         /* currentMoveString is set as a side-effect of yylex */
10125         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10126         strcat(moveList[boardIndex], "\n");
10127         boardIndex++;
10128         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10129         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10130           case MT_NONE:
10131           case MT_STALEMATE:
10132           default:
10133             break;
10134           case MT_CHECK:
10135             if(!IS_SHOGI(gameInfo.variant))
10136                 strcat(parseList[boardIndex - 1], "+");
10137             break;
10138           case MT_CHECKMATE:
10139           case MT_STAINMATE:
10140             strcat(parseList[boardIndex - 1], "#");
10141             break;
10142         }
10143     }
10144 }
10145
10146
10147 /* Apply a move to the given board  */
10148 void
10149 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10150 {
10151   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10152   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10153
10154     /* [HGM] compute & store e.p. status and castling rights for new position */
10155     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10156
10157       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10158       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10159       board[EP_STATUS] = EP_NONE;
10160       board[EP_FILE] = board[EP_RANK] = 100;
10161
10162   if (fromY == DROP_RANK) {
10163         /* must be first */
10164         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10165             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10166             return;
10167         }
10168         piece = board[toY][toX] = (ChessSquare) fromX;
10169   } else {
10170 //      ChessSquare victim;
10171       int i;
10172
10173       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10174 //           victim = board[killY][killX],
10175            killed = board[killY][killX],
10176            board[killY][killX] = EmptySquare,
10177            board[EP_STATUS] = EP_CAPTURE;
10178            if( kill2X >= 0 && kill2Y >= 0)
10179              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10180       }
10181
10182       if( board[toY][toX] != EmptySquare ) {
10183            board[EP_STATUS] = EP_CAPTURE;
10184            if( (fromX != toX || fromY != toY) && // not igui!
10185                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10186                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10187                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10188            }
10189       }
10190
10191       pawn = board[fromY][fromX];
10192       if( pawn == WhiteLance || pawn == BlackLance ) {
10193            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10194                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10195                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10196            }
10197       }
10198       if( pawn == WhitePawn ) {
10199            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10200                board[EP_STATUS] = EP_PAWN_MOVE;
10201            if( toY-fromY>=2) {
10202                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10203                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10204                         gameInfo.variant != VariantBerolina || toX < fromX)
10205                       board[EP_STATUS] = toX | berolina;
10206                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10207                         gameInfo.variant != VariantBerolina || toX > fromX)
10208                       board[EP_STATUS] = toX;
10209            }
10210       } else
10211       if( pawn == BlackPawn ) {
10212            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10213                board[EP_STATUS] = EP_PAWN_MOVE;
10214            if( toY-fromY<= -2) {
10215                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10216                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10217                         gameInfo.variant != VariantBerolina || toX < fromX)
10218                       board[EP_STATUS] = toX | berolina;
10219                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10220                         gameInfo.variant != VariantBerolina || toX > fromX)
10221                       board[EP_STATUS] = toX;
10222            }
10223        }
10224
10225        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10226        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10227        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10228        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10229
10230        for(i=0; i<nrCastlingRights; i++) {
10231            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10232               board[CASTLING][i] == toX   && castlingRank[i] == toY
10233              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10234        }
10235
10236        if(gameInfo.variant == VariantSChess) { // update virginity
10237            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10238            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10239            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10240            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10241        }
10242
10243      if (fromX == toX && fromY == toY && killX < 0) return;
10244
10245      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10246      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10247      if(gameInfo.variant == VariantKnightmate)
10248          king += (int) WhiteUnicorn - (int) WhiteKing;
10249
10250     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10251        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10252         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10253         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10254         board[EP_STATUS] = EP_NONE; // capture was fake!
10255     } else
10256     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10257         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10258         board[toY][toX] = piece;
10259         board[EP_STATUS] = EP_NONE; // capture was fake!
10260     } else
10261     /* Code added by Tord: */
10262     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10263     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10264         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10265       board[EP_STATUS] = EP_NONE; // capture was fake!
10266       board[fromY][fromX] = EmptySquare;
10267       board[toY][toX] = EmptySquare;
10268       if((toX > fromX) != (piece == WhiteRook)) {
10269         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10270       } else {
10271         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10272       }
10273     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10274                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10275       board[EP_STATUS] = EP_NONE;
10276       board[fromY][fromX] = EmptySquare;
10277       board[toY][toX] = EmptySquare;
10278       if((toX > fromX) != (piece == BlackRook)) {
10279         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10280       } else {
10281         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10282       }
10283     /* End of code added by Tord */
10284
10285     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10286         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10287         board[toY][toX] = piece;
10288     } else if (board[fromY][fromX] == king
10289         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10290         && toY == fromY && toX > fromX+1) {
10291         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10292         board[fromY][toX-1] = board[fromY][rookX];
10293         board[fromY][rookX] = EmptySquare;
10294         board[fromY][fromX] = EmptySquare;
10295         board[toY][toX] = king;
10296     } else if (board[fromY][fromX] == king
10297         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10298                && toY == fromY && toX < fromX-1) {
10299         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10300         board[fromY][toX+1] = board[fromY][rookX];
10301         board[fromY][rookX] = EmptySquare;
10302         board[fromY][fromX] = EmptySquare;
10303         board[toY][toX] = king;
10304     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10305                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10306                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10307                ) {
10308         /* white pawn promotion */
10309         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10310         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10311             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10312         board[fromY][fromX] = EmptySquare;
10313     } else if ((fromY >= BOARD_HEIGHT>>1)
10314                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10315                && (toX != fromX)
10316                && gameInfo.variant != VariantXiangqi
10317                && gameInfo.variant != VariantBerolina
10318                && (pawn == WhitePawn)
10319                && (board[toY][toX] == EmptySquare)) {
10320         board[fromY][fromX] = EmptySquare;
10321         board[toY][toX] = piece;
10322         if(toY == epRank - 128 + 1)
10323             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10324         else
10325             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10326     } else if ((fromY == BOARD_HEIGHT-4)
10327                && (toX == fromX)
10328                && gameInfo.variant == VariantBerolina
10329                && (board[fromY][fromX] == WhitePawn)
10330                && (board[toY][toX] == EmptySquare)) {
10331         board[fromY][fromX] = EmptySquare;
10332         board[toY][toX] = WhitePawn;
10333         if(oldEP & EP_BEROLIN_A) {
10334                 captured = board[fromY][fromX-1];
10335                 board[fromY][fromX-1] = EmptySquare;
10336         }else{  captured = board[fromY][fromX+1];
10337                 board[fromY][fromX+1] = EmptySquare;
10338         }
10339     } else if (board[fromY][fromX] == king
10340         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10341                && toY == fromY && toX > fromX+1) {
10342         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10343         board[fromY][toX-1] = board[fromY][rookX];
10344         board[fromY][rookX] = EmptySquare;
10345         board[fromY][fromX] = EmptySquare;
10346         board[toY][toX] = king;
10347     } else if (board[fromY][fromX] == king
10348         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10349                && toY == fromY && toX < fromX-1) {
10350         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10351         board[fromY][toX+1] = board[fromY][rookX];
10352         board[fromY][rookX] = EmptySquare;
10353         board[fromY][fromX] = EmptySquare;
10354         board[toY][toX] = king;
10355     } else if (fromY == 7 && fromX == 3
10356                && board[fromY][fromX] == BlackKing
10357                && toY == 7 && toX == 5) {
10358         board[fromY][fromX] = EmptySquare;
10359         board[toY][toX] = BlackKing;
10360         board[fromY][7] = EmptySquare;
10361         board[toY][4] = BlackRook;
10362     } else if (fromY == 7 && fromX == 3
10363                && board[fromY][fromX] == BlackKing
10364                && toY == 7 && toX == 1) {
10365         board[fromY][fromX] = EmptySquare;
10366         board[toY][toX] = BlackKing;
10367         board[fromY][0] = EmptySquare;
10368         board[toY][2] = BlackRook;
10369     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10370                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10371                && toY < promoRank && promoChar
10372                ) {
10373         /* black pawn promotion */
10374         board[toY][toX] = CharToPiece(ToLower(promoChar));
10375         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10376             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10377         board[fromY][fromX] = EmptySquare;
10378     } else if ((fromY < BOARD_HEIGHT>>1)
10379                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10380                && (toX != fromX)
10381                && gameInfo.variant != VariantXiangqi
10382                && gameInfo.variant != VariantBerolina
10383                && (pawn == BlackPawn)
10384                && (board[toY][toX] == EmptySquare)) {
10385         board[fromY][fromX] = EmptySquare;
10386         board[toY][toX] = piece;
10387         if(toY == epRank - 128 - 1)
10388             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10389         else
10390             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10391     } else if ((fromY == 3)
10392                && (toX == fromX)
10393                && gameInfo.variant == VariantBerolina
10394                && (board[fromY][fromX] == BlackPawn)
10395                && (board[toY][toX] == EmptySquare)) {
10396         board[fromY][fromX] = EmptySquare;
10397         board[toY][toX] = BlackPawn;
10398         if(oldEP & EP_BEROLIN_A) {
10399                 captured = board[fromY][fromX-1];
10400                 board[fromY][fromX-1] = EmptySquare;
10401         }else{  captured = board[fromY][fromX+1];
10402                 board[fromY][fromX+1] = EmptySquare;
10403         }
10404     } else {
10405         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10406         board[fromY][fromX] = EmptySquare;
10407         board[toY][toX] = piece;
10408     }
10409   }
10410
10411     if (gameInfo.holdingsWidth != 0) {
10412
10413       /* !!A lot more code needs to be written to support holdings  */
10414       /* [HGM] OK, so I have written it. Holdings are stored in the */
10415       /* penultimate board files, so they are automaticlly stored   */
10416       /* in the game history.                                       */
10417       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10418                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10419         /* Delete from holdings, by decreasing count */
10420         /* and erasing image if necessary            */
10421         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10422         if(p < (int) BlackPawn) { /* white drop */
10423              p -= (int)WhitePawn;
10424                  p = PieceToNumber((ChessSquare)p);
10425              if(p >= gameInfo.holdingsSize) p = 0;
10426              if(--board[p][BOARD_WIDTH-2] <= 0)
10427                   board[p][BOARD_WIDTH-1] = EmptySquare;
10428              if((int)board[p][BOARD_WIDTH-2] < 0)
10429                         board[p][BOARD_WIDTH-2] = 0;
10430         } else {                  /* black drop */
10431              p -= (int)BlackPawn;
10432                  p = PieceToNumber((ChessSquare)p);
10433              if(p >= gameInfo.holdingsSize) p = 0;
10434              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10435                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10436              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10437                         board[BOARD_HEIGHT-1-p][1] = 0;
10438         }
10439       }
10440       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10441           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10442         /* [HGM] holdings: Add to holdings, if holdings exist */
10443         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10444                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10445                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10446         }
10447         p = (int) captured;
10448         if (p >= (int) BlackPawn) {
10449           p -= (int)BlackPawn;
10450           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10451                   /* Restore shogi-promoted piece to its original  first */
10452                   captured = (ChessSquare) (DEMOTED(captured));
10453                   p = DEMOTED(p);
10454           }
10455           p = PieceToNumber((ChessSquare)p);
10456           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10457           board[p][BOARD_WIDTH-2]++;
10458           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10459         } else {
10460           p -= (int)WhitePawn;
10461           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10462                   captured = (ChessSquare) (DEMOTED(captured));
10463                   p = DEMOTED(p);
10464           }
10465           p = PieceToNumber((ChessSquare)p);
10466           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10467           board[BOARD_HEIGHT-1-p][1]++;
10468           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10469         }
10470       }
10471     } else if (gameInfo.variant == VariantAtomic) {
10472       if (captured != EmptySquare) {
10473         int y, x;
10474         for (y = toY-1; y <= toY+1; y++) {
10475           for (x = toX-1; x <= toX+1; x++) {
10476             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10477                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10478               board[y][x] = EmptySquare;
10479             }
10480           }
10481         }
10482         board[toY][toX] = EmptySquare;
10483       }
10484     }
10485
10486     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10487         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10488     } else
10489     if(promoChar == '+') {
10490         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10491         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10492         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10493           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10494     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10495         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10496         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10497            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10498         board[toY][toX] = newPiece;
10499     }
10500     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10501                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10502         // [HGM] superchess: take promotion piece out of holdings
10503         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10504         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10505             if(!--board[k][BOARD_WIDTH-2])
10506                 board[k][BOARD_WIDTH-1] = EmptySquare;
10507         } else {
10508             if(!--board[BOARD_HEIGHT-1-k][1])
10509                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10510         }
10511     }
10512 }
10513
10514 /* Updates forwardMostMove */
10515 void
10516 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10517 {
10518     int x = toX, y = toY;
10519     char *s = parseList[forwardMostMove];
10520     ChessSquare p = boards[forwardMostMove][toY][toX];
10521 //    forwardMostMove++; // [HGM] bare: moved downstream
10522
10523     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10524     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10525     (void) CoordsToAlgebraic(boards[forwardMostMove],
10526                              PosFlags(forwardMostMove),
10527                              fromY, fromX, y, x, (killX < 0)*promoChar,
10528                              s);
10529     if(kill2X >= 0 && kill2Y >= 0)
10530         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10531     if(killX >= 0 && killY >= 0)
10532         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10533                                            toX + AAA, toY + ONE - '0', promoChar);
10534
10535     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10536         int timeLeft; static int lastLoadFlag=0; int king, piece;
10537         piece = boards[forwardMostMove][fromY][fromX];
10538         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10539         if(gameInfo.variant == VariantKnightmate)
10540             king += (int) WhiteUnicorn - (int) WhiteKing;
10541         if(forwardMostMove == 0) {
10542             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10543                 fprintf(serverMoves, "%s;", UserName());
10544             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10545                 fprintf(serverMoves, "%s;", second.tidy);
10546             fprintf(serverMoves, "%s;", first.tidy);
10547             if(gameMode == MachinePlaysWhite)
10548                 fprintf(serverMoves, "%s;", UserName());
10549             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10550                 fprintf(serverMoves, "%s;", second.tidy);
10551         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10552         lastLoadFlag = loadFlag;
10553         // print base move
10554         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10555         // print castling suffix
10556         if( toY == fromY && piece == king ) {
10557             if(toX-fromX > 1)
10558                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10559             if(fromX-toX >1)
10560                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10561         }
10562         // e.p. suffix
10563         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10564              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10565              boards[forwardMostMove][toY][toX] == EmptySquare
10566              && fromX != toX && fromY != toY)
10567                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10568         // promotion suffix
10569         if(promoChar != NULLCHAR) {
10570             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10571                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10572                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10573             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10574         }
10575         if(!loadFlag) {
10576                 char buf[MOVE_LEN*2], *p; int len;
10577             fprintf(serverMoves, "/%d/%d",
10578                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10579             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10580             else                      timeLeft = blackTimeRemaining/1000;
10581             fprintf(serverMoves, "/%d", timeLeft);
10582                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10583                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10584                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10585                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10586             fprintf(serverMoves, "/%s", buf);
10587         }
10588         fflush(serverMoves);
10589     }
10590
10591     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10592         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10593       return;
10594     }
10595     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10596     if (commentList[forwardMostMove+1] != NULL) {
10597         free(commentList[forwardMostMove+1]);
10598         commentList[forwardMostMove+1] = NULL;
10599     }
10600     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10601     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10602     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10603     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10604     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10605     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10606     adjustedClock = FALSE;
10607     gameInfo.result = GameUnfinished;
10608     if (gameInfo.resultDetails != NULL) {
10609         free(gameInfo.resultDetails);
10610         gameInfo.resultDetails = NULL;
10611     }
10612     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10613                               moveList[forwardMostMove - 1]);
10614     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10615       case MT_NONE:
10616       case MT_STALEMATE:
10617       default:
10618         break;
10619       case MT_CHECK:
10620         if(!IS_SHOGI(gameInfo.variant))
10621             strcat(parseList[forwardMostMove - 1], "+");
10622         break;
10623       case MT_CHECKMATE:
10624       case MT_STAINMATE:
10625         strcat(parseList[forwardMostMove - 1], "#");
10626         break;
10627     }
10628 }
10629
10630 /* Updates currentMove if not pausing */
10631 void
10632 ShowMove (int fromX, int fromY, int toX, int toY)
10633 {
10634     int instant = (gameMode == PlayFromGameFile) ?
10635         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10636     if(appData.noGUI) return;
10637     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10638         if (!instant) {
10639             if (forwardMostMove == currentMove + 1) {
10640                 AnimateMove(boards[forwardMostMove - 1],
10641                             fromX, fromY, toX, toY);
10642             }
10643         }
10644         currentMove = forwardMostMove;
10645     }
10646
10647     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10648
10649     if (instant) return;
10650
10651     DisplayMove(currentMove - 1);
10652     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10653             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10654                 SetHighlights(fromX, fromY, toX, toY);
10655             }
10656     }
10657     DrawPosition(FALSE, boards[currentMove]);
10658     DisplayBothClocks();
10659     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10660 }
10661
10662 void
10663 SendEgtPath (ChessProgramState *cps)
10664 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10665         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10666
10667         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10668
10669         while(*p) {
10670             char c, *q = name+1, *r, *s;
10671
10672             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10673             while(*p && *p != ',') *q++ = *p++;
10674             *q++ = ':'; *q = 0;
10675             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10676                 strcmp(name, ",nalimov:") == 0 ) {
10677                 // take nalimov path from the menu-changeable option first, if it is defined
10678               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10679                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10680             } else
10681             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10682                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10683                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10684                 s = r = StrStr(s, ":") + 1; // beginning of path info
10685                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10686                 c = *r; *r = 0;             // temporarily null-terminate path info
10687                     *--q = 0;               // strip of trailig ':' from name
10688                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10689                 *r = c;
10690                 SendToProgram(buf,cps);     // send egtbpath command for this format
10691             }
10692             if(*p == ',') p++; // read away comma to position for next format name
10693         }
10694 }
10695
10696 static int
10697 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10698 {
10699       int width = 8, height = 8, holdings = 0;             // most common sizes
10700       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10701       // correct the deviations default for each variant
10702       if( v == VariantXiangqi ) width = 9,  height = 10;
10703       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10704       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10705       if( v == VariantCapablanca || v == VariantCapaRandom ||
10706           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10707                                 width = 10;
10708       if( v == VariantCourier ) width = 12;
10709       if( v == VariantSuper )                            holdings = 8;
10710       if( v == VariantGreat )   width = 10,              holdings = 8;
10711       if( v == VariantSChess )                           holdings = 7;
10712       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10713       if( v == VariantChuChess) width = 10, height = 10;
10714       if( v == VariantChu )     width = 12, height = 12;
10715       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10716              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10717              holdingsSize >= 0 && holdingsSize != holdings;
10718 }
10719
10720 char variantError[MSG_SIZ];
10721
10722 char *
10723 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10724 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10725       char *p, *variant = VariantName(v);
10726       static char b[MSG_SIZ];
10727       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10728            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10729                                                holdingsSize, variant); // cook up sized variant name
10730            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10731            if(StrStr(list, b) == NULL) {
10732                // specific sized variant not known, check if general sizing allowed
10733                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10734                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10735                             boardWidth, boardHeight, holdingsSize, engine);
10736                    return NULL;
10737                }
10738                /* [HGM] here we really should compare with the maximum supported board size */
10739            }
10740       } else snprintf(b, MSG_SIZ,"%s", variant);
10741       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10742       p = StrStr(list, b);
10743       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10744       if(p == NULL) {
10745           // occurs not at all in list, or only as sub-string
10746           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10747           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10748               int l = strlen(variantError);
10749               char *q;
10750               while(p != list && p[-1] != ',') p--;
10751               q = strchr(p, ',');
10752               if(q) *q = NULLCHAR;
10753               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10754               if(q) *q= ',';
10755           }
10756           return NULL;
10757       }
10758       return b;
10759 }
10760
10761 void
10762 InitChessProgram (ChessProgramState *cps, int setup)
10763 /* setup needed to setup FRC opening position */
10764 {
10765     char buf[MSG_SIZ], *b;
10766     if (appData.noChessProgram) return;
10767     hintRequested = FALSE;
10768     bookRequested = FALSE;
10769
10770     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10771     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10772     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10773     if(cps->memSize) { /* [HGM] memory */
10774       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10775         SendToProgram(buf, cps);
10776     }
10777     SendEgtPath(cps); /* [HGM] EGT */
10778     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10779       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10780         SendToProgram(buf, cps);
10781     }
10782
10783     setboardSpoiledMachineBlack = FALSE;
10784     SendToProgram(cps->initString, cps);
10785     if (gameInfo.variant != VariantNormal &&
10786         gameInfo.variant != VariantLoadable
10787         /* [HGM] also send variant if board size non-standard */
10788         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10789
10790       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10791                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10792       if (b == NULL) {
10793         VariantClass v;
10794         char c, *q = cps->variants, *p = strchr(q, ',');
10795         if(p) *p = NULLCHAR;
10796         v = StringToVariant(q);
10797         DisplayError(variantError, 0);
10798         if(v != VariantUnknown && cps == &first) {
10799             int w, h, s;
10800             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10801                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10802             ASSIGN(appData.variant, q);
10803             Reset(TRUE, FALSE);
10804         }
10805         if(p) *p = ',';
10806         return;
10807       }
10808
10809       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10810       SendToProgram(buf, cps);
10811     }
10812     currentlyInitializedVariant = gameInfo.variant;
10813
10814     /* [HGM] send opening position in FRC to first engine */
10815     if(setup) {
10816           SendToProgram("force\n", cps);
10817           SendBoard(cps, 0);
10818           /* engine is now in force mode! Set flag to wake it up after first move. */
10819           setboardSpoiledMachineBlack = 1;
10820     }
10821
10822     if (cps->sendICS) {
10823       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10824       SendToProgram(buf, cps);
10825     }
10826     cps->maybeThinking = FALSE;
10827     cps->offeredDraw = 0;
10828     if (!appData.icsActive) {
10829         SendTimeControl(cps, movesPerSession, timeControl,
10830                         timeIncrement, appData.searchDepth,
10831                         searchTime);
10832     }
10833     if (appData.showThinking
10834         // [HGM] thinking: four options require thinking output to be sent
10835         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10836                                 ) {
10837         SendToProgram("post\n", cps);
10838     }
10839     SendToProgram("hard\n", cps);
10840     if (!appData.ponderNextMove) {
10841         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10842            it without being sure what state we are in first.  "hard"
10843            is not a toggle, so that one is OK.
10844          */
10845         SendToProgram("easy\n", cps);
10846     }
10847     if (cps->usePing) {
10848       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10849       SendToProgram(buf, cps);
10850     }
10851     cps->initDone = TRUE;
10852     ClearEngineOutputPane(cps == &second);
10853 }
10854
10855
10856 void
10857 ResendOptions (ChessProgramState *cps)
10858 { // send the stored value of the options
10859   int i;
10860   char buf[MSG_SIZ];
10861   Option *opt = cps->option;
10862   for(i=0; i<cps->nrOptions; i++, opt++) {
10863       switch(opt->type) {
10864         case Spin:
10865         case Slider:
10866         case CheckBox:
10867             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10868           break;
10869         case ComboBox:
10870           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10871           break;
10872         default:
10873             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10874           break;
10875         case Button:
10876         case SaveButton:
10877           continue;
10878       }
10879       SendToProgram(buf, cps);
10880   }
10881 }
10882
10883 void
10884 StartChessProgram (ChessProgramState *cps)
10885 {
10886     char buf[MSG_SIZ];
10887     int err;
10888
10889     if (appData.noChessProgram) return;
10890     cps->initDone = FALSE;
10891
10892     if (strcmp(cps->host, "localhost") == 0) {
10893         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10894     } else if (*appData.remoteShell == NULLCHAR) {
10895         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10896     } else {
10897         if (*appData.remoteUser == NULLCHAR) {
10898           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10899                     cps->program);
10900         } else {
10901           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10902                     cps->host, appData.remoteUser, cps->program);
10903         }
10904         err = StartChildProcess(buf, "", &cps->pr);
10905     }
10906
10907     if (err != 0) {
10908       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10909         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10910         if(cps != &first) return;
10911         appData.noChessProgram = TRUE;
10912         ThawUI();
10913         SetNCPMode();
10914 //      DisplayFatalError(buf, err, 1);
10915 //      cps->pr = NoProc;
10916 //      cps->isr = NULL;
10917         return;
10918     }
10919
10920     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10921     if (cps->protocolVersion > 1) {
10922       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10923       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10924         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10925         cps->comboCnt = 0;  //                and values of combo boxes
10926       }
10927       SendToProgram(buf, cps);
10928       if(cps->reload) ResendOptions(cps);
10929     } else {
10930       SendToProgram("xboard\n", cps);
10931     }
10932 }
10933
10934 void
10935 TwoMachinesEventIfReady P((void))
10936 {
10937   static int curMess = 0;
10938   if (first.lastPing != first.lastPong) {
10939     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10940     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10941     return;
10942   }
10943   if (second.lastPing != second.lastPong) {
10944     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10945     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10946     return;
10947   }
10948   DisplayMessage("", ""); curMess = 0;
10949   TwoMachinesEvent();
10950 }
10951
10952 char *
10953 MakeName (char *template)
10954 {
10955     time_t clock;
10956     struct tm *tm;
10957     static char buf[MSG_SIZ];
10958     char *p = buf;
10959     int i;
10960
10961     clock = time((time_t *)NULL);
10962     tm = localtime(&clock);
10963
10964     while(*p++ = *template++) if(p[-1] == '%') {
10965         switch(*template++) {
10966           case 0:   *p = 0; return buf;
10967           case 'Y': i = tm->tm_year+1900; break;
10968           case 'y': i = tm->tm_year-100; break;
10969           case 'M': i = tm->tm_mon+1; break;
10970           case 'd': i = tm->tm_mday; break;
10971           case 'h': i = tm->tm_hour; break;
10972           case 'm': i = tm->tm_min; break;
10973           case 's': i = tm->tm_sec; break;
10974           default:  i = 0;
10975         }
10976         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10977     }
10978     return buf;
10979 }
10980
10981 int
10982 CountPlayers (char *p)
10983 {
10984     int n = 0;
10985     while(p = strchr(p, '\n')) p++, n++; // count participants
10986     return n;
10987 }
10988
10989 FILE *
10990 WriteTourneyFile (char *results, FILE *f)
10991 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10992     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10993     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10994         // create a file with tournament description
10995         fprintf(f, "-participants {%s}\n", appData.participants);
10996         fprintf(f, "-seedBase %d\n", appData.seedBase);
10997         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10998         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10999         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11000         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11001         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11002         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11003         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11004         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11005         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11006         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11007         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11008         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11009         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11010         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11011         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11012         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11013         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11014         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11015         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11016         fprintf(f, "-smpCores %d\n", appData.smpCores);
11017         if(searchTime > 0)
11018                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11019         else {
11020                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11021                 fprintf(f, "-tc %s\n", appData.timeControl);
11022                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11023         }
11024         fprintf(f, "-results \"%s\"\n", results);
11025     }
11026     return f;
11027 }
11028
11029 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11030
11031 void
11032 Substitute (char *participants, int expunge)
11033 {
11034     int i, changed, changes=0, nPlayers=0;
11035     char *p, *q, *r, buf[MSG_SIZ];
11036     if(participants == NULL) return;
11037     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11038     r = p = participants; q = appData.participants;
11039     while(*p && *p == *q) {
11040         if(*p == '\n') r = p+1, nPlayers++;
11041         p++; q++;
11042     }
11043     if(*p) { // difference
11044         while(*p && *p++ != '\n');
11045         while(*q && *q++ != '\n');
11046       changed = nPlayers;
11047         changes = 1 + (strcmp(p, q) != 0);
11048     }
11049     if(changes == 1) { // a single engine mnemonic was changed
11050         q = r; while(*q) nPlayers += (*q++ == '\n');
11051         p = buf; while(*r && (*p = *r++) != '\n') p++;
11052         *p = NULLCHAR;
11053         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11054         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11055         if(mnemonic[i]) { // The substitute is valid
11056             FILE *f;
11057             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11058                 flock(fileno(f), LOCK_EX);
11059                 ParseArgsFromFile(f);
11060                 fseek(f, 0, SEEK_SET);
11061                 FREE(appData.participants); appData.participants = participants;
11062                 if(expunge) { // erase results of replaced engine
11063                     int len = strlen(appData.results), w, b, dummy;
11064                     for(i=0; i<len; i++) {
11065                         Pairing(i, nPlayers, &w, &b, &dummy);
11066                         if((w == changed || b == changed) && appData.results[i] == '*') {
11067                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11068                             fclose(f);
11069                             return;
11070                         }
11071                     }
11072                     for(i=0; i<len; i++) {
11073                         Pairing(i, nPlayers, &w, &b, &dummy);
11074                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11075                     }
11076                 }
11077                 WriteTourneyFile(appData.results, f);
11078                 fclose(f); // release lock
11079                 return;
11080             }
11081         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11082     }
11083     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11084     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11085     free(participants);
11086     return;
11087 }
11088
11089 int
11090 CheckPlayers (char *participants)
11091 {
11092         int i;
11093         char buf[MSG_SIZ], *p;
11094         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11095         while(p = strchr(participants, '\n')) {
11096             *p = NULLCHAR;
11097             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11098             if(!mnemonic[i]) {
11099                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11100                 *p = '\n';
11101                 DisplayError(buf, 0);
11102                 return 1;
11103             }
11104             *p = '\n';
11105             participants = p + 1;
11106         }
11107         return 0;
11108 }
11109
11110 int
11111 CreateTourney (char *name)
11112 {
11113         FILE *f;
11114         if(matchMode && strcmp(name, appData.tourneyFile)) {
11115              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11116         }
11117         if(name[0] == NULLCHAR) {
11118             if(appData.participants[0])
11119                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11120             return 0;
11121         }
11122         f = fopen(name, "r");
11123         if(f) { // file exists
11124             ASSIGN(appData.tourneyFile, name);
11125             ParseArgsFromFile(f); // parse it
11126         } else {
11127             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11128             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11129                 DisplayError(_("Not enough participants"), 0);
11130                 return 0;
11131             }
11132             if(CheckPlayers(appData.participants)) return 0;
11133             ASSIGN(appData.tourneyFile, name);
11134             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11135             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11136         }
11137         fclose(f);
11138         appData.noChessProgram = FALSE;
11139         appData.clockMode = TRUE;
11140         SetGNUMode();
11141         return 1;
11142 }
11143
11144 int
11145 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11146 {
11147     char buf[MSG_SIZ], *p, *q;
11148     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11149     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11150     skip = !all && group[0]; // if group requested, we start in skip mode
11151     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11152         p = names; q = buf; header = 0;
11153         while(*p && *p != '\n') *q++ = *p++;
11154         *q = 0;
11155         if(*p == '\n') p++;
11156         if(buf[0] == '#') {
11157             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11158             depth++; // we must be entering a new group
11159             if(all) continue; // suppress printing group headers when complete list requested
11160             header = 1;
11161             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11162         }
11163         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11164         if(engineList[i]) free(engineList[i]);
11165         engineList[i] = strdup(buf);
11166         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11167         if(engineMnemonic[i]) free(engineMnemonic[i]);
11168         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11169             strcat(buf, " (");
11170             sscanf(q + 8, "%s", buf + strlen(buf));
11171             strcat(buf, ")");
11172         }
11173         engineMnemonic[i] = strdup(buf);
11174         i++;
11175     }
11176     engineList[i] = engineMnemonic[i] = NULL;
11177     return i;
11178 }
11179
11180 // following implemented as macro to avoid type limitations
11181 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11182
11183 void
11184 SwapEngines (int n)
11185 {   // swap settings for first engine and other engine (so far only some selected options)
11186     int h;
11187     char *p;
11188     if(n == 0) return;
11189     SWAP(directory, p)
11190     SWAP(chessProgram, p)
11191     SWAP(isUCI, h)
11192     SWAP(hasOwnBookUCI, h)
11193     SWAP(protocolVersion, h)
11194     SWAP(reuse, h)
11195     SWAP(scoreIsAbsolute, h)
11196     SWAP(timeOdds, h)
11197     SWAP(logo, p)
11198     SWAP(pgnName, p)
11199     SWAP(pvSAN, h)
11200     SWAP(engOptions, p)
11201     SWAP(engInitString, p)
11202     SWAP(computerString, p)
11203     SWAP(features, p)
11204     SWAP(fenOverride, p)
11205     SWAP(NPS, h)
11206     SWAP(accumulateTC, h)
11207     SWAP(drawDepth, h)
11208     SWAP(host, p)
11209     SWAP(pseudo, h)
11210 }
11211
11212 int
11213 GetEngineLine (char *s, int n)
11214 {
11215     int i;
11216     char buf[MSG_SIZ];
11217     extern char *icsNames;
11218     if(!s || !*s) return 0;
11219     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11220     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11221     if(!mnemonic[i]) return 0;
11222     if(n == 11) return 1; // just testing if there was a match
11223     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11224     if(n == 1) SwapEngines(n);
11225     ParseArgsFromString(buf);
11226     if(n == 1) SwapEngines(n);
11227     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11228         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11229         ParseArgsFromString(buf);
11230     }
11231     return 1;
11232 }
11233
11234 int
11235 SetPlayer (int player, char *p)
11236 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11237     int i;
11238     char buf[MSG_SIZ], *engineName;
11239     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11240     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11241     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11242     if(mnemonic[i]) {
11243         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11244         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11245         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11246         ParseArgsFromString(buf);
11247     } else { // no engine with this nickname is installed!
11248         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11249         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11250         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11251         ModeHighlight();
11252         DisplayError(buf, 0);
11253         return 0;
11254     }
11255     free(engineName);
11256     return i;
11257 }
11258
11259 char *recentEngines;
11260
11261 void
11262 RecentEngineEvent (int nr)
11263 {
11264     int n;
11265 //    SwapEngines(1); // bump first to second
11266 //    ReplaceEngine(&second, 1); // and load it there
11267     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11268     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11269     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11270         ReplaceEngine(&first, 0);
11271         FloatToFront(&appData.recentEngineList, command[n]);
11272     }
11273 }
11274
11275 int
11276 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11277 {   // determine players from game number
11278     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11279
11280     if(appData.tourneyType == 0) {
11281         roundsPerCycle = (nPlayers - 1) | 1;
11282         pairingsPerRound = nPlayers / 2;
11283     } else if(appData.tourneyType > 0) {
11284         roundsPerCycle = nPlayers - appData.tourneyType;
11285         pairingsPerRound = appData.tourneyType;
11286     }
11287     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11288     gamesPerCycle = gamesPerRound * roundsPerCycle;
11289     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11290     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11291     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11292     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11293     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11294     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11295
11296     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11297     if(appData.roundSync) *syncInterval = gamesPerRound;
11298
11299     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11300
11301     if(appData.tourneyType == 0) {
11302         if(curPairing == (nPlayers-1)/2 ) {
11303             *whitePlayer = curRound;
11304             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11305         } else {
11306             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11307             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11308             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11309             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11310         }
11311     } else if(appData.tourneyType > 1) {
11312         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11313         *whitePlayer = curRound + appData.tourneyType;
11314     } else if(appData.tourneyType > 0) {
11315         *whitePlayer = curPairing;
11316         *blackPlayer = curRound + appData.tourneyType;
11317     }
11318
11319     // take care of white/black alternation per round.
11320     // For cycles and games this is already taken care of by default, derived from matchGame!
11321     return curRound & 1;
11322 }
11323
11324 int
11325 NextTourneyGame (int nr, int *swapColors)
11326 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11327     char *p, *q;
11328     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11329     FILE *tf;
11330     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11331     tf = fopen(appData.tourneyFile, "r");
11332     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11333     ParseArgsFromFile(tf); fclose(tf);
11334     InitTimeControls(); // TC might be altered from tourney file
11335
11336     nPlayers = CountPlayers(appData.participants); // count participants
11337     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11338     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11339
11340     if(syncInterval) {
11341         p = q = appData.results;
11342         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11343         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11344             DisplayMessage(_("Waiting for other game(s)"),"");
11345             waitingForGame = TRUE;
11346             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11347             return 0;
11348         }
11349         waitingForGame = FALSE;
11350     }
11351
11352     if(appData.tourneyType < 0) {
11353         if(nr>=0 && !pairingReceived) {
11354             char buf[1<<16];
11355             if(pairing.pr == NoProc) {
11356                 if(!appData.pairingEngine[0]) {
11357                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11358                     return 0;
11359                 }
11360                 StartChessProgram(&pairing); // starts the pairing engine
11361             }
11362             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11363             SendToProgram(buf, &pairing);
11364             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11365             SendToProgram(buf, &pairing);
11366             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11367         }
11368         pairingReceived = 0;                              // ... so we continue here
11369         *swapColors = 0;
11370         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11371         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11372         matchGame = 1; roundNr = nr / syncInterval + 1;
11373     }
11374
11375     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11376
11377     // redefine engines, engine dir, etc.
11378     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11379     if(first.pr == NoProc) {
11380       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11381       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11382     }
11383     if(second.pr == NoProc) {
11384       SwapEngines(1);
11385       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11386       SwapEngines(1);         // and make that valid for second engine by swapping
11387       InitEngine(&second, 1);
11388     }
11389     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11390     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11391     return OK;
11392 }
11393
11394 void
11395 NextMatchGame ()
11396 {   // performs game initialization that does not invoke engines, and then tries to start the game
11397     int res, firstWhite, swapColors = 0;
11398     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11399     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
11400         char buf[MSG_SIZ];
11401         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11402         if(strcmp(buf, currentDebugFile)) { // name has changed
11403             FILE *f = fopen(buf, "w");
11404             if(f) { // if opening the new file failed, just keep using the old one
11405                 ASSIGN(currentDebugFile, buf);
11406                 fclose(debugFP);
11407                 debugFP = f;
11408             }
11409             if(appData.serverFileName) {
11410                 if(serverFP) fclose(serverFP);
11411                 serverFP = fopen(appData.serverFileName, "w");
11412                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11413                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11414             }
11415         }
11416     }
11417     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11418     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11419     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11420     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11421     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11422     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11423     Reset(FALSE, first.pr != NoProc);
11424     res = LoadGameOrPosition(matchGame); // setup game
11425     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11426     if(!res) return; // abort when bad game/pos file
11427     TwoMachinesEvent();
11428 }
11429
11430 void
11431 UserAdjudicationEvent (int result)
11432 {
11433     ChessMove gameResult = GameIsDrawn;
11434
11435     if( result > 0 ) {
11436         gameResult = WhiteWins;
11437     }
11438     else if( result < 0 ) {
11439         gameResult = BlackWins;
11440     }
11441
11442     if( gameMode == TwoMachinesPlay ) {
11443         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11444     }
11445 }
11446
11447
11448 // [HGM] save: calculate checksum of game to make games easily identifiable
11449 int
11450 StringCheckSum (char *s)
11451 {
11452         int i = 0;
11453         if(s==NULL) return 0;
11454         while(*s) i = i*259 + *s++;
11455         return i;
11456 }
11457
11458 int
11459 GameCheckSum ()
11460 {
11461         int i, sum=0;
11462         for(i=backwardMostMove; i<forwardMostMove; i++) {
11463                 sum += pvInfoList[i].depth;
11464                 sum += StringCheckSum(parseList[i]);
11465                 sum += StringCheckSum(commentList[i]);
11466                 sum *= 261;
11467         }
11468         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11469         return sum + StringCheckSum(commentList[i]);
11470 } // end of save patch
11471
11472 void
11473 GameEnds (ChessMove result, char *resultDetails, int whosays)
11474 {
11475     GameMode nextGameMode;
11476     int isIcsGame;
11477     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11478
11479     if(endingGame) return; /* [HGM] crash: forbid recursion */
11480     endingGame = 1;
11481     if(twoBoards) { // [HGM] dual: switch back to one board
11482         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11483         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11484     }
11485     if (appData.debugMode) {
11486       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11487               result, resultDetails ? resultDetails : "(null)", whosays);
11488     }
11489
11490     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11491
11492     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11493
11494     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11495         /* If we are playing on ICS, the server decides when the
11496            game is over, but the engine can offer to draw, claim
11497            a draw, or resign.
11498          */
11499 #if ZIPPY
11500         if (appData.zippyPlay && first.initDone) {
11501             if (result == GameIsDrawn) {
11502                 /* In case draw still needs to be claimed */
11503                 SendToICS(ics_prefix);
11504                 SendToICS("draw\n");
11505             } else if (StrCaseStr(resultDetails, "resign")) {
11506                 SendToICS(ics_prefix);
11507                 SendToICS("resign\n");
11508             }
11509         }
11510 #endif
11511         endingGame = 0; /* [HGM] crash */
11512         return;
11513     }
11514
11515     /* If we're loading the game from a file, stop */
11516     if (whosays == GE_FILE) {
11517       (void) StopLoadGameTimer();
11518       gameFileFP = NULL;
11519     }
11520
11521     /* Cancel draw offers */
11522     first.offeredDraw = second.offeredDraw = 0;
11523
11524     /* If this is an ICS game, only ICS can really say it's done;
11525        if not, anyone can. */
11526     isIcsGame = (gameMode == IcsPlayingWhite ||
11527                  gameMode == IcsPlayingBlack ||
11528                  gameMode == IcsObserving    ||
11529                  gameMode == IcsExamining);
11530
11531     if (!isIcsGame || whosays == GE_ICS) {
11532         /* OK -- not an ICS game, or ICS said it was done */
11533         StopClocks();
11534         if (!isIcsGame && !appData.noChessProgram)
11535           SetUserThinkingEnables();
11536
11537         /* [HGM] if a machine claims the game end we verify this claim */
11538         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11539             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11540                 char claimer;
11541                 ChessMove trueResult = (ChessMove) -1;
11542
11543                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11544                                             first.twoMachinesColor[0] :
11545                                             second.twoMachinesColor[0] ;
11546
11547                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11548                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11549                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11550                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11551                 } else
11552                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11553                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11554                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11555                 } else
11556                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11557                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11558                 }
11559
11560                 // now verify win claims, but not in drop games, as we don't understand those yet
11561                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11562                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11563                     (result == WhiteWins && claimer == 'w' ||
11564                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11565                       if (appData.debugMode) {
11566                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11567                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11568                       }
11569                       if(result != trueResult) {
11570                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11571                               result = claimer == 'w' ? BlackWins : WhiteWins;
11572                               resultDetails = buf;
11573                       }
11574                 } else
11575                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11576                     && (forwardMostMove <= backwardMostMove ||
11577                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11578                         (claimer=='b')==(forwardMostMove&1))
11579                                                                                   ) {
11580                       /* [HGM] verify: draws that were not flagged are false claims */
11581                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11582                       result = claimer == 'w' ? BlackWins : WhiteWins;
11583                       resultDetails = buf;
11584                 }
11585                 /* (Claiming a loss is accepted no questions asked!) */
11586             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11587                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11588                 result = GameUnfinished;
11589                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11590             }
11591             /* [HGM] bare: don't allow bare King to win */
11592             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11593                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11594                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11595                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11596                && result != GameIsDrawn)
11597             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11598                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11599                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11600                         if(p >= 0 && p <= (int)WhiteKing) k++;
11601                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11602                 }
11603                 if (appData.debugMode) {
11604                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11605                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11606                 }
11607                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11608                         result = GameIsDrawn;
11609                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11610                         resultDetails = buf;
11611                 }
11612             }
11613         }
11614
11615
11616         if(serverMoves != NULL && !loadFlag) { char c = '=';
11617             if(result==WhiteWins) c = '+';
11618             if(result==BlackWins) c = '-';
11619             if(resultDetails != NULL)
11620                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11621         }
11622         if (resultDetails != NULL) {
11623             gameInfo.result = result;
11624             gameInfo.resultDetails = StrSave(resultDetails);
11625
11626             /* display last move only if game was not loaded from file */
11627             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11628                 DisplayMove(currentMove - 1);
11629
11630             if (forwardMostMove != 0) {
11631                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11632                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11633                                                                 ) {
11634                     if (*appData.saveGameFile != NULLCHAR) {
11635                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11636                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11637                         else
11638                         SaveGameToFile(appData.saveGameFile, TRUE);
11639                     } else if (appData.autoSaveGames) {
11640                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11641                     }
11642                     if (*appData.savePositionFile != NULLCHAR) {
11643                         SavePositionToFile(appData.savePositionFile);
11644                     }
11645                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11646                 }
11647             }
11648
11649             /* Tell program how game ended in case it is learning */
11650             /* [HGM] Moved this to after saving the PGN, just in case */
11651             /* engine died and we got here through time loss. In that */
11652             /* case we will get a fatal error writing the pipe, which */
11653             /* would otherwise lose us the PGN.                       */
11654             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11655             /* output during GameEnds should never be fatal anymore   */
11656             if (gameMode == MachinePlaysWhite ||
11657                 gameMode == MachinePlaysBlack ||
11658                 gameMode == TwoMachinesPlay ||
11659                 gameMode == IcsPlayingWhite ||
11660                 gameMode == IcsPlayingBlack ||
11661                 gameMode == BeginningOfGame) {
11662                 char buf[MSG_SIZ];
11663                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11664                         resultDetails);
11665                 if (first.pr != NoProc) {
11666                     SendToProgram(buf, &first);
11667                 }
11668                 if (second.pr != NoProc &&
11669                     gameMode == TwoMachinesPlay) {
11670                     SendToProgram(buf, &second);
11671                 }
11672             }
11673         }
11674
11675         if (appData.icsActive) {
11676             if (appData.quietPlay &&
11677                 (gameMode == IcsPlayingWhite ||
11678                  gameMode == IcsPlayingBlack)) {
11679                 SendToICS(ics_prefix);
11680                 SendToICS("set shout 1\n");
11681             }
11682             nextGameMode = IcsIdle;
11683             ics_user_moved = FALSE;
11684             /* clean up premove.  It's ugly when the game has ended and the
11685              * premove highlights are still on the board.
11686              */
11687             if (gotPremove) {
11688               gotPremove = FALSE;
11689               ClearPremoveHighlights();
11690               DrawPosition(FALSE, boards[currentMove]);
11691             }
11692             if (whosays == GE_ICS) {
11693                 switch (result) {
11694                 case WhiteWins:
11695                     if (gameMode == IcsPlayingWhite)
11696                         PlayIcsWinSound();
11697                     else if(gameMode == IcsPlayingBlack)
11698                         PlayIcsLossSound();
11699                     break;
11700                 case BlackWins:
11701                     if (gameMode == IcsPlayingBlack)
11702                         PlayIcsWinSound();
11703                     else if(gameMode == IcsPlayingWhite)
11704                         PlayIcsLossSound();
11705                     break;
11706                 case GameIsDrawn:
11707                     PlayIcsDrawSound();
11708                     break;
11709                 default:
11710                     PlayIcsUnfinishedSound();
11711                 }
11712             }
11713             if(appData.quitNext) { ExitEvent(0); return; }
11714         } else if (gameMode == EditGame ||
11715                    gameMode == PlayFromGameFile ||
11716                    gameMode == AnalyzeMode ||
11717                    gameMode == AnalyzeFile) {
11718             nextGameMode = gameMode;
11719         } else {
11720             nextGameMode = EndOfGame;
11721         }
11722         pausing = FALSE;
11723         ModeHighlight();
11724     } else {
11725         nextGameMode = gameMode;
11726     }
11727
11728     if (appData.noChessProgram) {
11729         gameMode = nextGameMode;
11730         ModeHighlight();
11731         endingGame = 0; /* [HGM] crash */
11732         return;
11733     }
11734
11735     if (first.reuse) {
11736         /* Put first chess program into idle state */
11737         if (first.pr != NoProc &&
11738             (gameMode == MachinePlaysWhite ||
11739              gameMode == MachinePlaysBlack ||
11740              gameMode == TwoMachinesPlay ||
11741              gameMode == IcsPlayingWhite ||
11742              gameMode == IcsPlayingBlack ||
11743              gameMode == BeginningOfGame)) {
11744             SendToProgram("force\n", &first);
11745             if (first.usePing) {
11746               char buf[MSG_SIZ];
11747               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11748               SendToProgram(buf, &first);
11749             }
11750         }
11751     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11752         /* Kill off first chess program */
11753         if (first.isr != NULL)
11754           RemoveInputSource(first.isr);
11755         first.isr = NULL;
11756
11757         if (first.pr != NoProc) {
11758             ExitAnalyzeMode();
11759             DoSleep( appData.delayBeforeQuit );
11760             SendToProgram("quit\n", &first);
11761             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11762             first.reload = TRUE;
11763         }
11764         first.pr = NoProc;
11765     }
11766     if (second.reuse) {
11767         /* Put second chess program into idle state */
11768         if (second.pr != NoProc &&
11769             gameMode == TwoMachinesPlay) {
11770             SendToProgram("force\n", &second);
11771             if (second.usePing) {
11772               char buf[MSG_SIZ];
11773               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11774               SendToProgram(buf, &second);
11775             }
11776         }
11777     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11778         /* Kill off second chess program */
11779         if (second.isr != NULL)
11780           RemoveInputSource(second.isr);
11781         second.isr = NULL;
11782
11783         if (second.pr != NoProc) {
11784             DoSleep( appData.delayBeforeQuit );
11785             SendToProgram("quit\n", &second);
11786             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11787             second.reload = TRUE;
11788         }
11789         second.pr = NoProc;
11790     }
11791
11792     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11793         char resChar = '=';
11794         switch (result) {
11795         case WhiteWins:
11796           resChar = '+';
11797           if (first.twoMachinesColor[0] == 'w') {
11798             first.matchWins++;
11799           } else {
11800             second.matchWins++;
11801           }
11802           break;
11803         case BlackWins:
11804           resChar = '-';
11805           if (first.twoMachinesColor[0] == 'b') {
11806             first.matchWins++;
11807           } else {
11808             second.matchWins++;
11809           }
11810           break;
11811         case GameUnfinished:
11812           resChar = ' ';
11813         default:
11814           break;
11815         }
11816
11817         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11818         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11819             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11820             ReserveGame(nextGame, resChar); // sets nextGame
11821             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11822             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11823         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11824
11825         if (nextGame <= appData.matchGames && !abortMatch) {
11826             gameMode = nextGameMode;
11827             matchGame = nextGame; // this will be overruled in tourney mode!
11828             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11829             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11830             endingGame = 0; /* [HGM] crash */
11831             return;
11832         } else {
11833             gameMode = nextGameMode;
11834             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11835                      first.tidy, second.tidy,
11836                      first.matchWins, second.matchWins,
11837                      appData.matchGames - (first.matchWins + second.matchWins));
11838             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11839             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11840             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11841             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11842                 first.twoMachinesColor = "black\n";
11843                 second.twoMachinesColor = "white\n";
11844             } else {
11845                 first.twoMachinesColor = "white\n";
11846                 second.twoMachinesColor = "black\n";
11847             }
11848         }
11849     }
11850     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11851         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11852       ExitAnalyzeMode();
11853     gameMode = nextGameMode;
11854     ModeHighlight();
11855     endingGame = 0;  /* [HGM] crash */
11856     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11857         if(matchMode == TRUE) { // match through command line: exit with or without popup
11858             if(ranking) {
11859                 ToNrEvent(forwardMostMove);
11860                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11861                 else ExitEvent(0);
11862             } else DisplayFatalError(buf, 0, 0);
11863         } else { // match through menu; just stop, with or without popup
11864             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11865             ModeHighlight();
11866             if(ranking){
11867                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11868             } else DisplayNote(buf);
11869       }
11870       if(ranking) free(ranking);
11871     }
11872 }
11873
11874 /* Assumes program was just initialized (initString sent).
11875    Leaves program in force mode. */
11876 void
11877 FeedMovesToProgram (ChessProgramState *cps, int upto)
11878 {
11879     int i;
11880
11881     if (appData.debugMode)
11882       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11883               startedFromSetupPosition ? "position and " : "",
11884               backwardMostMove, upto, cps->which);
11885     if(currentlyInitializedVariant != gameInfo.variant) {
11886       char buf[MSG_SIZ];
11887         // [HGM] variantswitch: make engine aware of new variant
11888         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11889                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11890                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11891         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11892         SendToProgram(buf, cps);
11893         currentlyInitializedVariant = gameInfo.variant;
11894     }
11895     SendToProgram("force\n", cps);
11896     if (startedFromSetupPosition) {
11897         SendBoard(cps, backwardMostMove);
11898     if (appData.debugMode) {
11899         fprintf(debugFP, "feedMoves\n");
11900     }
11901     }
11902     for (i = backwardMostMove; i < upto; i++) {
11903         SendMoveToProgram(i, cps);
11904     }
11905 }
11906
11907
11908 int
11909 ResurrectChessProgram ()
11910 {
11911      /* The chess program may have exited.
11912         If so, restart it and feed it all the moves made so far. */
11913     static int doInit = 0;
11914
11915     if (appData.noChessProgram) return 1;
11916
11917     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11918         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11919         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11920         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11921     } else {
11922         if (first.pr != NoProc) return 1;
11923         StartChessProgram(&first);
11924     }
11925     InitChessProgram(&first, FALSE);
11926     FeedMovesToProgram(&first, currentMove);
11927
11928     if (!first.sendTime) {
11929         /* can't tell gnuchess what its clock should read,
11930            so we bow to its notion. */
11931         ResetClocks();
11932         timeRemaining[0][currentMove] = whiteTimeRemaining;
11933         timeRemaining[1][currentMove] = blackTimeRemaining;
11934     }
11935
11936     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11937                 appData.icsEngineAnalyze) && first.analysisSupport) {
11938       SendToProgram("analyze\n", &first);
11939       first.analyzing = TRUE;
11940     }
11941     return 1;
11942 }
11943
11944 /*
11945  * Button procedures
11946  */
11947 void
11948 Reset (int redraw, int init)
11949 {
11950     int i;
11951
11952     if (appData.debugMode) {
11953         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11954                 redraw, init, gameMode);
11955     }
11956     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11957     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11958     CleanupTail(); // [HGM] vari: delete any stored variations
11959     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11960     pausing = pauseExamInvalid = FALSE;
11961     startedFromSetupPosition = blackPlaysFirst = FALSE;
11962     firstMove = TRUE;
11963     whiteFlag = blackFlag = FALSE;
11964     userOfferedDraw = FALSE;
11965     hintRequested = bookRequested = FALSE;
11966     first.maybeThinking = FALSE;
11967     second.maybeThinking = FALSE;
11968     first.bookSuspend = FALSE; // [HGM] book
11969     second.bookSuspend = FALSE;
11970     thinkOutput[0] = NULLCHAR;
11971     lastHint[0] = NULLCHAR;
11972     ClearGameInfo(&gameInfo);
11973     gameInfo.variant = StringToVariant(appData.variant);
11974     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11975     ics_user_moved = ics_clock_paused = FALSE;
11976     ics_getting_history = H_FALSE;
11977     ics_gamenum = -1;
11978     white_holding[0] = black_holding[0] = NULLCHAR;
11979     ClearProgramStats();
11980     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11981
11982     ResetFrontEnd();
11983     ClearHighlights();
11984     flipView = appData.flipView;
11985     ClearPremoveHighlights();
11986     gotPremove = FALSE;
11987     alarmSounded = FALSE;
11988     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
11989
11990     GameEnds(EndOfFile, NULL, GE_PLAYER);
11991     if(appData.serverMovesName != NULL) {
11992         /* [HGM] prepare to make moves file for broadcasting */
11993         clock_t t = clock();
11994         if(serverMoves != NULL) fclose(serverMoves);
11995         serverMoves = fopen(appData.serverMovesName, "r");
11996         if(serverMoves != NULL) {
11997             fclose(serverMoves);
11998             /* delay 15 sec before overwriting, so all clients can see end */
11999             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12000         }
12001         serverMoves = fopen(appData.serverMovesName, "w");
12002     }
12003
12004     ExitAnalyzeMode();
12005     gameMode = BeginningOfGame;
12006     ModeHighlight();
12007     if(appData.icsActive) gameInfo.variant = VariantNormal;
12008     currentMove = forwardMostMove = backwardMostMove = 0;
12009     MarkTargetSquares(1);
12010     InitPosition(redraw);
12011     for (i = 0; i < MAX_MOVES; i++) {
12012         if (commentList[i] != NULL) {
12013             free(commentList[i]);
12014             commentList[i] = NULL;
12015         }
12016     }
12017     ResetClocks();
12018     timeRemaining[0][0] = whiteTimeRemaining;
12019     timeRemaining[1][0] = blackTimeRemaining;
12020
12021     if (first.pr == NoProc) {
12022         StartChessProgram(&first);
12023     }
12024     if (init) {
12025             InitChessProgram(&first, startedFromSetupPosition);
12026     }
12027     DisplayTitle("");
12028     DisplayMessage("", "");
12029     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12030     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12031     ClearMap();        // [HGM] exclude: invalidate map
12032 }
12033
12034 void
12035 AutoPlayGameLoop ()
12036 {
12037     for (;;) {
12038         if (!AutoPlayOneMove())
12039           return;
12040         if (matchMode || appData.timeDelay == 0)
12041           continue;
12042         if (appData.timeDelay < 0)
12043           return;
12044         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12045         break;
12046     }
12047 }
12048
12049 void
12050 AnalyzeNextGame()
12051 {
12052     ReloadGame(1); // next game
12053 }
12054
12055 int
12056 AutoPlayOneMove ()
12057 {
12058     int fromX, fromY, toX, toY;
12059
12060     if (appData.debugMode) {
12061       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12062     }
12063
12064     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12065       return FALSE;
12066
12067     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12068       pvInfoList[currentMove].depth = programStats.depth;
12069       pvInfoList[currentMove].score = programStats.score;
12070       pvInfoList[currentMove].time  = 0;
12071       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12072       else { // append analysis of final position as comment
12073         char buf[MSG_SIZ];
12074         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12075         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12076       }
12077       programStats.depth = 0;
12078     }
12079
12080     if (currentMove >= forwardMostMove) {
12081       if(gameMode == AnalyzeFile) {
12082           if(appData.loadGameIndex == -1) {
12083             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12084           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12085           } else {
12086           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12087         }
12088       }
12089 //      gameMode = EndOfGame;
12090 //      ModeHighlight();
12091
12092       /* [AS] Clear current move marker at the end of a game */
12093       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12094
12095       return FALSE;
12096     }
12097
12098     toX = moveList[currentMove][2] - AAA;
12099     toY = moveList[currentMove][3] - ONE;
12100
12101     if (moveList[currentMove][1] == '@') {
12102         if (appData.highlightLastMove) {
12103             SetHighlights(-1, -1, toX, toY);
12104         }
12105     } else {
12106         fromX = moveList[currentMove][0] - AAA;
12107         fromY = moveList[currentMove][1] - ONE;
12108
12109         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12110
12111         if(moveList[currentMove][4] == ';') { // multi-leg
12112             killX = moveList[currentMove][5] - AAA;
12113             killY = moveList[currentMove][6] - ONE;
12114         }
12115         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12116         killX = killY = -1;
12117
12118         if (appData.highlightLastMove) {
12119             SetHighlights(fromX, fromY, toX, toY);
12120         }
12121     }
12122     DisplayMove(currentMove);
12123     SendMoveToProgram(currentMove++, &first);
12124     DisplayBothClocks();
12125     DrawPosition(FALSE, boards[currentMove]);
12126     // [HGM] PV info: always display, routine tests if empty
12127     DisplayComment(currentMove - 1, commentList[currentMove]);
12128     return TRUE;
12129 }
12130
12131
12132 int
12133 LoadGameOneMove (ChessMove readAhead)
12134 {
12135     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12136     char promoChar = NULLCHAR;
12137     ChessMove moveType;
12138     char move[MSG_SIZ];
12139     char *p, *q;
12140
12141     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12142         gameMode != AnalyzeMode && gameMode != Training) {
12143         gameFileFP = NULL;
12144         return FALSE;
12145     }
12146
12147     yyboardindex = forwardMostMove;
12148     if (readAhead != EndOfFile) {
12149       moveType = readAhead;
12150     } else {
12151       if (gameFileFP == NULL)
12152           return FALSE;
12153       moveType = (ChessMove) Myylex();
12154     }
12155
12156     done = FALSE;
12157     switch (moveType) {
12158       case Comment:
12159         if (appData.debugMode)
12160           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12161         p = yy_text;
12162
12163         /* append the comment but don't display it */
12164         AppendComment(currentMove, p, FALSE);
12165         return TRUE;
12166
12167       case WhiteCapturesEnPassant:
12168       case BlackCapturesEnPassant:
12169       case WhitePromotion:
12170       case BlackPromotion:
12171       case WhiteNonPromotion:
12172       case BlackNonPromotion:
12173       case NormalMove:
12174       case FirstLeg:
12175       case WhiteKingSideCastle:
12176       case WhiteQueenSideCastle:
12177       case BlackKingSideCastle:
12178       case BlackQueenSideCastle:
12179       case WhiteKingSideCastleWild:
12180       case WhiteQueenSideCastleWild:
12181       case BlackKingSideCastleWild:
12182       case BlackQueenSideCastleWild:
12183       /* PUSH Fabien */
12184       case WhiteHSideCastleFR:
12185       case WhiteASideCastleFR:
12186       case BlackHSideCastleFR:
12187       case BlackASideCastleFR:
12188       /* POP Fabien */
12189         if (appData.debugMode)
12190           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12191         fromX = currentMoveString[0] - AAA;
12192         fromY = currentMoveString[1] - ONE;
12193         toX = currentMoveString[2] - AAA;
12194         toY = currentMoveString[3] - ONE;
12195         promoChar = currentMoveString[4];
12196         if(promoChar == ';') promoChar = currentMoveString[7];
12197         break;
12198
12199       case WhiteDrop:
12200       case BlackDrop:
12201         if (appData.debugMode)
12202           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12203         fromX = moveType == WhiteDrop ?
12204           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12205         (int) CharToPiece(ToLower(currentMoveString[0]));
12206         fromY = DROP_RANK;
12207         toX = currentMoveString[2] - AAA;
12208         toY = currentMoveString[3] - ONE;
12209         break;
12210
12211       case WhiteWins:
12212       case BlackWins:
12213       case GameIsDrawn:
12214       case GameUnfinished:
12215         if (appData.debugMode)
12216           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12217         p = strchr(yy_text, '{');
12218         if (p == NULL) p = strchr(yy_text, '(');
12219         if (p == NULL) {
12220             p = yy_text;
12221             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12222         } else {
12223             q = strchr(p, *p == '{' ? '}' : ')');
12224             if (q != NULL) *q = NULLCHAR;
12225             p++;
12226         }
12227         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12228         GameEnds(moveType, p, GE_FILE);
12229         done = TRUE;
12230         if (cmailMsgLoaded) {
12231             ClearHighlights();
12232             flipView = WhiteOnMove(currentMove);
12233             if (moveType == GameUnfinished) flipView = !flipView;
12234             if (appData.debugMode)
12235               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12236         }
12237         break;
12238
12239       case EndOfFile:
12240         if (appData.debugMode)
12241           fprintf(debugFP, "Parser hit end of file\n");
12242         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12243           case MT_NONE:
12244           case MT_CHECK:
12245             break;
12246           case MT_CHECKMATE:
12247           case MT_STAINMATE:
12248             if (WhiteOnMove(currentMove)) {
12249                 GameEnds(BlackWins, "Black mates", GE_FILE);
12250             } else {
12251                 GameEnds(WhiteWins, "White mates", GE_FILE);
12252             }
12253             break;
12254           case MT_STALEMATE:
12255             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12256             break;
12257         }
12258         done = TRUE;
12259         break;
12260
12261       case MoveNumberOne:
12262         if (lastLoadGameStart == GNUChessGame) {
12263             /* GNUChessGames have numbers, but they aren't move numbers */
12264             if (appData.debugMode)
12265               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12266                       yy_text, (int) moveType);
12267             return LoadGameOneMove(EndOfFile); /* tail recursion */
12268         }
12269         /* else fall thru */
12270
12271       case XBoardGame:
12272       case GNUChessGame:
12273       case PGNTag:
12274         /* Reached start of next game in file */
12275         if (appData.debugMode)
12276           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12277         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12278           case MT_NONE:
12279           case MT_CHECK:
12280             break;
12281           case MT_CHECKMATE:
12282           case MT_STAINMATE:
12283             if (WhiteOnMove(currentMove)) {
12284                 GameEnds(BlackWins, "Black mates", GE_FILE);
12285             } else {
12286                 GameEnds(WhiteWins, "White mates", GE_FILE);
12287             }
12288             break;
12289           case MT_STALEMATE:
12290             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12291             break;
12292         }
12293         done = TRUE;
12294         break;
12295
12296       case PositionDiagram:     /* should not happen; ignore */
12297       case ElapsedTime:         /* ignore */
12298       case NAG:                 /* ignore */
12299         if (appData.debugMode)
12300           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12301                   yy_text, (int) moveType);
12302         return LoadGameOneMove(EndOfFile); /* tail recursion */
12303
12304       case IllegalMove:
12305         if (appData.testLegality) {
12306             if (appData.debugMode)
12307               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12308             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12309                     (forwardMostMove / 2) + 1,
12310                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12311             DisplayError(move, 0);
12312             done = TRUE;
12313         } else {
12314             if (appData.debugMode)
12315               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12316                       yy_text, currentMoveString);
12317             if(currentMoveString[1] == '@') {
12318                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12319                 fromY = DROP_RANK;
12320             } else {
12321                 fromX = currentMoveString[0] - AAA;
12322                 fromY = currentMoveString[1] - ONE;
12323             }
12324             toX = currentMoveString[2] - AAA;
12325             toY = currentMoveString[3] - ONE;
12326             promoChar = currentMoveString[4];
12327         }
12328         break;
12329
12330       case AmbiguousMove:
12331         if (appData.debugMode)
12332           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12333         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12334                 (forwardMostMove / 2) + 1,
12335                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12336         DisplayError(move, 0);
12337         done = TRUE;
12338         break;
12339
12340       default:
12341       case ImpossibleMove:
12342         if (appData.debugMode)
12343           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12344         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12345                 (forwardMostMove / 2) + 1,
12346                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12347         DisplayError(move, 0);
12348         done = TRUE;
12349         break;
12350     }
12351
12352     if (done) {
12353         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12354             DrawPosition(FALSE, boards[currentMove]);
12355             DisplayBothClocks();
12356             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12357               DisplayComment(currentMove - 1, commentList[currentMove]);
12358         }
12359         (void) StopLoadGameTimer();
12360         gameFileFP = NULL;
12361         cmailOldMove = forwardMostMove;
12362         return FALSE;
12363     } else {
12364         /* currentMoveString is set as a side-effect of yylex */
12365
12366         thinkOutput[0] = NULLCHAR;
12367         MakeMove(fromX, fromY, toX, toY, promoChar);
12368         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12369         currentMove = forwardMostMove;
12370         return TRUE;
12371     }
12372 }
12373
12374 /* Load the nth game from the given file */
12375 int
12376 LoadGameFromFile (char *filename, int n, char *title, int useList)
12377 {
12378     FILE *f;
12379     char buf[MSG_SIZ];
12380
12381     if (strcmp(filename, "-") == 0) {
12382         f = stdin;
12383         title = "stdin";
12384     } else {
12385         f = fopen(filename, "rb");
12386         if (f == NULL) {
12387           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12388             DisplayError(buf, errno);
12389             return FALSE;
12390         }
12391     }
12392     if (fseek(f, 0, 0) == -1) {
12393         /* f is not seekable; probably a pipe */
12394         useList = FALSE;
12395     }
12396     if (useList && n == 0) {
12397         int error = GameListBuild(f);
12398         if (error) {
12399             DisplayError(_("Cannot build game list"), error);
12400         } else if (!ListEmpty(&gameList) &&
12401                    ((ListGame *) gameList.tailPred)->number > 1) {
12402             GameListPopUp(f, title);
12403             return TRUE;
12404         }
12405         GameListDestroy();
12406         n = 1;
12407     }
12408     if (n == 0) n = 1;
12409     return LoadGame(f, n, title, FALSE);
12410 }
12411
12412
12413 void
12414 MakeRegisteredMove ()
12415 {
12416     int fromX, fromY, toX, toY;
12417     char promoChar;
12418     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12419         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12420           case CMAIL_MOVE:
12421           case CMAIL_DRAW:
12422             if (appData.debugMode)
12423               fprintf(debugFP, "Restoring %s for game %d\n",
12424                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12425
12426             thinkOutput[0] = NULLCHAR;
12427             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12428             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12429             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12430             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12431             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12432             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12433             MakeMove(fromX, fromY, toX, toY, promoChar);
12434             ShowMove(fromX, fromY, toX, toY);
12435
12436             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12437               case MT_NONE:
12438               case MT_CHECK:
12439                 break;
12440
12441               case MT_CHECKMATE:
12442               case MT_STAINMATE:
12443                 if (WhiteOnMove(currentMove)) {
12444                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12445                 } else {
12446                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12447                 }
12448                 break;
12449
12450               case MT_STALEMATE:
12451                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12452                 break;
12453             }
12454
12455             break;
12456
12457           case CMAIL_RESIGN:
12458             if (WhiteOnMove(currentMove)) {
12459                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12460             } else {
12461                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12462             }
12463             break;
12464
12465           case CMAIL_ACCEPT:
12466             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12467             break;
12468
12469           default:
12470             break;
12471         }
12472     }
12473
12474     return;
12475 }
12476
12477 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12478 int
12479 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12480 {
12481     int retVal;
12482
12483     if (gameNumber > nCmailGames) {
12484         DisplayError(_("No more games in this message"), 0);
12485         return FALSE;
12486     }
12487     if (f == lastLoadGameFP) {
12488         int offset = gameNumber - lastLoadGameNumber;
12489         if (offset == 0) {
12490             cmailMsg[0] = NULLCHAR;
12491             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12492                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12493                 nCmailMovesRegistered--;
12494             }
12495             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12496             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12497                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12498             }
12499         } else {
12500             if (! RegisterMove()) return FALSE;
12501         }
12502     }
12503
12504     retVal = LoadGame(f, gameNumber, title, useList);
12505
12506     /* Make move registered during previous look at this game, if any */
12507     MakeRegisteredMove();
12508
12509     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12510         commentList[currentMove]
12511           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12512         DisplayComment(currentMove - 1, commentList[currentMove]);
12513     }
12514
12515     return retVal;
12516 }
12517
12518 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12519 int
12520 ReloadGame (int offset)
12521 {
12522     int gameNumber = lastLoadGameNumber + offset;
12523     if (lastLoadGameFP == NULL) {
12524         DisplayError(_("No game has been loaded yet"), 0);
12525         return FALSE;
12526     }
12527     if (gameNumber <= 0) {
12528         DisplayError(_("Can't back up any further"), 0);
12529         return FALSE;
12530     }
12531     if (cmailMsgLoaded) {
12532         return CmailLoadGame(lastLoadGameFP, gameNumber,
12533                              lastLoadGameTitle, lastLoadGameUseList);
12534     } else {
12535         return LoadGame(lastLoadGameFP, gameNumber,
12536                         lastLoadGameTitle, lastLoadGameUseList);
12537     }
12538 }
12539
12540 int keys[EmptySquare+1];
12541
12542 int
12543 PositionMatches (Board b1, Board b2)
12544 {
12545     int r, f, sum=0;
12546     switch(appData.searchMode) {
12547         case 1: return CompareWithRights(b1, b2);
12548         case 2:
12549             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12550                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12551             }
12552             return TRUE;
12553         case 3:
12554             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12555               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12556                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12557             }
12558             return sum==0;
12559         case 4:
12560             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12561                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12562             }
12563             return sum==0;
12564     }
12565     return TRUE;
12566 }
12567
12568 #define Q_PROMO  4
12569 #define Q_EP     3
12570 #define Q_BCASTL 2
12571 #define Q_WCASTL 1
12572
12573 int pieceList[256], quickBoard[256];
12574 ChessSquare pieceType[256] = { EmptySquare };
12575 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12576 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12577 int soughtTotal, turn;
12578 Boolean epOK, flipSearch;
12579
12580 typedef struct {
12581     unsigned char piece, to;
12582 } Move;
12583
12584 #define DSIZE (250000)
12585
12586 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12587 Move *moveDatabase = initialSpace;
12588 unsigned int movePtr, dataSize = DSIZE;
12589
12590 int
12591 MakePieceList (Board board, int *counts)
12592 {
12593     int r, f, n=Q_PROMO, total=0;
12594     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12595     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12596         int sq = f + (r<<4);
12597         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12598             quickBoard[sq] = ++n;
12599             pieceList[n] = sq;
12600             pieceType[n] = board[r][f];
12601             counts[board[r][f]]++;
12602             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12603             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12604             total++;
12605         }
12606     }
12607     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12608     return total;
12609 }
12610
12611 void
12612 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12613 {
12614     int sq = fromX + (fromY<<4);
12615     int piece = quickBoard[sq], rook;
12616     quickBoard[sq] = 0;
12617     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12618     if(piece == pieceList[1] && fromY == toY) {
12619       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12620         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12621         moveDatabase[movePtr++].piece = Q_WCASTL;
12622         quickBoard[sq] = piece;
12623         piece = quickBoard[from]; quickBoard[from] = 0;
12624         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12625       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12626         quickBoard[sq] = 0; // remove Rook
12627         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12628         moveDatabase[movePtr++].piece = Q_WCASTL;
12629         quickBoard[sq] = pieceList[1]; // put King
12630         piece = rook;
12631         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12632       }
12633     } else
12634     if(piece == pieceList[2] && fromY == toY) {
12635       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12636         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12637         moveDatabase[movePtr++].piece = Q_BCASTL;
12638         quickBoard[sq] = piece;
12639         piece = quickBoard[from]; quickBoard[from] = 0;
12640         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12641       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12642         quickBoard[sq] = 0; // remove Rook
12643         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12644         moveDatabase[movePtr++].piece = Q_BCASTL;
12645         quickBoard[sq] = pieceList[2]; // put King
12646         piece = rook;
12647         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12648       }
12649     } else
12650     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12651         quickBoard[(fromY<<4)+toX] = 0;
12652         moveDatabase[movePtr].piece = Q_EP;
12653         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12654         moveDatabase[movePtr].to = sq;
12655     } else
12656     if(promoPiece != pieceType[piece]) {
12657         moveDatabase[movePtr++].piece = Q_PROMO;
12658         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12659     }
12660     moveDatabase[movePtr].piece = piece;
12661     quickBoard[sq] = piece;
12662     movePtr++;
12663 }
12664
12665 int
12666 PackGame (Board board)
12667 {
12668     Move *newSpace = NULL;
12669     moveDatabase[movePtr].piece = 0; // terminate previous game
12670     if(movePtr > dataSize) {
12671         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12672         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12673         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12674         if(newSpace) {
12675             int i;
12676             Move *p = moveDatabase, *q = newSpace;
12677             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12678             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12679             moveDatabase = newSpace;
12680         } else { // calloc failed, we must be out of memory. Too bad...
12681             dataSize = 0; // prevent calloc events for all subsequent games
12682             return 0;     // and signal this one isn't cached
12683         }
12684     }
12685     movePtr++;
12686     MakePieceList(board, counts);
12687     return movePtr;
12688 }
12689
12690 int
12691 QuickCompare (Board board, int *minCounts, int *maxCounts)
12692 {   // compare according to search mode
12693     int r, f;
12694     switch(appData.searchMode)
12695     {
12696       case 1: // exact position match
12697         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12698         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12699             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12700         }
12701         break;
12702       case 2: // can have extra material on empty squares
12703         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12704             if(board[r][f] == EmptySquare) continue;
12705             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12706         }
12707         break;
12708       case 3: // material with exact Pawn structure
12709         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12710             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12711             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12712         } // fall through to material comparison
12713       case 4: // exact material
12714         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12715         break;
12716       case 6: // material range with given imbalance
12717         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12718         // fall through to range comparison
12719       case 5: // material range
12720         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12721     }
12722     return TRUE;
12723 }
12724
12725 int
12726 QuickScan (Board board, Move *move)
12727 {   // reconstruct game,and compare all positions in it
12728     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12729     do {
12730         int piece = move->piece;
12731         int to = move->to, from = pieceList[piece];
12732         if(found < 0) { // if already found just scan to game end for final piece count
12733           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12734            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12735            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12736                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12737             ) {
12738             static int lastCounts[EmptySquare+1];
12739             int i;
12740             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12741             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12742           } else stretch = 0;
12743           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12744           if(found >= 0 && !appData.minPieces) return found;
12745         }
12746         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12747           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12748           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12749             piece = (++move)->piece;
12750             from = pieceList[piece];
12751             counts[pieceType[piece]]--;
12752             pieceType[piece] = (ChessSquare) move->to;
12753             counts[move->to]++;
12754           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12755             counts[pieceType[quickBoard[to]]]--;
12756             quickBoard[to] = 0; total--;
12757             move++;
12758             continue;
12759           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12760             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12761             from  = pieceList[piece]; // so this must be King
12762             quickBoard[from] = 0;
12763             pieceList[piece] = to;
12764             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12765             quickBoard[from] = 0; // rook
12766             quickBoard[to] = piece;
12767             to = move->to; piece = move->piece;
12768             goto aftercastle;
12769           }
12770         }
12771         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12772         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12773         quickBoard[from] = 0;
12774       aftercastle:
12775         quickBoard[to] = piece;
12776         pieceList[piece] = to;
12777         cnt++; turn ^= 3;
12778         move++;
12779     } while(1);
12780 }
12781
12782 void
12783 InitSearch ()
12784 {
12785     int r, f;
12786     flipSearch = FALSE;
12787     CopyBoard(soughtBoard, boards[currentMove]);
12788     soughtTotal = MakePieceList(soughtBoard, maxSought);
12789     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12790     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12791     CopyBoard(reverseBoard, boards[currentMove]);
12792     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12793         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12794         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12795         reverseBoard[r][f] = piece;
12796     }
12797     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12798     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12799     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12800                  || (boards[currentMove][CASTLING][2] == NoRights ||
12801                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12802                  && (boards[currentMove][CASTLING][5] == NoRights ||
12803                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12804       ) {
12805         flipSearch = TRUE;
12806         CopyBoard(flipBoard, soughtBoard);
12807         CopyBoard(rotateBoard, reverseBoard);
12808         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12809             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12810             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12811         }
12812     }
12813     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12814     if(appData.searchMode >= 5) {
12815         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12816         MakePieceList(soughtBoard, minSought);
12817         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12818     }
12819     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12820         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12821 }
12822
12823 GameInfo dummyInfo;
12824 static int creatingBook;
12825
12826 int
12827 GameContainsPosition (FILE *f, ListGame *lg)
12828 {
12829     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12830     int fromX, fromY, toX, toY;
12831     char promoChar;
12832     static int initDone=FALSE;
12833
12834     // weed out games based on numerical tag comparison
12835     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12836     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12837     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12838     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12839     if(!initDone) {
12840         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12841         initDone = TRUE;
12842     }
12843     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12844     else CopyBoard(boards[scratch], initialPosition); // default start position
12845     if(lg->moves) {
12846         turn = btm + 1;
12847         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12848         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12849     }
12850     if(btm) plyNr++;
12851     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12852     fseek(f, lg->offset, 0);
12853     yynewfile(f);
12854     while(1) {
12855         yyboardindex = scratch;
12856         quickFlag = plyNr+1;
12857         next = Myylex();
12858         quickFlag = 0;
12859         switch(next) {
12860             case PGNTag:
12861                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12862             default:
12863                 continue;
12864
12865             case XBoardGame:
12866             case GNUChessGame:
12867                 if(plyNr) return -1; // after we have seen moves, this is for new game
12868               continue;
12869
12870             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12871             case ImpossibleMove:
12872             case WhiteWins: // game ends here with these four
12873             case BlackWins:
12874             case GameIsDrawn:
12875             case GameUnfinished:
12876                 return -1;
12877
12878             case IllegalMove:
12879                 if(appData.testLegality) return -1;
12880             case WhiteCapturesEnPassant:
12881             case BlackCapturesEnPassant:
12882             case WhitePromotion:
12883             case BlackPromotion:
12884             case WhiteNonPromotion:
12885             case BlackNonPromotion:
12886             case NormalMove:
12887             case FirstLeg:
12888             case WhiteKingSideCastle:
12889             case WhiteQueenSideCastle:
12890             case BlackKingSideCastle:
12891             case BlackQueenSideCastle:
12892             case WhiteKingSideCastleWild:
12893             case WhiteQueenSideCastleWild:
12894             case BlackKingSideCastleWild:
12895             case BlackQueenSideCastleWild:
12896             case WhiteHSideCastleFR:
12897             case WhiteASideCastleFR:
12898             case BlackHSideCastleFR:
12899             case BlackASideCastleFR:
12900                 fromX = currentMoveString[0] - AAA;
12901                 fromY = currentMoveString[1] - ONE;
12902                 toX = currentMoveString[2] - AAA;
12903                 toY = currentMoveString[3] - ONE;
12904                 promoChar = currentMoveString[4];
12905                 break;
12906             case WhiteDrop:
12907             case BlackDrop:
12908                 fromX = next == WhiteDrop ?
12909                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12910                   (int) CharToPiece(ToLower(currentMoveString[0]));
12911                 fromY = DROP_RANK;
12912                 toX = currentMoveString[2] - AAA;
12913                 toY = currentMoveString[3] - ONE;
12914                 promoChar = 0;
12915                 break;
12916         }
12917         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12918         plyNr++;
12919         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12920         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12921         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12922         if(appData.findMirror) {
12923             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12924             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12925         }
12926     }
12927 }
12928
12929 /* Load the nth game from open file f */
12930 int
12931 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12932 {
12933     ChessMove cm;
12934     char buf[MSG_SIZ];
12935     int gn = gameNumber;
12936     ListGame *lg = NULL;
12937     int numPGNTags = 0, i;
12938     int err, pos = -1;
12939     GameMode oldGameMode;
12940     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12941     char oldName[MSG_SIZ];
12942
12943     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12944
12945     if (appData.debugMode)
12946         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12947
12948     if (gameMode == Training )
12949         SetTrainingModeOff();
12950
12951     oldGameMode = gameMode;
12952     if (gameMode != BeginningOfGame) {
12953       Reset(FALSE, TRUE);
12954     }
12955     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
12956
12957     gameFileFP = f;
12958     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12959         fclose(lastLoadGameFP);
12960     }
12961
12962     if (useList) {
12963         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12964
12965         if (lg) {
12966             fseek(f, lg->offset, 0);
12967             GameListHighlight(gameNumber);
12968             pos = lg->position;
12969             gn = 1;
12970         }
12971         else {
12972             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12973               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12974             else
12975             DisplayError(_("Game number out of range"), 0);
12976             return FALSE;
12977         }
12978     } else {
12979         GameListDestroy();
12980         if (fseek(f, 0, 0) == -1) {
12981             if (f == lastLoadGameFP ?
12982                 gameNumber == lastLoadGameNumber + 1 :
12983                 gameNumber == 1) {
12984                 gn = 1;
12985             } else {
12986                 DisplayError(_("Can't seek on game file"), 0);
12987                 return FALSE;
12988             }
12989         }
12990     }
12991     lastLoadGameFP = f;
12992     lastLoadGameNumber = gameNumber;
12993     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12994     lastLoadGameUseList = useList;
12995
12996     yynewfile(f);
12997
12998     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12999       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13000                 lg->gameInfo.black);
13001             DisplayTitle(buf);
13002     } else if (*title != NULLCHAR) {
13003         if (gameNumber > 1) {
13004           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13005             DisplayTitle(buf);
13006         } else {
13007             DisplayTitle(title);
13008         }
13009     }
13010
13011     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13012         gameMode = PlayFromGameFile;
13013         ModeHighlight();
13014     }
13015
13016     currentMove = forwardMostMove = backwardMostMove = 0;
13017     CopyBoard(boards[0], initialPosition);
13018     StopClocks();
13019
13020     /*
13021      * Skip the first gn-1 games in the file.
13022      * Also skip over anything that precedes an identifiable
13023      * start of game marker, to avoid being confused by
13024      * garbage at the start of the file.  Currently
13025      * recognized start of game markers are the move number "1",
13026      * the pattern "gnuchess .* game", the pattern
13027      * "^[#;%] [^ ]* game file", and a PGN tag block.
13028      * A game that starts with one of the latter two patterns
13029      * will also have a move number 1, possibly
13030      * following a position diagram.
13031      * 5-4-02: Let's try being more lenient and allowing a game to
13032      * start with an unnumbered move.  Does that break anything?
13033      */
13034     cm = lastLoadGameStart = EndOfFile;
13035     while (gn > 0) {
13036         yyboardindex = forwardMostMove;
13037         cm = (ChessMove) Myylex();
13038         switch (cm) {
13039           case EndOfFile:
13040             if (cmailMsgLoaded) {
13041                 nCmailGames = CMAIL_MAX_GAMES - gn;
13042             } else {
13043                 Reset(TRUE, TRUE);
13044                 DisplayError(_("Game not found in file"), 0);
13045             }
13046             return FALSE;
13047
13048           case GNUChessGame:
13049           case XBoardGame:
13050             gn--;
13051             lastLoadGameStart = cm;
13052             break;
13053
13054           case MoveNumberOne:
13055             switch (lastLoadGameStart) {
13056               case GNUChessGame:
13057               case XBoardGame:
13058               case PGNTag:
13059                 break;
13060               case MoveNumberOne:
13061               case EndOfFile:
13062                 gn--;           /* count this game */
13063                 lastLoadGameStart = cm;
13064                 break;
13065               default:
13066                 /* impossible */
13067                 break;
13068             }
13069             break;
13070
13071           case PGNTag:
13072             switch (lastLoadGameStart) {
13073               case GNUChessGame:
13074               case PGNTag:
13075               case MoveNumberOne:
13076               case EndOfFile:
13077                 gn--;           /* count this game */
13078                 lastLoadGameStart = cm;
13079                 break;
13080               case XBoardGame:
13081                 lastLoadGameStart = cm; /* game counted already */
13082                 break;
13083               default:
13084                 /* impossible */
13085                 break;
13086             }
13087             if (gn > 0) {
13088                 do {
13089                     yyboardindex = forwardMostMove;
13090                     cm = (ChessMove) Myylex();
13091                 } while (cm == PGNTag || cm == Comment);
13092             }
13093             break;
13094
13095           case WhiteWins:
13096           case BlackWins:
13097           case GameIsDrawn:
13098             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13099                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13100                     != CMAIL_OLD_RESULT) {
13101                     nCmailResults ++ ;
13102                     cmailResult[  CMAIL_MAX_GAMES
13103                                 - gn - 1] = CMAIL_OLD_RESULT;
13104                 }
13105             }
13106             break;
13107
13108           case NormalMove:
13109           case FirstLeg:
13110             /* Only a NormalMove can be at the start of a game
13111              * without a position diagram. */
13112             if (lastLoadGameStart == EndOfFile ) {
13113               gn--;
13114               lastLoadGameStart = MoveNumberOne;
13115             }
13116             break;
13117
13118           default:
13119             break;
13120         }
13121     }
13122
13123     if (appData.debugMode)
13124       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13125
13126     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13127
13128     if (cm == XBoardGame) {
13129         /* Skip any header junk before position diagram and/or move 1 */
13130         for (;;) {
13131             yyboardindex = forwardMostMove;
13132             cm = (ChessMove) Myylex();
13133
13134             if (cm == EndOfFile ||
13135                 cm == GNUChessGame || cm == XBoardGame) {
13136                 /* Empty game; pretend end-of-file and handle later */
13137                 cm = EndOfFile;
13138                 break;
13139             }
13140
13141             if (cm == MoveNumberOne || cm == PositionDiagram ||
13142                 cm == PGNTag || cm == Comment)
13143               break;
13144         }
13145     } else if (cm == GNUChessGame) {
13146         if (gameInfo.event != NULL) {
13147             free(gameInfo.event);
13148         }
13149         gameInfo.event = StrSave(yy_text);
13150     }
13151
13152     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13153     while (cm == PGNTag) {
13154         if (appData.debugMode)
13155           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13156         err = ParsePGNTag(yy_text, &gameInfo);
13157         if (!err) numPGNTags++;
13158
13159         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13160         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13161             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13162             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13163             InitPosition(TRUE);
13164             oldVariant = gameInfo.variant;
13165             if (appData.debugMode)
13166               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13167         }
13168
13169
13170         if (gameInfo.fen != NULL) {
13171           Board initial_position;
13172           startedFromSetupPosition = TRUE;
13173           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13174             Reset(TRUE, TRUE);
13175             DisplayError(_("Bad FEN position in file"), 0);
13176             return FALSE;
13177           }
13178           CopyBoard(boards[0], initial_position);
13179           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13180             CopyBoard(initialPosition, initial_position);
13181           if (blackPlaysFirst) {
13182             currentMove = forwardMostMove = backwardMostMove = 1;
13183             CopyBoard(boards[1], initial_position);
13184             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13185             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13186             timeRemaining[0][1] = whiteTimeRemaining;
13187             timeRemaining[1][1] = blackTimeRemaining;
13188             if (commentList[0] != NULL) {
13189               commentList[1] = commentList[0];
13190               commentList[0] = NULL;
13191             }
13192           } else {
13193             currentMove = forwardMostMove = backwardMostMove = 0;
13194           }
13195           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13196           {   int i;
13197               initialRulePlies = FENrulePlies;
13198               for( i=0; i< nrCastlingRights; i++ )
13199                   initialRights[i] = initial_position[CASTLING][i];
13200           }
13201           yyboardindex = forwardMostMove;
13202           free(gameInfo.fen);
13203           gameInfo.fen = NULL;
13204         }
13205
13206         yyboardindex = forwardMostMove;
13207         cm = (ChessMove) Myylex();
13208
13209         /* Handle comments interspersed among the tags */
13210         while (cm == Comment) {
13211             char *p;
13212             if (appData.debugMode)
13213               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13214             p = yy_text;
13215             AppendComment(currentMove, p, FALSE);
13216             yyboardindex = forwardMostMove;
13217             cm = (ChessMove) Myylex();
13218         }
13219     }
13220
13221     /* don't rely on existence of Event tag since if game was
13222      * pasted from clipboard the Event tag may not exist
13223      */
13224     if (numPGNTags > 0){
13225         char *tags;
13226         if (gameInfo.variant == VariantNormal) {
13227           VariantClass v = StringToVariant(gameInfo.event);
13228           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13229           if(v < VariantShogi) gameInfo.variant = v;
13230         }
13231         if (!matchMode) {
13232           if( appData.autoDisplayTags ) {
13233             tags = PGNTags(&gameInfo);
13234             TagsPopUp(tags, CmailMsg());
13235             free(tags);
13236           }
13237         }
13238     } else {
13239         /* Make something up, but don't display it now */
13240         SetGameInfo();
13241         TagsPopDown();
13242     }
13243
13244     if (cm == PositionDiagram) {
13245         int i, j;
13246         char *p;
13247         Board initial_position;
13248
13249         if (appData.debugMode)
13250           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13251
13252         if (!startedFromSetupPosition) {
13253             p = yy_text;
13254             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13255               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13256                 switch (*p) {
13257                   case '{':
13258                   case '[':
13259                   case '-':
13260                   case ' ':
13261                   case '\t':
13262                   case '\n':
13263                   case '\r':
13264                     break;
13265                   default:
13266                     initial_position[i][j++] = CharToPiece(*p);
13267                     break;
13268                 }
13269             while (*p == ' ' || *p == '\t' ||
13270                    *p == '\n' || *p == '\r') p++;
13271
13272             if (strncmp(p, "black", strlen("black"))==0)
13273               blackPlaysFirst = TRUE;
13274             else
13275               blackPlaysFirst = FALSE;
13276             startedFromSetupPosition = TRUE;
13277
13278             CopyBoard(boards[0], initial_position);
13279             if (blackPlaysFirst) {
13280                 currentMove = forwardMostMove = backwardMostMove = 1;
13281                 CopyBoard(boards[1], initial_position);
13282                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13283                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13284                 timeRemaining[0][1] = whiteTimeRemaining;
13285                 timeRemaining[1][1] = blackTimeRemaining;
13286                 if (commentList[0] != NULL) {
13287                     commentList[1] = commentList[0];
13288                     commentList[0] = NULL;
13289                 }
13290             } else {
13291                 currentMove = forwardMostMove = backwardMostMove = 0;
13292             }
13293         }
13294         yyboardindex = forwardMostMove;
13295         cm = (ChessMove) Myylex();
13296     }
13297
13298   if(!creatingBook) {
13299     if (first.pr == NoProc) {
13300         StartChessProgram(&first);
13301     }
13302     InitChessProgram(&first, FALSE);
13303     if(gameInfo.variant == VariantUnknown && *oldName) {
13304         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13305         gameInfo.variant = v;
13306     }
13307     SendToProgram("force\n", &first);
13308     if (startedFromSetupPosition) {
13309         SendBoard(&first, forwardMostMove);
13310     if (appData.debugMode) {
13311         fprintf(debugFP, "Load Game\n");
13312     }
13313         DisplayBothClocks();
13314     }
13315   }
13316
13317     /* [HGM] server: flag to write setup moves in broadcast file as one */
13318     loadFlag = appData.suppressLoadMoves;
13319
13320     while (cm == Comment) {
13321         char *p;
13322         if (appData.debugMode)
13323           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13324         p = yy_text;
13325         AppendComment(currentMove, p, FALSE);
13326         yyboardindex = forwardMostMove;
13327         cm = (ChessMove) Myylex();
13328     }
13329
13330     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13331         cm == WhiteWins || cm == BlackWins ||
13332         cm == GameIsDrawn || cm == GameUnfinished) {
13333         DisplayMessage("", _("No moves in game"));
13334         if (cmailMsgLoaded) {
13335             if (appData.debugMode)
13336               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13337             ClearHighlights();
13338             flipView = FALSE;
13339         }
13340         DrawPosition(FALSE, boards[currentMove]);
13341         DisplayBothClocks();
13342         gameMode = EditGame;
13343         ModeHighlight();
13344         gameFileFP = NULL;
13345         cmailOldMove = 0;
13346         return TRUE;
13347     }
13348
13349     // [HGM] PV info: routine tests if comment empty
13350     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13351         DisplayComment(currentMove - 1, commentList[currentMove]);
13352     }
13353     if (!matchMode && appData.timeDelay != 0)
13354       DrawPosition(FALSE, boards[currentMove]);
13355
13356     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13357       programStats.ok_to_send = 1;
13358     }
13359
13360     /* if the first token after the PGN tags is a move
13361      * and not move number 1, retrieve it from the parser
13362      */
13363     if (cm != MoveNumberOne)
13364         LoadGameOneMove(cm);
13365
13366     /* load the remaining moves from the file */
13367     while (LoadGameOneMove(EndOfFile)) {
13368       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13369       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13370     }
13371
13372     /* rewind to the start of the game */
13373     currentMove = backwardMostMove;
13374
13375     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13376
13377     if (oldGameMode == AnalyzeFile) {
13378       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13379       AnalyzeFileEvent();
13380     } else
13381     if (oldGameMode == AnalyzeMode) {
13382       AnalyzeFileEvent();
13383     }
13384
13385     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13386         long int w, b; // [HGM] adjourn: restore saved clock times
13387         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13388         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13389             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13390             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13391         }
13392     }
13393
13394     if(creatingBook) return TRUE;
13395     if (!matchMode && pos > 0) {
13396         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13397     } else
13398     if (matchMode || appData.timeDelay == 0) {
13399       ToEndEvent();
13400     } else if (appData.timeDelay > 0) {
13401       AutoPlayGameLoop();
13402     }
13403
13404     if (appData.debugMode)
13405         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13406
13407     loadFlag = 0; /* [HGM] true game starts */
13408     return TRUE;
13409 }
13410
13411 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13412 int
13413 ReloadPosition (int offset)
13414 {
13415     int positionNumber = lastLoadPositionNumber + offset;
13416     if (lastLoadPositionFP == NULL) {
13417         DisplayError(_("No position has been loaded yet"), 0);
13418         return FALSE;
13419     }
13420     if (positionNumber <= 0) {
13421         DisplayError(_("Can't back up any further"), 0);
13422         return FALSE;
13423     }
13424     return LoadPosition(lastLoadPositionFP, positionNumber,
13425                         lastLoadPositionTitle);
13426 }
13427
13428 /* Load the nth position from the given file */
13429 int
13430 LoadPositionFromFile (char *filename, int n, char *title)
13431 {
13432     FILE *f;
13433     char buf[MSG_SIZ];
13434
13435     if (strcmp(filename, "-") == 0) {
13436         return LoadPosition(stdin, n, "stdin");
13437     } else {
13438         f = fopen(filename, "rb");
13439         if (f == NULL) {
13440             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13441             DisplayError(buf, errno);
13442             return FALSE;
13443         } else {
13444             return LoadPosition(f, n, title);
13445         }
13446     }
13447 }
13448
13449 /* Load the nth position from the given open file, and close it */
13450 int
13451 LoadPosition (FILE *f, int positionNumber, char *title)
13452 {
13453     char *p, line[MSG_SIZ];
13454     Board initial_position;
13455     int i, j, fenMode, pn;
13456
13457     if (gameMode == Training )
13458         SetTrainingModeOff();
13459
13460     if (gameMode != BeginningOfGame) {
13461         Reset(FALSE, TRUE);
13462     }
13463     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13464         fclose(lastLoadPositionFP);
13465     }
13466     if (positionNumber == 0) positionNumber = 1;
13467     lastLoadPositionFP = f;
13468     lastLoadPositionNumber = positionNumber;
13469     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13470     if (first.pr == NoProc && !appData.noChessProgram) {
13471       StartChessProgram(&first);
13472       InitChessProgram(&first, FALSE);
13473     }
13474     pn = positionNumber;
13475     if (positionNumber < 0) {
13476         /* Negative position number means to seek to that byte offset */
13477         if (fseek(f, -positionNumber, 0) == -1) {
13478             DisplayError(_("Can't seek on position file"), 0);
13479             return FALSE;
13480         };
13481         pn = 1;
13482     } else {
13483         if (fseek(f, 0, 0) == -1) {
13484             if (f == lastLoadPositionFP ?
13485                 positionNumber == lastLoadPositionNumber + 1 :
13486                 positionNumber == 1) {
13487                 pn = 1;
13488             } else {
13489                 DisplayError(_("Can't seek on position file"), 0);
13490                 return FALSE;
13491             }
13492         }
13493     }
13494     /* See if this file is FEN or old-style xboard */
13495     if (fgets(line, MSG_SIZ, f) == NULL) {
13496         DisplayError(_("Position not found in file"), 0);
13497         return FALSE;
13498     }
13499     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13500     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13501
13502     if (pn >= 2) {
13503         if (fenMode || line[0] == '#') pn--;
13504         while (pn > 0) {
13505             /* skip positions before number pn */
13506             if (fgets(line, MSG_SIZ, f) == NULL) {
13507                 Reset(TRUE, TRUE);
13508                 DisplayError(_("Position not found in file"), 0);
13509                 return FALSE;
13510             }
13511             if (fenMode || line[0] == '#') pn--;
13512         }
13513     }
13514
13515     if (fenMode) {
13516         char *p;
13517         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13518             DisplayError(_("Bad FEN position in file"), 0);
13519             return FALSE;
13520         }
13521         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13522             sscanf(p+3, "%s", bestMove);
13523         } else *bestMove = NULLCHAR;
13524     } else {
13525         (void) fgets(line, MSG_SIZ, f);
13526         (void) fgets(line, MSG_SIZ, f);
13527
13528         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13529             (void) fgets(line, MSG_SIZ, f);
13530             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13531                 if (*p == ' ')
13532                   continue;
13533                 initial_position[i][j++] = CharToPiece(*p);
13534             }
13535         }
13536
13537         blackPlaysFirst = FALSE;
13538         if (!feof(f)) {
13539             (void) fgets(line, MSG_SIZ, f);
13540             if (strncmp(line, "black", strlen("black"))==0)
13541               blackPlaysFirst = TRUE;
13542         }
13543     }
13544     startedFromSetupPosition = TRUE;
13545
13546     CopyBoard(boards[0], initial_position);
13547     if (blackPlaysFirst) {
13548         currentMove = forwardMostMove = backwardMostMove = 1;
13549         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13550         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13551         CopyBoard(boards[1], initial_position);
13552         DisplayMessage("", _("Black to play"));
13553     } else {
13554         currentMove = forwardMostMove = backwardMostMove = 0;
13555         DisplayMessage("", _("White to play"));
13556     }
13557     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13558     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13559         SendToProgram("force\n", &first);
13560         SendBoard(&first, forwardMostMove);
13561     }
13562     if (appData.debugMode) {
13563 int i, j;
13564   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13565   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13566         fprintf(debugFP, "Load Position\n");
13567     }
13568
13569     if (positionNumber > 1) {
13570       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13571         DisplayTitle(line);
13572     } else {
13573         DisplayTitle(title);
13574     }
13575     gameMode = EditGame;
13576     ModeHighlight();
13577     ResetClocks();
13578     timeRemaining[0][1] = whiteTimeRemaining;
13579     timeRemaining[1][1] = blackTimeRemaining;
13580     DrawPosition(FALSE, boards[currentMove]);
13581
13582     return TRUE;
13583 }
13584
13585
13586 void
13587 CopyPlayerNameIntoFileName (char **dest, char *src)
13588 {
13589     while (*src != NULLCHAR && *src != ',') {
13590         if (*src == ' ') {
13591             *(*dest)++ = '_';
13592             src++;
13593         } else {
13594             *(*dest)++ = *src++;
13595         }
13596     }
13597 }
13598
13599 char *
13600 DefaultFileName (char *ext)
13601 {
13602     static char def[MSG_SIZ];
13603     char *p;
13604
13605     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13606         p = def;
13607         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13608         *p++ = '-';
13609         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13610         *p++ = '.';
13611         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13612     } else {
13613         def[0] = NULLCHAR;
13614     }
13615     return def;
13616 }
13617
13618 /* Save the current game to the given file */
13619 int
13620 SaveGameToFile (char *filename, int append)
13621 {
13622     FILE *f;
13623     char buf[MSG_SIZ];
13624     int result, i, t,tot=0;
13625
13626     if (strcmp(filename, "-") == 0) {
13627         return SaveGame(stdout, 0, NULL);
13628     } else {
13629         for(i=0; i<10; i++) { // upto 10 tries
13630              f = fopen(filename, append ? "a" : "w");
13631              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13632              if(f || errno != 13) break;
13633              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13634              tot += t;
13635         }
13636         if (f == NULL) {
13637             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13638             DisplayError(buf, errno);
13639             return FALSE;
13640         } else {
13641             safeStrCpy(buf, lastMsg, MSG_SIZ);
13642             DisplayMessage(_("Waiting for access to save file"), "");
13643             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13644             DisplayMessage(_("Saving game"), "");
13645             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13646             result = SaveGame(f, 0, NULL);
13647             DisplayMessage(buf, "");
13648             return result;
13649         }
13650     }
13651 }
13652
13653 char *
13654 SavePart (char *str)
13655 {
13656     static char buf[MSG_SIZ];
13657     char *p;
13658
13659     p = strchr(str, ' ');
13660     if (p == NULL) return str;
13661     strncpy(buf, str, p - str);
13662     buf[p - str] = NULLCHAR;
13663     return buf;
13664 }
13665
13666 #define PGN_MAX_LINE 75
13667
13668 #define PGN_SIDE_WHITE  0
13669 #define PGN_SIDE_BLACK  1
13670
13671 static int
13672 FindFirstMoveOutOfBook (int side)
13673 {
13674     int result = -1;
13675
13676     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13677         int index = backwardMostMove;
13678         int has_book_hit = 0;
13679
13680         if( (index % 2) != side ) {
13681             index++;
13682         }
13683
13684         while( index < forwardMostMove ) {
13685             /* Check to see if engine is in book */
13686             int depth = pvInfoList[index].depth;
13687             int score = pvInfoList[index].score;
13688             int in_book = 0;
13689
13690             if( depth <= 2 ) {
13691                 in_book = 1;
13692             }
13693             else if( score == 0 && depth == 63 ) {
13694                 in_book = 1; /* Zappa */
13695             }
13696             else if( score == 2 && depth == 99 ) {
13697                 in_book = 1; /* Abrok */
13698             }
13699
13700             has_book_hit += in_book;
13701
13702             if( ! in_book ) {
13703                 result = index;
13704
13705                 break;
13706             }
13707
13708             index += 2;
13709         }
13710     }
13711
13712     return result;
13713 }
13714
13715 void
13716 GetOutOfBookInfo (char * buf)
13717 {
13718     int oob[2];
13719     int i;
13720     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13721
13722     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13723     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13724
13725     *buf = '\0';
13726
13727     if( oob[0] >= 0 || oob[1] >= 0 ) {
13728         for( i=0; i<2; i++ ) {
13729             int idx = oob[i];
13730
13731             if( idx >= 0 ) {
13732                 if( i > 0 && oob[0] >= 0 ) {
13733                     strcat( buf, "   " );
13734                 }
13735
13736                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13737                 sprintf( buf+strlen(buf), "%s%.2f",
13738                     pvInfoList[idx].score >= 0 ? "+" : "",
13739                     pvInfoList[idx].score / 100.0 );
13740             }
13741         }
13742     }
13743 }
13744
13745 /* Save game in PGN style */
13746 static void
13747 SaveGamePGN2 (FILE *f)
13748 {
13749     int i, offset, linelen, newblock;
13750 //    char *movetext;
13751     char numtext[32];
13752     int movelen, numlen, blank;
13753     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13754
13755     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13756
13757     PrintPGNTags(f, &gameInfo);
13758
13759     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13760
13761     if (backwardMostMove > 0 || startedFromSetupPosition) {
13762         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13763         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13764         fprintf(f, "\n{--------------\n");
13765         PrintPosition(f, backwardMostMove);
13766         fprintf(f, "--------------}\n");
13767         free(fen);
13768     }
13769     else {
13770         /* [AS] Out of book annotation */
13771         if( appData.saveOutOfBookInfo ) {
13772             char buf[64];
13773
13774             GetOutOfBookInfo( buf );
13775
13776             if( buf[0] != '\0' ) {
13777                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13778             }
13779         }
13780
13781         fprintf(f, "\n");
13782     }
13783
13784     i = backwardMostMove;
13785     linelen = 0;
13786     newblock = TRUE;
13787
13788     while (i < forwardMostMove) {
13789         /* Print comments preceding this move */
13790         if (commentList[i] != NULL) {
13791             if (linelen > 0) fprintf(f, "\n");
13792             fprintf(f, "%s", commentList[i]);
13793             linelen = 0;
13794             newblock = TRUE;
13795         }
13796
13797         /* Format move number */
13798         if ((i % 2) == 0)
13799           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13800         else
13801           if (newblock)
13802             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13803           else
13804             numtext[0] = NULLCHAR;
13805
13806         numlen = strlen(numtext);
13807         newblock = FALSE;
13808
13809         /* Print move number */
13810         blank = linelen > 0 && numlen > 0;
13811         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13812             fprintf(f, "\n");
13813             linelen = 0;
13814             blank = 0;
13815         }
13816         if (blank) {
13817             fprintf(f, " ");
13818             linelen++;
13819         }
13820         fprintf(f, "%s", numtext);
13821         linelen += numlen;
13822
13823         /* Get move */
13824         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13825         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13826
13827         /* Print move */
13828         blank = linelen > 0 && movelen > 0;
13829         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13830             fprintf(f, "\n");
13831             linelen = 0;
13832             blank = 0;
13833         }
13834         if (blank) {
13835             fprintf(f, " ");
13836             linelen++;
13837         }
13838         fprintf(f, "%s", move_buffer);
13839         linelen += movelen;
13840
13841         /* [AS] Add PV info if present */
13842         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13843             /* [HGM] add time */
13844             char buf[MSG_SIZ]; int seconds;
13845
13846             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13847
13848             if( seconds <= 0)
13849               buf[0] = 0;
13850             else
13851               if( seconds < 30 )
13852                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13853               else
13854                 {
13855                   seconds = (seconds + 4)/10; // round to full seconds
13856                   if( seconds < 60 )
13857                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13858                   else
13859                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13860                 }
13861
13862             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13863                       pvInfoList[i].score >= 0 ? "+" : "",
13864                       pvInfoList[i].score / 100.0,
13865                       pvInfoList[i].depth,
13866                       buf );
13867
13868             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13869
13870             /* Print score/depth */
13871             blank = linelen > 0 && movelen > 0;
13872             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13873                 fprintf(f, "\n");
13874                 linelen = 0;
13875                 blank = 0;
13876             }
13877             if (blank) {
13878                 fprintf(f, " ");
13879                 linelen++;
13880             }
13881             fprintf(f, "%s", move_buffer);
13882             linelen += movelen;
13883         }
13884
13885         i++;
13886     }
13887
13888     /* Start a new line */
13889     if (linelen > 0) fprintf(f, "\n");
13890
13891     /* Print comments after last move */
13892     if (commentList[i] != NULL) {
13893         fprintf(f, "%s\n", commentList[i]);
13894     }
13895
13896     /* Print result */
13897     if (gameInfo.resultDetails != NULL &&
13898         gameInfo.resultDetails[0] != NULLCHAR) {
13899         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13900         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13901            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13902             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13903         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13904     } else {
13905         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13906     }
13907 }
13908
13909 /* Save game in PGN style and close the file */
13910 int
13911 SaveGamePGN (FILE *f)
13912 {
13913     SaveGamePGN2(f);
13914     fclose(f);
13915     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13916     return TRUE;
13917 }
13918
13919 /* Save game in old style and close the file */
13920 int
13921 SaveGameOldStyle (FILE *f)
13922 {
13923     int i, offset;
13924     time_t tm;
13925
13926     tm = time((time_t *) NULL);
13927
13928     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13929     PrintOpponents(f);
13930
13931     if (backwardMostMove > 0 || startedFromSetupPosition) {
13932         fprintf(f, "\n[--------------\n");
13933         PrintPosition(f, backwardMostMove);
13934         fprintf(f, "--------------]\n");
13935     } else {
13936         fprintf(f, "\n");
13937     }
13938
13939     i = backwardMostMove;
13940     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13941
13942     while (i < forwardMostMove) {
13943         if (commentList[i] != NULL) {
13944             fprintf(f, "[%s]\n", commentList[i]);
13945         }
13946
13947         if ((i % 2) == 1) {
13948             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13949             i++;
13950         } else {
13951             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13952             i++;
13953             if (commentList[i] != NULL) {
13954                 fprintf(f, "\n");
13955                 continue;
13956             }
13957             if (i >= forwardMostMove) {
13958                 fprintf(f, "\n");
13959                 break;
13960             }
13961             fprintf(f, "%s\n", parseList[i]);
13962             i++;
13963         }
13964     }
13965
13966     if (commentList[i] != NULL) {
13967         fprintf(f, "[%s]\n", commentList[i]);
13968     }
13969
13970     /* This isn't really the old style, but it's close enough */
13971     if (gameInfo.resultDetails != NULL &&
13972         gameInfo.resultDetails[0] != NULLCHAR) {
13973         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13974                 gameInfo.resultDetails);
13975     } else {
13976         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13977     }
13978
13979     fclose(f);
13980     return TRUE;
13981 }
13982
13983 /* Save the current game to open file f and close the file */
13984 int
13985 SaveGame (FILE *f, int dummy, char *dummy2)
13986 {
13987     if (gameMode == EditPosition) EditPositionDone(TRUE);
13988     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13989     if (appData.oldSaveStyle)
13990       return SaveGameOldStyle(f);
13991     else
13992       return SaveGamePGN(f);
13993 }
13994
13995 /* Save the current position to the given file */
13996 int
13997 SavePositionToFile (char *filename)
13998 {
13999     FILE *f;
14000     char buf[MSG_SIZ];
14001
14002     if (strcmp(filename, "-") == 0) {
14003         return SavePosition(stdout, 0, NULL);
14004     } else {
14005         f = fopen(filename, "a");
14006         if (f == NULL) {
14007             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14008             DisplayError(buf, errno);
14009             return FALSE;
14010         } else {
14011             safeStrCpy(buf, lastMsg, MSG_SIZ);
14012             DisplayMessage(_("Waiting for access to save file"), "");
14013             flock(fileno(f), LOCK_EX); // [HGM] lock
14014             DisplayMessage(_("Saving position"), "");
14015             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14016             SavePosition(f, 0, NULL);
14017             DisplayMessage(buf, "");
14018             return TRUE;
14019         }
14020     }
14021 }
14022
14023 /* Save the current position to the given open file and close the file */
14024 int
14025 SavePosition (FILE *f, int dummy, char *dummy2)
14026 {
14027     time_t tm;
14028     char *fen;
14029
14030     if (gameMode == EditPosition) EditPositionDone(TRUE);
14031     if (appData.oldSaveStyle) {
14032         tm = time((time_t *) NULL);
14033
14034         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14035         PrintOpponents(f);
14036         fprintf(f, "[--------------\n");
14037         PrintPosition(f, currentMove);
14038         fprintf(f, "--------------]\n");
14039     } else {
14040         fen = PositionToFEN(currentMove, NULL, 1);
14041         fprintf(f, "%s\n", fen);
14042         free(fen);
14043     }
14044     fclose(f);
14045     return TRUE;
14046 }
14047
14048 void
14049 ReloadCmailMsgEvent (int unregister)
14050 {
14051 #if !WIN32
14052     static char *inFilename = NULL;
14053     static char *outFilename;
14054     int i;
14055     struct stat inbuf, outbuf;
14056     int status;
14057
14058     /* Any registered moves are unregistered if unregister is set, */
14059     /* i.e. invoked by the signal handler */
14060     if (unregister) {
14061         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14062             cmailMoveRegistered[i] = FALSE;
14063             if (cmailCommentList[i] != NULL) {
14064                 free(cmailCommentList[i]);
14065                 cmailCommentList[i] = NULL;
14066             }
14067         }
14068         nCmailMovesRegistered = 0;
14069     }
14070
14071     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14072         cmailResult[i] = CMAIL_NOT_RESULT;
14073     }
14074     nCmailResults = 0;
14075
14076     if (inFilename == NULL) {
14077         /* Because the filenames are static they only get malloced once  */
14078         /* and they never get freed                                      */
14079         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14080         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14081
14082         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14083         sprintf(outFilename, "%s.out", appData.cmailGameName);
14084     }
14085
14086     status = stat(outFilename, &outbuf);
14087     if (status < 0) {
14088         cmailMailedMove = FALSE;
14089     } else {
14090         status = stat(inFilename, &inbuf);
14091         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14092     }
14093
14094     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14095        counts the games, notes how each one terminated, etc.
14096
14097        It would be nice to remove this kludge and instead gather all
14098        the information while building the game list.  (And to keep it
14099        in the game list nodes instead of having a bunch of fixed-size
14100        parallel arrays.)  Note this will require getting each game's
14101        termination from the PGN tags, as the game list builder does
14102        not process the game moves.  --mann
14103        */
14104     cmailMsgLoaded = TRUE;
14105     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14106
14107     /* Load first game in the file or popup game menu */
14108     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14109
14110 #endif /* !WIN32 */
14111     return;
14112 }
14113
14114 int
14115 RegisterMove ()
14116 {
14117     FILE *f;
14118     char string[MSG_SIZ];
14119
14120     if (   cmailMailedMove
14121         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14122         return TRUE;            /* Allow free viewing  */
14123     }
14124
14125     /* Unregister move to ensure that we don't leave RegisterMove        */
14126     /* with the move registered when the conditions for registering no   */
14127     /* longer hold                                                       */
14128     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14129         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14130         nCmailMovesRegistered --;
14131
14132         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14133           {
14134               free(cmailCommentList[lastLoadGameNumber - 1]);
14135               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14136           }
14137     }
14138
14139     if (cmailOldMove == -1) {
14140         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14141         return FALSE;
14142     }
14143
14144     if (currentMove > cmailOldMove + 1) {
14145         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14146         return FALSE;
14147     }
14148
14149     if (currentMove < cmailOldMove) {
14150         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14151         return FALSE;
14152     }
14153
14154     if (forwardMostMove > currentMove) {
14155         /* Silently truncate extra moves */
14156         TruncateGame();
14157     }
14158
14159     if (   (currentMove == cmailOldMove + 1)
14160         || (   (currentMove == cmailOldMove)
14161             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14162                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14163         if (gameInfo.result != GameUnfinished) {
14164             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14165         }
14166
14167         if (commentList[currentMove] != NULL) {
14168             cmailCommentList[lastLoadGameNumber - 1]
14169               = StrSave(commentList[currentMove]);
14170         }
14171         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14172
14173         if (appData.debugMode)
14174           fprintf(debugFP, "Saving %s for game %d\n",
14175                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14176
14177         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14178
14179         f = fopen(string, "w");
14180         if (appData.oldSaveStyle) {
14181             SaveGameOldStyle(f); /* also closes the file */
14182
14183             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14184             f = fopen(string, "w");
14185             SavePosition(f, 0, NULL); /* also closes the file */
14186         } else {
14187             fprintf(f, "{--------------\n");
14188             PrintPosition(f, currentMove);
14189             fprintf(f, "--------------}\n\n");
14190
14191             SaveGame(f, 0, NULL); /* also closes the file*/
14192         }
14193
14194         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14195         nCmailMovesRegistered ++;
14196     } else if (nCmailGames == 1) {
14197         DisplayError(_("You have not made a move yet"), 0);
14198         return FALSE;
14199     }
14200
14201     return TRUE;
14202 }
14203
14204 void
14205 MailMoveEvent ()
14206 {
14207 #if !WIN32
14208     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14209     FILE *commandOutput;
14210     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14211     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14212     int nBuffers;
14213     int i;
14214     int archived;
14215     char *arcDir;
14216
14217     if (! cmailMsgLoaded) {
14218         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14219         return;
14220     }
14221
14222     if (nCmailGames == nCmailResults) {
14223         DisplayError(_("No unfinished games"), 0);
14224         return;
14225     }
14226
14227 #if CMAIL_PROHIBIT_REMAIL
14228     if (cmailMailedMove) {
14229       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);
14230         DisplayError(msg, 0);
14231         return;
14232     }
14233 #endif
14234
14235     if (! (cmailMailedMove || RegisterMove())) return;
14236
14237     if (   cmailMailedMove
14238         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14239       snprintf(string, MSG_SIZ, partCommandString,
14240                appData.debugMode ? " -v" : "", appData.cmailGameName);
14241         commandOutput = popen(string, "r");
14242
14243         if (commandOutput == NULL) {
14244             DisplayError(_("Failed to invoke cmail"), 0);
14245         } else {
14246             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14247                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14248             }
14249             if (nBuffers > 1) {
14250                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14251                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14252                 nBytes = MSG_SIZ - 1;
14253             } else {
14254                 (void) memcpy(msg, buffer, nBytes);
14255             }
14256             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14257
14258             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14259                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14260
14261                 archived = TRUE;
14262                 for (i = 0; i < nCmailGames; i ++) {
14263                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14264                         archived = FALSE;
14265                     }
14266                 }
14267                 if (   archived
14268                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14269                         != NULL)) {
14270                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14271                            arcDir,
14272                            appData.cmailGameName,
14273                            gameInfo.date);
14274                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14275                     cmailMsgLoaded = FALSE;
14276                 }
14277             }
14278
14279             DisplayInformation(msg);
14280             pclose(commandOutput);
14281         }
14282     } else {
14283         if ((*cmailMsg) != '\0') {
14284             DisplayInformation(cmailMsg);
14285         }
14286     }
14287
14288     return;
14289 #endif /* !WIN32 */
14290 }
14291
14292 char *
14293 CmailMsg ()
14294 {
14295 #if WIN32
14296     return NULL;
14297 #else
14298     int  prependComma = 0;
14299     char number[5];
14300     char string[MSG_SIZ];       /* Space for game-list */
14301     int  i;
14302
14303     if (!cmailMsgLoaded) return "";
14304
14305     if (cmailMailedMove) {
14306       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14307     } else {
14308         /* Create a list of games left */
14309       snprintf(string, MSG_SIZ, "[");
14310         for (i = 0; i < nCmailGames; i ++) {
14311             if (! (   cmailMoveRegistered[i]
14312                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14313                 if (prependComma) {
14314                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14315                 } else {
14316                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14317                     prependComma = 1;
14318                 }
14319
14320                 strcat(string, number);
14321             }
14322         }
14323         strcat(string, "]");
14324
14325         if (nCmailMovesRegistered + nCmailResults == 0) {
14326             switch (nCmailGames) {
14327               case 1:
14328                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14329                 break;
14330
14331               case 2:
14332                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14333                 break;
14334
14335               default:
14336                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14337                          nCmailGames);
14338                 break;
14339             }
14340         } else {
14341             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14342               case 1:
14343                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14344                          string);
14345                 break;
14346
14347               case 0:
14348                 if (nCmailResults == nCmailGames) {
14349                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14350                 } else {
14351                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14352                 }
14353                 break;
14354
14355               default:
14356                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14357                          string);
14358             }
14359         }
14360     }
14361     return cmailMsg;
14362 #endif /* WIN32 */
14363 }
14364
14365 void
14366 ResetGameEvent ()
14367 {
14368     if (gameMode == Training)
14369       SetTrainingModeOff();
14370
14371     Reset(TRUE, TRUE);
14372     cmailMsgLoaded = FALSE;
14373     if (appData.icsActive) {
14374       SendToICS(ics_prefix);
14375       SendToICS("refresh\n");
14376     }
14377 }
14378
14379 void
14380 ExitEvent (int status)
14381 {
14382     exiting++;
14383     if (exiting > 2) {
14384       /* Give up on clean exit */
14385       exit(status);
14386     }
14387     if (exiting > 1) {
14388       /* Keep trying for clean exit */
14389       return;
14390     }
14391
14392     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14393     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14394
14395     if (telnetISR != NULL) {
14396       RemoveInputSource(telnetISR);
14397     }
14398     if (icsPR != NoProc) {
14399       DestroyChildProcess(icsPR, TRUE);
14400     }
14401
14402     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14403     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14404
14405     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14406     /* make sure this other one finishes before killing it!                  */
14407     if(endingGame) { int count = 0;
14408         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14409         while(endingGame && count++ < 10) DoSleep(1);
14410         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14411     }
14412
14413     /* Kill off chess programs */
14414     if (first.pr != NoProc) {
14415         ExitAnalyzeMode();
14416
14417         DoSleep( appData.delayBeforeQuit );
14418         SendToProgram("quit\n", &first);
14419         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14420     }
14421     if (second.pr != NoProc) {
14422         DoSleep( appData.delayBeforeQuit );
14423         SendToProgram("quit\n", &second);
14424         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14425     }
14426     if (first.isr != NULL) {
14427         RemoveInputSource(first.isr);
14428     }
14429     if (second.isr != NULL) {
14430         RemoveInputSource(second.isr);
14431     }
14432
14433     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14434     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14435
14436     ShutDownFrontEnd();
14437     exit(status);
14438 }
14439
14440 void
14441 PauseEngine (ChessProgramState *cps)
14442 {
14443     SendToProgram("pause\n", cps);
14444     cps->pause = 2;
14445 }
14446
14447 void
14448 UnPauseEngine (ChessProgramState *cps)
14449 {
14450     SendToProgram("resume\n", cps);
14451     cps->pause = 1;
14452 }
14453
14454 void
14455 PauseEvent ()
14456 {
14457     if (appData.debugMode)
14458         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14459     if (pausing) {
14460         pausing = FALSE;
14461         ModeHighlight();
14462         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14463             StartClocks();
14464             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14465                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14466                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14467             }
14468             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14469             HandleMachineMove(stashedInputMove, stalledEngine);
14470             stalledEngine = NULL;
14471             return;
14472         }
14473         if (gameMode == MachinePlaysWhite ||
14474             gameMode == TwoMachinesPlay   ||
14475             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14476             if(first.pause)  UnPauseEngine(&first);
14477             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14478             if(second.pause) UnPauseEngine(&second);
14479             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14480             StartClocks();
14481         } else {
14482             DisplayBothClocks();
14483         }
14484         if (gameMode == PlayFromGameFile) {
14485             if (appData.timeDelay >= 0)
14486                 AutoPlayGameLoop();
14487         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14488             Reset(FALSE, TRUE);
14489             SendToICS(ics_prefix);
14490             SendToICS("refresh\n");
14491         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14492             ForwardInner(forwardMostMove);
14493         }
14494         pauseExamInvalid = FALSE;
14495     } else {
14496         switch (gameMode) {
14497           default:
14498             return;
14499           case IcsExamining:
14500             pauseExamForwardMostMove = forwardMostMove;
14501             pauseExamInvalid = FALSE;
14502             /* fall through */
14503           case IcsObserving:
14504           case IcsPlayingWhite:
14505           case IcsPlayingBlack:
14506             pausing = TRUE;
14507             ModeHighlight();
14508             return;
14509           case PlayFromGameFile:
14510             (void) StopLoadGameTimer();
14511             pausing = TRUE;
14512             ModeHighlight();
14513             break;
14514           case BeginningOfGame:
14515             if (appData.icsActive) return;
14516             /* else fall through */
14517           case MachinePlaysWhite:
14518           case MachinePlaysBlack:
14519           case TwoMachinesPlay:
14520             if (forwardMostMove == 0)
14521               return;           /* don't pause if no one has moved */
14522             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14523                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14524                 if(onMove->pause) {           // thinking engine can be paused
14525                     PauseEngine(onMove);      // do it
14526                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14527                         PauseEngine(onMove->other);
14528                     else
14529                         SendToProgram("easy\n", onMove->other);
14530                     StopClocks();
14531                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14532             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14533                 if(first.pause) {
14534                     PauseEngine(&first);
14535                     StopClocks();
14536                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14537             } else { // human on move, pause pondering by either method
14538                 if(first.pause)
14539                     PauseEngine(&first);
14540                 else if(appData.ponderNextMove)
14541                     SendToProgram("easy\n", &first);
14542                 StopClocks();
14543             }
14544             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14545           case AnalyzeMode:
14546             pausing = TRUE;
14547             ModeHighlight();
14548             break;
14549         }
14550     }
14551 }
14552
14553 void
14554 EditCommentEvent ()
14555 {
14556     char title[MSG_SIZ];
14557
14558     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14559       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14560     } else {
14561       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14562                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14563                parseList[currentMove - 1]);
14564     }
14565
14566     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14567 }
14568
14569
14570 void
14571 EditTagsEvent ()
14572 {
14573     char *tags = PGNTags(&gameInfo);
14574     bookUp = FALSE;
14575     EditTagsPopUp(tags, NULL);
14576     free(tags);
14577 }
14578
14579 void
14580 ToggleSecond ()
14581 {
14582   if(second.analyzing) {
14583     SendToProgram("exit\n", &second);
14584     second.analyzing = FALSE;
14585   } else {
14586     if (second.pr == NoProc) StartChessProgram(&second);
14587     InitChessProgram(&second, FALSE);
14588     FeedMovesToProgram(&second, currentMove);
14589
14590     SendToProgram("analyze\n", &second);
14591     second.analyzing = TRUE;
14592   }
14593 }
14594
14595 /* Toggle ShowThinking */
14596 void
14597 ToggleShowThinking()
14598 {
14599   appData.showThinking = !appData.showThinking;
14600   ShowThinkingEvent();
14601 }
14602
14603 int
14604 AnalyzeModeEvent ()
14605 {
14606     char buf[MSG_SIZ];
14607
14608     if (!first.analysisSupport) {
14609       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14610       DisplayError(buf, 0);
14611       return 0;
14612     }
14613     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14614     if (appData.icsActive) {
14615         if (gameMode != IcsObserving) {
14616           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14617             DisplayError(buf, 0);
14618             /* secure check */
14619             if (appData.icsEngineAnalyze) {
14620                 if (appData.debugMode)
14621                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14622                 ExitAnalyzeMode();
14623                 ModeHighlight();
14624             }
14625             return 0;
14626         }
14627         /* if enable, user wants to disable icsEngineAnalyze */
14628         if (appData.icsEngineAnalyze) {
14629                 ExitAnalyzeMode();
14630                 ModeHighlight();
14631                 return 0;
14632         }
14633         appData.icsEngineAnalyze = TRUE;
14634         if (appData.debugMode)
14635             fprintf(debugFP, "ICS engine analyze starting... \n");
14636     }
14637
14638     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14639     if (appData.noChessProgram || gameMode == AnalyzeMode)
14640       return 0;
14641
14642     if (gameMode != AnalyzeFile) {
14643         if (!appData.icsEngineAnalyze) {
14644                EditGameEvent();
14645                if (gameMode != EditGame) return 0;
14646         }
14647         if (!appData.showThinking) ToggleShowThinking();
14648         ResurrectChessProgram();
14649         SendToProgram("analyze\n", &first);
14650         first.analyzing = TRUE;
14651         /*first.maybeThinking = TRUE;*/
14652         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14653         EngineOutputPopUp();
14654     }
14655     if (!appData.icsEngineAnalyze) {
14656         gameMode = AnalyzeMode;
14657         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14658     }
14659     pausing = FALSE;
14660     ModeHighlight();
14661     SetGameInfo();
14662
14663     StartAnalysisClock();
14664     GetTimeMark(&lastNodeCountTime);
14665     lastNodeCount = 0;
14666     return 1;
14667 }
14668
14669 void
14670 AnalyzeFileEvent ()
14671 {
14672     if (appData.noChessProgram || gameMode == AnalyzeFile)
14673       return;
14674
14675     if (!first.analysisSupport) {
14676       char buf[MSG_SIZ];
14677       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14678       DisplayError(buf, 0);
14679       return;
14680     }
14681
14682     if (gameMode != AnalyzeMode) {
14683         keepInfo = 1; // mere annotating should not alter PGN tags
14684         EditGameEvent();
14685         keepInfo = 0;
14686         if (gameMode != EditGame) return;
14687         if (!appData.showThinking) ToggleShowThinking();
14688         ResurrectChessProgram();
14689         SendToProgram("analyze\n", &first);
14690         first.analyzing = TRUE;
14691         /*first.maybeThinking = TRUE;*/
14692         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14693         EngineOutputPopUp();
14694     }
14695     gameMode = AnalyzeFile;
14696     pausing = FALSE;
14697     ModeHighlight();
14698
14699     StartAnalysisClock();
14700     GetTimeMark(&lastNodeCountTime);
14701     lastNodeCount = 0;
14702     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14703     AnalysisPeriodicEvent(1);
14704 }
14705
14706 void
14707 MachineWhiteEvent ()
14708 {
14709     char buf[MSG_SIZ];
14710     char *bookHit = NULL;
14711
14712     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14713       return;
14714
14715
14716     if (gameMode == PlayFromGameFile ||
14717         gameMode == TwoMachinesPlay  ||
14718         gameMode == Training         ||
14719         gameMode == AnalyzeMode      ||
14720         gameMode == EndOfGame)
14721         EditGameEvent();
14722
14723     if (gameMode == EditPosition)
14724         EditPositionDone(TRUE);
14725
14726     if (!WhiteOnMove(currentMove)) {
14727         DisplayError(_("It is not White's turn"), 0);
14728         return;
14729     }
14730
14731     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14732       ExitAnalyzeMode();
14733
14734     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14735         gameMode == AnalyzeFile)
14736         TruncateGame();
14737
14738     ResurrectChessProgram();    /* in case it isn't running */
14739     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14740         gameMode = MachinePlaysWhite;
14741         ResetClocks();
14742     } else
14743     gameMode = MachinePlaysWhite;
14744     pausing = FALSE;
14745     ModeHighlight();
14746     SetGameInfo();
14747     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14748     DisplayTitle(buf);
14749     if (first.sendName) {
14750       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14751       SendToProgram(buf, &first);
14752     }
14753     if (first.sendTime) {
14754       if (first.useColors) {
14755         SendToProgram("black\n", &first); /*gnu kludge*/
14756       }
14757       SendTimeRemaining(&first, TRUE);
14758     }
14759     if (first.useColors) {
14760       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14761     }
14762     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14763     SetMachineThinkingEnables();
14764     first.maybeThinking = TRUE;
14765     StartClocks();
14766     firstMove = FALSE;
14767
14768     if (appData.autoFlipView && !flipView) {
14769       flipView = !flipView;
14770       DrawPosition(FALSE, NULL);
14771       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14772     }
14773
14774     if(bookHit) { // [HGM] book: simulate book reply
14775         static char bookMove[MSG_SIZ]; // a bit generous?
14776
14777         programStats.nodes = programStats.depth = programStats.time =
14778         programStats.score = programStats.got_only_move = 0;
14779         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14780
14781         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14782         strcat(bookMove, bookHit);
14783         HandleMachineMove(bookMove, &first);
14784     }
14785 }
14786
14787 void
14788 MachineBlackEvent ()
14789 {
14790   char buf[MSG_SIZ];
14791   char *bookHit = NULL;
14792
14793     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14794         return;
14795
14796
14797     if (gameMode == PlayFromGameFile ||
14798         gameMode == TwoMachinesPlay  ||
14799         gameMode == Training         ||
14800         gameMode == AnalyzeMode      ||
14801         gameMode == EndOfGame)
14802         EditGameEvent();
14803
14804     if (gameMode == EditPosition)
14805         EditPositionDone(TRUE);
14806
14807     if (WhiteOnMove(currentMove)) {
14808         DisplayError(_("It is not Black's turn"), 0);
14809         return;
14810     }
14811
14812     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14813       ExitAnalyzeMode();
14814
14815     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14816         gameMode == AnalyzeFile)
14817         TruncateGame();
14818
14819     ResurrectChessProgram();    /* in case it isn't running */
14820     gameMode = MachinePlaysBlack;
14821     pausing = FALSE;
14822     ModeHighlight();
14823     SetGameInfo();
14824     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14825     DisplayTitle(buf);
14826     if (first.sendName) {
14827       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14828       SendToProgram(buf, &first);
14829     }
14830     if (first.sendTime) {
14831       if (first.useColors) {
14832         SendToProgram("white\n", &first); /*gnu kludge*/
14833       }
14834       SendTimeRemaining(&first, FALSE);
14835     }
14836     if (first.useColors) {
14837       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14838     }
14839     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14840     SetMachineThinkingEnables();
14841     first.maybeThinking = TRUE;
14842     StartClocks();
14843
14844     if (appData.autoFlipView && flipView) {
14845       flipView = !flipView;
14846       DrawPosition(FALSE, NULL);
14847       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14848     }
14849     if(bookHit) { // [HGM] book: simulate book reply
14850         static char bookMove[MSG_SIZ]; // a bit generous?
14851
14852         programStats.nodes = programStats.depth = programStats.time =
14853         programStats.score = programStats.got_only_move = 0;
14854         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14855
14856         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14857         strcat(bookMove, bookHit);
14858         HandleMachineMove(bookMove, &first);
14859     }
14860 }
14861
14862
14863 void
14864 DisplayTwoMachinesTitle ()
14865 {
14866     char buf[MSG_SIZ];
14867     if (appData.matchGames > 0) {
14868         if(appData.tourneyFile[0]) {
14869           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14870                    gameInfo.white, _("vs."), gameInfo.black,
14871                    nextGame+1, appData.matchGames+1,
14872                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14873         } else
14874         if (first.twoMachinesColor[0] == 'w') {
14875           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14876                    gameInfo.white, _("vs."),  gameInfo.black,
14877                    first.matchWins, second.matchWins,
14878                    matchGame - 1 - (first.matchWins + second.matchWins));
14879         } else {
14880           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14881                    gameInfo.white, _("vs."), gameInfo.black,
14882                    second.matchWins, first.matchWins,
14883                    matchGame - 1 - (first.matchWins + second.matchWins));
14884         }
14885     } else {
14886       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14887     }
14888     DisplayTitle(buf);
14889 }
14890
14891 void
14892 SettingsMenuIfReady ()
14893 {
14894   if (second.lastPing != second.lastPong) {
14895     DisplayMessage("", _("Waiting for second chess program"));
14896     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14897     return;
14898   }
14899   ThawUI();
14900   DisplayMessage("", "");
14901   SettingsPopUp(&second);
14902 }
14903
14904 int
14905 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14906 {
14907     char buf[MSG_SIZ];
14908     if (cps->pr == NoProc) {
14909         StartChessProgram(cps);
14910         if (cps->protocolVersion == 1) {
14911           retry();
14912           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14913         } else {
14914           /* kludge: allow timeout for initial "feature" command */
14915           if(retry != TwoMachinesEventIfReady) FreezeUI();
14916           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14917           DisplayMessage("", buf);
14918           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14919         }
14920         return 1;
14921     }
14922     return 0;
14923 }
14924
14925 void
14926 TwoMachinesEvent P((void))
14927 {
14928     int i;
14929     char buf[MSG_SIZ];
14930     ChessProgramState *onmove;
14931     char *bookHit = NULL;
14932     static int stalling = 0;
14933     TimeMark now;
14934     long wait;
14935
14936     if (appData.noChessProgram) return;
14937
14938     switch (gameMode) {
14939       case TwoMachinesPlay:
14940         return;
14941       case MachinePlaysWhite:
14942       case MachinePlaysBlack:
14943         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14944             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14945             return;
14946         }
14947         /* fall through */
14948       case BeginningOfGame:
14949       case PlayFromGameFile:
14950       case EndOfGame:
14951         EditGameEvent();
14952         if (gameMode != EditGame) return;
14953         break;
14954       case EditPosition:
14955         EditPositionDone(TRUE);
14956         break;
14957       case AnalyzeMode:
14958       case AnalyzeFile:
14959         ExitAnalyzeMode();
14960         break;
14961       case EditGame:
14962       default:
14963         break;
14964     }
14965
14966 //    forwardMostMove = currentMove;
14967     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14968     startingEngine = TRUE;
14969
14970     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14971
14972     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14973     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14974       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14975       return;
14976     }
14977     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14978
14979     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14980                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14981         startingEngine = matchMode = FALSE;
14982         DisplayError("second engine does not play this", 0);
14983         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14984         EditGameEvent(); // switch back to EditGame mode
14985         return;
14986     }
14987
14988     if(!stalling) {
14989       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14990       SendToProgram("force\n", &second);
14991       stalling = 1;
14992       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14993       return;
14994     }
14995     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14996     if(appData.matchPause>10000 || appData.matchPause<10)
14997                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14998     wait = SubtractTimeMarks(&now, &pauseStart);
14999     if(wait < appData.matchPause) {
15000         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15001         return;
15002     }
15003     // we are now committed to starting the game
15004     stalling = 0;
15005     DisplayMessage("", "");
15006     if (startedFromSetupPosition) {
15007         SendBoard(&second, backwardMostMove);
15008     if (appData.debugMode) {
15009         fprintf(debugFP, "Two Machines\n");
15010     }
15011     }
15012     for (i = backwardMostMove; i < forwardMostMove; i++) {
15013         SendMoveToProgram(i, &second);
15014     }
15015
15016     gameMode = TwoMachinesPlay;
15017     pausing = startingEngine = FALSE;
15018     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15019     SetGameInfo();
15020     DisplayTwoMachinesTitle();
15021     firstMove = TRUE;
15022     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15023         onmove = &first;
15024     } else {
15025         onmove = &second;
15026     }
15027     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15028     SendToProgram(first.computerString, &first);
15029     if (first.sendName) {
15030       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15031       SendToProgram(buf, &first);
15032     }
15033     SendToProgram(second.computerString, &second);
15034     if (second.sendName) {
15035       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15036       SendToProgram(buf, &second);
15037     }
15038
15039     ResetClocks();
15040     if (!first.sendTime || !second.sendTime) {
15041         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15042         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15043     }
15044     if (onmove->sendTime) {
15045       if (onmove->useColors) {
15046         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15047       }
15048       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15049     }
15050     if (onmove->useColors) {
15051       SendToProgram(onmove->twoMachinesColor, onmove);
15052     }
15053     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15054 //    SendToProgram("go\n", onmove);
15055     onmove->maybeThinking = TRUE;
15056     SetMachineThinkingEnables();
15057
15058     StartClocks();
15059
15060     if(bookHit) { // [HGM] book: simulate book reply
15061         static char bookMove[MSG_SIZ]; // a bit generous?
15062
15063         programStats.nodes = programStats.depth = programStats.time =
15064         programStats.score = programStats.got_only_move = 0;
15065         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15066
15067         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15068         strcat(bookMove, bookHit);
15069         savedMessage = bookMove; // args for deferred call
15070         savedState = onmove;
15071         ScheduleDelayedEvent(DeferredBookMove, 1);
15072     }
15073 }
15074
15075 void
15076 TrainingEvent ()
15077 {
15078     if (gameMode == Training) {
15079       SetTrainingModeOff();
15080       gameMode = PlayFromGameFile;
15081       DisplayMessage("", _("Training mode off"));
15082     } else {
15083       gameMode = Training;
15084       animateTraining = appData.animate;
15085
15086       /* make sure we are not already at the end of the game */
15087       if (currentMove < forwardMostMove) {
15088         SetTrainingModeOn();
15089         DisplayMessage("", _("Training mode on"));
15090       } else {
15091         gameMode = PlayFromGameFile;
15092         DisplayError(_("Already at end of game"), 0);
15093       }
15094     }
15095     ModeHighlight();
15096 }
15097
15098 void
15099 IcsClientEvent ()
15100 {
15101     if (!appData.icsActive) return;
15102     switch (gameMode) {
15103       case IcsPlayingWhite:
15104       case IcsPlayingBlack:
15105       case IcsObserving:
15106       case IcsIdle:
15107       case BeginningOfGame:
15108       case IcsExamining:
15109         return;
15110
15111       case EditGame:
15112         break;
15113
15114       case EditPosition:
15115         EditPositionDone(TRUE);
15116         break;
15117
15118       case AnalyzeMode:
15119       case AnalyzeFile:
15120         ExitAnalyzeMode();
15121         break;
15122
15123       default:
15124         EditGameEvent();
15125         break;
15126     }
15127
15128     gameMode = IcsIdle;
15129     ModeHighlight();
15130     return;
15131 }
15132
15133 void
15134 EditGameEvent ()
15135 {
15136     int i;
15137
15138     switch (gameMode) {
15139       case Training:
15140         SetTrainingModeOff();
15141         break;
15142       case MachinePlaysWhite:
15143       case MachinePlaysBlack:
15144       case BeginningOfGame:
15145         SendToProgram("force\n", &first);
15146         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15147             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15148                 char buf[MSG_SIZ];
15149                 abortEngineThink = TRUE;
15150                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15151                 SendToProgram(buf, &first);
15152                 DisplayMessage("Aborting engine think", "");
15153                 FreezeUI();
15154             }
15155         }
15156         SetUserThinkingEnables();
15157         break;
15158       case PlayFromGameFile:
15159         (void) StopLoadGameTimer();
15160         if (gameFileFP != NULL) {
15161             gameFileFP = NULL;
15162         }
15163         break;
15164       case EditPosition:
15165         EditPositionDone(TRUE);
15166         break;
15167       case AnalyzeMode:
15168       case AnalyzeFile:
15169         ExitAnalyzeMode();
15170         SendToProgram("force\n", &first);
15171         break;
15172       case TwoMachinesPlay:
15173         GameEnds(EndOfFile, NULL, GE_PLAYER);
15174         ResurrectChessProgram();
15175         SetUserThinkingEnables();
15176         break;
15177       case EndOfGame:
15178         ResurrectChessProgram();
15179         break;
15180       case IcsPlayingBlack:
15181       case IcsPlayingWhite:
15182         DisplayError(_("Warning: You are still playing a game"), 0);
15183         break;
15184       case IcsObserving:
15185         DisplayError(_("Warning: You are still observing a game"), 0);
15186         break;
15187       case IcsExamining:
15188         DisplayError(_("Warning: You are still examining a game"), 0);
15189         break;
15190       case IcsIdle:
15191         break;
15192       case EditGame:
15193       default:
15194         return;
15195     }
15196
15197     pausing = FALSE;
15198     StopClocks();
15199     first.offeredDraw = second.offeredDraw = 0;
15200
15201     if (gameMode == PlayFromGameFile) {
15202         whiteTimeRemaining = timeRemaining[0][currentMove];
15203         blackTimeRemaining = timeRemaining[1][currentMove];
15204         DisplayTitle("");
15205     }
15206
15207     if (gameMode == MachinePlaysWhite ||
15208         gameMode == MachinePlaysBlack ||
15209         gameMode == TwoMachinesPlay ||
15210         gameMode == EndOfGame) {
15211         i = forwardMostMove;
15212         while (i > currentMove) {
15213             SendToProgram("undo\n", &first);
15214             i--;
15215         }
15216         if(!adjustedClock) {
15217         whiteTimeRemaining = timeRemaining[0][currentMove];
15218         blackTimeRemaining = timeRemaining[1][currentMove];
15219         DisplayBothClocks();
15220         }
15221         if (whiteFlag || blackFlag) {
15222             whiteFlag = blackFlag = 0;
15223         }
15224         DisplayTitle("");
15225     }
15226
15227     gameMode = EditGame;
15228     ModeHighlight();
15229     SetGameInfo();
15230 }
15231
15232
15233 void
15234 EditPositionEvent ()
15235 {
15236     if (gameMode == EditPosition) {
15237         EditGameEvent();
15238         return;
15239     }
15240
15241     EditGameEvent();
15242     if (gameMode != EditGame) return;
15243
15244     gameMode = EditPosition;
15245     ModeHighlight();
15246     SetGameInfo();
15247     if (currentMove > 0)
15248       CopyBoard(boards[0], boards[currentMove]);
15249
15250     blackPlaysFirst = !WhiteOnMove(currentMove);
15251     ResetClocks();
15252     currentMove = forwardMostMove = backwardMostMove = 0;
15253     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15254     DisplayMove(-1);
15255     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15256 }
15257
15258 void
15259 ExitAnalyzeMode ()
15260 {
15261     /* [DM] icsEngineAnalyze - possible call from other functions */
15262     if (appData.icsEngineAnalyze) {
15263         appData.icsEngineAnalyze = FALSE;
15264
15265         DisplayMessage("",_("Close ICS engine analyze..."));
15266     }
15267     if (first.analysisSupport && first.analyzing) {
15268       SendToBoth("exit\n");
15269       first.analyzing = second.analyzing = FALSE;
15270     }
15271     thinkOutput[0] = NULLCHAR;
15272 }
15273
15274 void
15275 EditPositionDone (Boolean fakeRights)
15276 {
15277     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15278
15279     startedFromSetupPosition = TRUE;
15280     InitChessProgram(&first, FALSE);
15281     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15282       boards[0][EP_STATUS] = EP_NONE;
15283       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15284       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15285         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15286         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15287       } else boards[0][CASTLING][2] = NoRights;
15288       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15289         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15290         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15291       } else boards[0][CASTLING][5] = NoRights;
15292       if(gameInfo.variant == VariantSChess) {
15293         int i;
15294         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15295           boards[0][VIRGIN][i] = 0;
15296           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15297           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15298         }
15299       }
15300     }
15301     SendToProgram("force\n", &first);
15302     if (blackPlaysFirst) {
15303         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15304         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15305         currentMove = forwardMostMove = backwardMostMove = 1;
15306         CopyBoard(boards[1], boards[0]);
15307     } else {
15308         currentMove = forwardMostMove = backwardMostMove = 0;
15309     }
15310     SendBoard(&first, forwardMostMove);
15311     if (appData.debugMode) {
15312         fprintf(debugFP, "EditPosDone\n");
15313     }
15314     DisplayTitle("");
15315     DisplayMessage("", "");
15316     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15317     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15318     gameMode = EditGame;
15319     ModeHighlight();
15320     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15321     ClearHighlights(); /* [AS] */
15322 }
15323
15324 /* Pause for `ms' milliseconds */
15325 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15326 void
15327 TimeDelay (long ms)
15328 {
15329     TimeMark m1, m2;
15330
15331     GetTimeMark(&m1);
15332     do {
15333         GetTimeMark(&m2);
15334     } while (SubtractTimeMarks(&m2, &m1) < ms);
15335 }
15336
15337 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15338 void
15339 SendMultiLineToICS (char *buf)
15340 {
15341     char temp[MSG_SIZ+1], *p;
15342     int len;
15343
15344     len = strlen(buf);
15345     if (len > MSG_SIZ)
15346       len = MSG_SIZ;
15347
15348     strncpy(temp, buf, len);
15349     temp[len] = 0;
15350
15351     p = temp;
15352     while (*p) {
15353         if (*p == '\n' || *p == '\r')
15354           *p = ' ';
15355         ++p;
15356     }
15357
15358     strcat(temp, "\n");
15359     SendToICS(temp);
15360     SendToPlayer(temp, strlen(temp));
15361 }
15362
15363 void
15364 SetWhiteToPlayEvent ()
15365 {
15366     if (gameMode == EditPosition) {
15367         blackPlaysFirst = FALSE;
15368         DisplayBothClocks();    /* works because currentMove is 0 */
15369     } else if (gameMode == IcsExamining) {
15370         SendToICS(ics_prefix);
15371         SendToICS("tomove white\n");
15372     }
15373 }
15374
15375 void
15376 SetBlackToPlayEvent ()
15377 {
15378     if (gameMode == EditPosition) {
15379         blackPlaysFirst = TRUE;
15380         currentMove = 1;        /* kludge */
15381         DisplayBothClocks();
15382         currentMove = 0;
15383     } else if (gameMode == IcsExamining) {
15384         SendToICS(ics_prefix);
15385         SendToICS("tomove black\n");
15386     }
15387 }
15388
15389 void
15390 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15391 {
15392     char buf[MSG_SIZ];
15393     ChessSquare piece = boards[0][y][x];
15394     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15395     static int lastVariant;
15396
15397     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15398
15399     switch (selection) {
15400       case ClearBoard:
15401         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15402         MarkTargetSquares(1);
15403         CopyBoard(currentBoard, boards[0]);
15404         CopyBoard(menuBoard, initialPosition);
15405         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15406             SendToICS(ics_prefix);
15407             SendToICS("bsetup clear\n");
15408         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15409             SendToICS(ics_prefix);
15410             SendToICS("clearboard\n");
15411         } else {
15412             int nonEmpty = 0;
15413             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15414                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15415                 for (y = 0; y < BOARD_HEIGHT; y++) {
15416                     if (gameMode == IcsExamining) {
15417                         if (boards[currentMove][y][x] != EmptySquare) {
15418                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15419                                     AAA + x, ONE + y);
15420                             SendToICS(buf);
15421                         }
15422                     } else if(boards[0][y][x] != DarkSquare) {
15423                         if(boards[0][y][x] != p) nonEmpty++;
15424                         boards[0][y][x] = p;
15425                     }
15426                 }
15427             }
15428             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15429                 int r;
15430                 for(r = 0; r < BOARD_HEIGHT; r++) {
15431                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15432                     ChessSquare p = menuBoard[r][x];
15433                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15434                   }
15435                 }
15436                 DisplayMessage("Clicking clock again restores position", "");
15437                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15438                 if(!nonEmpty) { // asked to clear an empty board
15439                     CopyBoard(boards[0], menuBoard);
15440                 } else
15441                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15442                     CopyBoard(boards[0], initialPosition);
15443                 } else
15444                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15445                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15446                     CopyBoard(boards[0], erasedBoard);
15447                 } else
15448                     CopyBoard(erasedBoard, currentBoard);
15449
15450             }
15451         }
15452         if (gameMode == EditPosition) {
15453             DrawPosition(FALSE, boards[0]);
15454         }
15455         break;
15456
15457       case WhitePlay:
15458         SetWhiteToPlayEvent();
15459         break;
15460
15461       case BlackPlay:
15462         SetBlackToPlayEvent();
15463         break;
15464
15465       case EmptySquare:
15466         if (gameMode == IcsExamining) {
15467             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15468             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15469             SendToICS(buf);
15470         } else {
15471             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15472                 if(x == BOARD_LEFT-2) {
15473                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15474                     boards[0][y][1] = 0;
15475                 } else
15476                 if(x == BOARD_RGHT+1) {
15477                     if(y >= gameInfo.holdingsSize) break;
15478                     boards[0][y][BOARD_WIDTH-2] = 0;
15479                 } else break;
15480             }
15481             boards[0][y][x] = EmptySquare;
15482             DrawPosition(FALSE, boards[0]);
15483         }
15484         break;
15485
15486       case PromotePiece:
15487         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15488            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15489             selection = (ChessSquare) (PROMOTED(piece));
15490         } else if(piece == EmptySquare) selection = WhiteSilver;
15491         else selection = (ChessSquare)((int)piece - 1);
15492         goto defaultlabel;
15493
15494       case DemotePiece:
15495         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15496            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15497             selection = (ChessSquare) (DEMOTED(piece));
15498         } else if(piece == EmptySquare) selection = BlackSilver;
15499         else selection = (ChessSquare)((int)piece + 1);
15500         goto defaultlabel;
15501
15502       case WhiteQueen:
15503       case BlackQueen:
15504         if(gameInfo.variant == VariantShatranj ||
15505            gameInfo.variant == VariantXiangqi  ||
15506            gameInfo.variant == VariantCourier  ||
15507            gameInfo.variant == VariantASEAN    ||
15508            gameInfo.variant == VariantMakruk     )
15509             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15510         goto defaultlabel;
15511
15512       case WhiteKing:
15513       case BlackKing:
15514         if(gameInfo.variant == VariantXiangqi)
15515             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15516         if(gameInfo.variant == VariantKnightmate)
15517             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15518       default:
15519         defaultlabel:
15520         if (gameMode == IcsExamining) {
15521             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15522             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15523                      PieceToChar(selection), AAA + x, ONE + y);
15524             SendToICS(buf);
15525         } else {
15526             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15527                 int n;
15528                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15529                     n = PieceToNumber(selection - BlackPawn);
15530                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15531                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15532                     boards[0][BOARD_HEIGHT-1-n][1]++;
15533                 } else
15534                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15535                     n = PieceToNumber(selection);
15536                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15537                     boards[0][n][BOARD_WIDTH-1] = selection;
15538                     boards[0][n][BOARD_WIDTH-2]++;
15539                 }
15540             } else
15541             boards[0][y][x] = selection;
15542             DrawPosition(TRUE, boards[0]);
15543             ClearHighlights();
15544             fromX = fromY = -1;
15545         }
15546         break;
15547     }
15548 }
15549
15550
15551 void
15552 DropMenuEvent (ChessSquare selection, int x, int y)
15553 {
15554     ChessMove moveType;
15555
15556     switch (gameMode) {
15557       case IcsPlayingWhite:
15558       case MachinePlaysBlack:
15559         if (!WhiteOnMove(currentMove)) {
15560             DisplayMoveError(_("It is Black's turn"));
15561             return;
15562         }
15563         moveType = WhiteDrop;
15564         break;
15565       case IcsPlayingBlack:
15566       case MachinePlaysWhite:
15567         if (WhiteOnMove(currentMove)) {
15568             DisplayMoveError(_("It is White's turn"));
15569             return;
15570         }
15571         moveType = BlackDrop;
15572         break;
15573       case EditGame:
15574         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15575         break;
15576       default:
15577         return;
15578     }
15579
15580     if (moveType == BlackDrop && selection < BlackPawn) {
15581       selection = (ChessSquare) ((int) selection
15582                                  + (int) BlackPawn - (int) WhitePawn);
15583     }
15584     if (boards[currentMove][y][x] != EmptySquare) {
15585         DisplayMoveError(_("That square is occupied"));
15586         return;
15587     }
15588
15589     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15590 }
15591
15592 void
15593 AcceptEvent ()
15594 {
15595     /* Accept a pending offer of any kind from opponent */
15596
15597     if (appData.icsActive) {
15598         SendToICS(ics_prefix);
15599         SendToICS("accept\n");
15600     } else if (cmailMsgLoaded) {
15601         if (currentMove == cmailOldMove &&
15602             commentList[cmailOldMove] != NULL &&
15603             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15604                    "Black offers a draw" : "White offers a draw")) {
15605             TruncateGame();
15606             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15607             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15608         } else {
15609             DisplayError(_("There is no pending offer on this move"), 0);
15610             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15611         }
15612     } else {
15613         /* Not used for offers from chess program */
15614     }
15615 }
15616
15617 void
15618 DeclineEvent ()
15619 {
15620     /* Decline a pending offer of any kind from opponent */
15621
15622     if (appData.icsActive) {
15623         SendToICS(ics_prefix);
15624         SendToICS("decline\n");
15625     } else if (cmailMsgLoaded) {
15626         if (currentMove == cmailOldMove &&
15627             commentList[cmailOldMove] != NULL &&
15628             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15629                    "Black offers a draw" : "White offers a draw")) {
15630 #ifdef NOTDEF
15631             AppendComment(cmailOldMove, "Draw declined", TRUE);
15632             DisplayComment(cmailOldMove - 1, "Draw declined");
15633 #endif /*NOTDEF*/
15634         } else {
15635             DisplayError(_("There is no pending offer on this move"), 0);
15636         }
15637     } else {
15638         /* Not used for offers from chess program */
15639     }
15640 }
15641
15642 void
15643 RematchEvent ()
15644 {
15645     /* Issue ICS rematch command */
15646     if (appData.icsActive) {
15647         SendToICS(ics_prefix);
15648         SendToICS("rematch\n");
15649     }
15650 }
15651
15652 void
15653 CallFlagEvent ()
15654 {
15655     /* Call your opponent's flag (claim a win on time) */
15656     if (appData.icsActive) {
15657         SendToICS(ics_prefix);
15658         SendToICS("flag\n");
15659     } else {
15660         switch (gameMode) {
15661           default:
15662             return;
15663           case MachinePlaysWhite:
15664             if (whiteFlag) {
15665                 if (blackFlag)
15666                   GameEnds(GameIsDrawn, "Both players ran out of time",
15667                            GE_PLAYER);
15668                 else
15669                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15670             } else {
15671                 DisplayError(_("Your opponent is not out of time"), 0);
15672             }
15673             break;
15674           case MachinePlaysBlack:
15675             if (blackFlag) {
15676                 if (whiteFlag)
15677                   GameEnds(GameIsDrawn, "Both players ran out of time",
15678                            GE_PLAYER);
15679                 else
15680                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15681             } else {
15682                 DisplayError(_("Your opponent is not out of time"), 0);
15683             }
15684             break;
15685         }
15686     }
15687 }
15688
15689 void
15690 ClockClick (int which)
15691 {       // [HGM] code moved to back-end from winboard.c
15692         if(which) { // black clock
15693           if (gameMode == EditPosition || gameMode == IcsExamining) {
15694             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15695             SetBlackToPlayEvent();
15696           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15697                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15698           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15699           } else if (shiftKey) {
15700             AdjustClock(which, -1);
15701           } else if (gameMode == IcsPlayingWhite ||
15702                      gameMode == MachinePlaysBlack) {
15703             CallFlagEvent();
15704           }
15705         } else { // white clock
15706           if (gameMode == EditPosition || gameMode == IcsExamining) {
15707             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15708             SetWhiteToPlayEvent();
15709           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15710                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15711           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15712           } else if (shiftKey) {
15713             AdjustClock(which, -1);
15714           } else if (gameMode == IcsPlayingBlack ||
15715                    gameMode == MachinePlaysWhite) {
15716             CallFlagEvent();
15717           }
15718         }
15719 }
15720
15721 void
15722 DrawEvent ()
15723 {
15724     /* Offer draw or accept pending draw offer from opponent */
15725
15726     if (appData.icsActive) {
15727         /* Note: tournament rules require draw offers to be
15728            made after you make your move but before you punch
15729            your clock.  Currently ICS doesn't let you do that;
15730            instead, you immediately punch your clock after making
15731            a move, but you can offer a draw at any time. */
15732
15733         SendToICS(ics_prefix);
15734         SendToICS("draw\n");
15735         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15736     } else if (cmailMsgLoaded) {
15737         if (currentMove == cmailOldMove &&
15738             commentList[cmailOldMove] != NULL &&
15739             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15740                    "Black offers a draw" : "White offers a draw")) {
15741             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15742             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15743         } else if (currentMove == cmailOldMove + 1) {
15744             char *offer = WhiteOnMove(cmailOldMove) ?
15745               "White offers a draw" : "Black offers a draw";
15746             AppendComment(currentMove, offer, TRUE);
15747             DisplayComment(currentMove - 1, offer);
15748             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15749         } else {
15750             DisplayError(_("You must make your move before offering a draw"), 0);
15751             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15752         }
15753     } else if (first.offeredDraw) {
15754         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15755     } else {
15756         if (first.sendDrawOffers) {
15757             SendToProgram("draw\n", &first);
15758             userOfferedDraw = TRUE;
15759         }
15760     }
15761 }
15762
15763 void
15764 AdjournEvent ()
15765 {
15766     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15767
15768     if (appData.icsActive) {
15769         SendToICS(ics_prefix);
15770         SendToICS("adjourn\n");
15771     } else {
15772         /* Currently GNU Chess doesn't offer or accept Adjourns */
15773     }
15774 }
15775
15776
15777 void
15778 AbortEvent ()
15779 {
15780     /* Offer Abort or accept pending Abort offer from opponent */
15781
15782     if (appData.icsActive) {
15783         SendToICS(ics_prefix);
15784         SendToICS("abort\n");
15785     } else {
15786         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15787     }
15788 }
15789
15790 void
15791 ResignEvent ()
15792 {
15793     /* Resign.  You can do this even if it's not your turn. */
15794
15795     if (appData.icsActive) {
15796         SendToICS(ics_prefix);
15797         SendToICS("resign\n");
15798     } else {
15799         switch (gameMode) {
15800           case MachinePlaysWhite:
15801             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15802             break;
15803           case MachinePlaysBlack:
15804             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15805             break;
15806           case EditGame:
15807             if (cmailMsgLoaded) {
15808                 TruncateGame();
15809                 if (WhiteOnMove(cmailOldMove)) {
15810                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15811                 } else {
15812                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15813                 }
15814                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15815             }
15816             break;
15817           default:
15818             break;
15819         }
15820     }
15821 }
15822
15823
15824 void
15825 StopObservingEvent ()
15826 {
15827     /* Stop observing current games */
15828     SendToICS(ics_prefix);
15829     SendToICS("unobserve\n");
15830 }
15831
15832 void
15833 StopExaminingEvent ()
15834 {
15835     /* Stop observing current game */
15836     SendToICS(ics_prefix);
15837     SendToICS("unexamine\n");
15838 }
15839
15840 void
15841 ForwardInner (int target)
15842 {
15843     int limit; int oldSeekGraphUp = seekGraphUp;
15844
15845     if (appData.debugMode)
15846         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15847                 target, currentMove, forwardMostMove);
15848
15849     if (gameMode == EditPosition)
15850       return;
15851
15852     seekGraphUp = FALSE;
15853     MarkTargetSquares(1);
15854     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15855
15856     if (gameMode == PlayFromGameFile && !pausing)
15857       PauseEvent();
15858
15859     if (gameMode == IcsExamining && pausing)
15860       limit = pauseExamForwardMostMove;
15861     else
15862       limit = forwardMostMove;
15863
15864     if (target > limit) target = limit;
15865
15866     if (target > 0 && moveList[target - 1][0]) {
15867         int fromX, fromY, toX, toY;
15868         toX = moveList[target - 1][2] - AAA;
15869         toY = moveList[target - 1][3] - ONE;
15870         if (moveList[target - 1][1] == '@') {
15871             if (appData.highlightLastMove) {
15872                 SetHighlights(-1, -1, toX, toY);
15873             }
15874         } else {
15875             fromX = moveList[target - 1][0] - AAA;
15876             fromY = moveList[target - 1][1] - ONE;
15877             if (target == currentMove + 1) {
15878                 if(moveList[target - 1][4] == ';') { // multi-leg
15879                     killX = moveList[target - 1][5] - AAA;
15880                     killY = moveList[target - 1][6] - ONE;
15881                 }
15882                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15883                 killX = killY = -1;
15884             }
15885             if (appData.highlightLastMove) {
15886                 SetHighlights(fromX, fromY, toX, toY);
15887             }
15888         }
15889     }
15890     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15891         gameMode == Training || gameMode == PlayFromGameFile ||
15892         gameMode == AnalyzeFile) {
15893         while (currentMove < target) {
15894             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15895             SendMoveToProgram(currentMove++, &first);
15896         }
15897     } else {
15898         currentMove = target;
15899     }
15900
15901     if (gameMode == EditGame || gameMode == EndOfGame) {
15902         whiteTimeRemaining = timeRemaining[0][currentMove];
15903         blackTimeRemaining = timeRemaining[1][currentMove];
15904     }
15905     DisplayBothClocks();
15906     DisplayMove(currentMove - 1);
15907     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15908     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15909     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15910         DisplayComment(currentMove - 1, commentList[currentMove]);
15911     }
15912     ClearMap(); // [HGM] exclude: invalidate map
15913 }
15914
15915
15916 void
15917 ForwardEvent ()
15918 {
15919     if (gameMode == IcsExamining && !pausing) {
15920         SendToICS(ics_prefix);
15921         SendToICS("forward\n");
15922     } else {
15923         ForwardInner(currentMove + 1);
15924     }
15925 }
15926
15927 void
15928 ToEndEvent ()
15929 {
15930     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15931         /* to optimze, we temporarily turn off analysis mode while we feed
15932          * the remaining moves to the engine. Otherwise we get analysis output
15933          * after each move.
15934          */
15935         if (first.analysisSupport) {
15936           SendToProgram("exit\nforce\n", &first);
15937           first.analyzing = FALSE;
15938         }
15939     }
15940
15941     if (gameMode == IcsExamining && !pausing) {
15942         SendToICS(ics_prefix);
15943         SendToICS("forward 999999\n");
15944     } else {
15945         ForwardInner(forwardMostMove);
15946     }
15947
15948     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15949         /* we have fed all the moves, so reactivate analysis mode */
15950         SendToProgram("analyze\n", &first);
15951         first.analyzing = TRUE;
15952         /*first.maybeThinking = TRUE;*/
15953         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15954     }
15955 }
15956
15957 void
15958 BackwardInner (int target)
15959 {
15960     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15961
15962     if (appData.debugMode)
15963         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15964                 target, currentMove, forwardMostMove);
15965
15966     if (gameMode == EditPosition) return;
15967     seekGraphUp = FALSE;
15968     MarkTargetSquares(1);
15969     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15970     if (currentMove <= backwardMostMove) {
15971         ClearHighlights();
15972         DrawPosition(full_redraw, boards[currentMove]);
15973         return;
15974     }
15975     if (gameMode == PlayFromGameFile && !pausing)
15976       PauseEvent();
15977
15978     if (moveList[target][0]) {
15979         int fromX, fromY, toX, toY;
15980         toX = moveList[target][2] - AAA;
15981         toY = moveList[target][3] - ONE;
15982         if (moveList[target][1] == '@') {
15983             if (appData.highlightLastMove) {
15984                 SetHighlights(-1, -1, toX, toY);
15985             }
15986         } else {
15987             fromX = moveList[target][0] - AAA;
15988             fromY = moveList[target][1] - ONE;
15989             if (target == currentMove - 1) {
15990                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15991             }
15992             if (appData.highlightLastMove) {
15993                 SetHighlights(fromX, fromY, toX, toY);
15994             }
15995         }
15996     }
15997     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15998         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15999         while (currentMove > target) {
16000             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16001                 // null move cannot be undone. Reload program with move history before it.
16002                 int i;
16003                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16004                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16005                 }
16006                 SendBoard(&first, i);
16007               if(second.analyzing) SendBoard(&second, i);
16008                 for(currentMove=i; currentMove<target; currentMove++) {
16009                     SendMoveToProgram(currentMove, &first);
16010                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16011                 }
16012                 break;
16013             }
16014             SendToBoth("undo\n");
16015             currentMove--;
16016         }
16017     } else {
16018         currentMove = target;
16019     }
16020
16021     if (gameMode == EditGame || gameMode == EndOfGame) {
16022         whiteTimeRemaining = timeRemaining[0][currentMove];
16023         blackTimeRemaining = timeRemaining[1][currentMove];
16024     }
16025     DisplayBothClocks();
16026     DisplayMove(currentMove - 1);
16027     DrawPosition(full_redraw, boards[currentMove]);
16028     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16029     // [HGM] PV info: routine tests if comment empty
16030     DisplayComment(currentMove - 1, commentList[currentMove]);
16031     ClearMap(); // [HGM] exclude: invalidate map
16032 }
16033
16034 void
16035 BackwardEvent ()
16036 {
16037     if (gameMode == IcsExamining && !pausing) {
16038         SendToICS(ics_prefix);
16039         SendToICS("backward\n");
16040     } else {
16041         BackwardInner(currentMove - 1);
16042     }
16043 }
16044
16045 void
16046 ToStartEvent ()
16047 {
16048     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16049         /* to optimize, we temporarily turn off analysis mode while we undo
16050          * all the moves. Otherwise we get analysis output after each undo.
16051          */
16052         if (first.analysisSupport) {
16053           SendToProgram("exit\nforce\n", &first);
16054           first.analyzing = FALSE;
16055         }
16056     }
16057
16058     if (gameMode == IcsExamining && !pausing) {
16059         SendToICS(ics_prefix);
16060         SendToICS("backward 999999\n");
16061     } else {
16062         BackwardInner(backwardMostMove);
16063     }
16064
16065     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16066         /* we have fed all the moves, so reactivate analysis mode */
16067         SendToProgram("analyze\n", &first);
16068         first.analyzing = TRUE;
16069         /*first.maybeThinking = TRUE;*/
16070         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16071     }
16072 }
16073
16074 void
16075 ToNrEvent (int to)
16076 {
16077   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16078   if (to >= forwardMostMove) to = forwardMostMove;
16079   if (to <= backwardMostMove) to = backwardMostMove;
16080   if (to < currentMove) {
16081     BackwardInner(to);
16082   } else {
16083     ForwardInner(to);
16084   }
16085 }
16086
16087 void
16088 RevertEvent (Boolean annotate)
16089 {
16090     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16091         return;
16092     }
16093     if (gameMode != IcsExamining) {
16094         DisplayError(_("You are not examining a game"), 0);
16095         return;
16096     }
16097     if (pausing) {
16098         DisplayError(_("You can't revert while pausing"), 0);
16099         return;
16100     }
16101     SendToICS(ics_prefix);
16102     SendToICS("revert\n");
16103 }
16104
16105 void
16106 RetractMoveEvent ()
16107 {
16108     switch (gameMode) {
16109       case MachinePlaysWhite:
16110       case MachinePlaysBlack:
16111         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16112             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16113             return;
16114         }
16115         if (forwardMostMove < 2) return;
16116         currentMove = forwardMostMove = forwardMostMove - 2;
16117         whiteTimeRemaining = timeRemaining[0][currentMove];
16118         blackTimeRemaining = timeRemaining[1][currentMove];
16119         DisplayBothClocks();
16120         DisplayMove(currentMove - 1);
16121         ClearHighlights();/*!! could figure this out*/
16122         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16123         SendToProgram("remove\n", &first);
16124         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16125         break;
16126
16127       case BeginningOfGame:
16128       default:
16129         break;
16130
16131       case IcsPlayingWhite:
16132       case IcsPlayingBlack:
16133         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16134             SendToICS(ics_prefix);
16135             SendToICS("takeback 2\n");
16136         } else {
16137             SendToICS(ics_prefix);
16138             SendToICS("takeback 1\n");
16139         }
16140         break;
16141     }
16142 }
16143
16144 void
16145 MoveNowEvent ()
16146 {
16147     ChessProgramState *cps;
16148
16149     switch (gameMode) {
16150       case MachinePlaysWhite:
16151         if (!WhiteOnMove(forwardMostMove)) {
16152             DisplayError(_("It is your turn"), 0);
16153             return;
16154         }
16155         cps = &first;
16156         break;
16157       case MachinePlaysBlack:
16158         if (WhiteOnMove(forwardMostMove)) {
16159             DisplayError(_("It is your turn"), 0);
16160             return;
16161         }
16162         cps = &first;
16163         break;
16164       case TwoMachinesPlay:
16165         if (WhiteOnMove(forwardMostMove) ==
16166             (first.twoMachinesColor[0] == 'w')) {
16167             cps = &first;
16168         } else {
16169             cps = &second;
16170         }
16171         break;
16172       case BeginningOfGame:
16173       default:
16174         return;
16175     }
16176     SendToProgram("?\n", cps);
16177 }
16178
16179 void
16180 TruncateGameEvent ()
16181 {
16182     EditGameEvent();
16183     if (gameMode != EditGame) return;
16184     TruncateGame();
16185 }
16186
16187 void
16188 TruncateGame ()
16189 {
16190     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16191     if (forwardMostMove > currentMove) {
16192         if (gameInfo.resultDetails != NULL) {
16193             free(gameInfo.resultDetails);
16194             gameInfo.resultDetails = NULL;
16195             gameInfo.result = GameUnfinished;
16196         }
16197         forwardMostMove = currentMove;
16198         HistorySet(parseList, backwardMostMove, forwardMostMove,
16199                    currentMove-1);
16200     }
16201 }
16202
16203 void
16204 HintEvent ()
16205 {
16206     if (appData.noChessProgram) return;
16207     switch (gameMode) {
16208       case MachinePlaysWhite:
16209         if (WhiteOnMove(forwardMostMove)) {
16210             DisplayError(_("Wait until your turn."), 0);
16211             return;
16212         }
16213         break;
16214       case BeginningOfGame:
16215       case MachinePlaysBlack:
16216         if (!WhiteOnMove(forwardMostMove)) {
16217             DisplayError(_("Wait until your turn."), 0);
16218             return;
16219         }
16220         break;
16221       default:
16222         DisplayError(_("No hint available"), 0);
16223         return;
16224     }
16225     SendToProgram("hint\n", &first);
16226     hintRequested = TRUE;
16227 }
16228
16229 int
16230 SaveSelected (FILE *g, int dummy, char *dummy2)
16231 {
16232     ListGame * lg = (ListGame *) gameList.head;
16233     int nItem, cnt=0;
16234     FILE *f;
16235
16236     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16237         DisplayError(_("Game list not loaded or empty"), 0);
16238         return 0;
16239     }
16240
16241     creatingBook = TRUE; // suppresses stuff during load game
16242
16243     /* Get list size */
16244     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16245         if(lg->position >= 0) { // selected?
16246             LoadGame(f, nItem, "", TRUE);
16247             SaveGamePGN2(g); // leaves g open
16248             cnt++; DoEvents();
16249         }
16250         lg = (ListGame *) lg->node.succ;
16251     }
16252
16253     fclose(g);
16254     creatingBook = FALSE;
16255
16256     return cnt;
16257 }
16258
16259 void
16260 CreateBookEvent ()
16261 {
16262     ListGame * lg = (ListGame *) gameList.head;
16263     FILE *f, *g;
16264     int nItem;
16265     static int secondTime = FALSE;
16266
16267     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16268         DisplayError(_("Game list not loaded or empty"), 0);
16269         return;
16270     }
16271
16272     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16273         fclose(g);
16274         secondTime++;
16275         DisplayNote(_("Book file exists! Try again for overwrite."));
16276         return;
16277     }
16278
16279     creatingBook = TRUE;
16280     secondTime = FALSE;
16281
16282     /* Get list size */
16283     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16284         if(lg->position >= 0) {
16285             LoadGame(f, nItem, "", TRUE);
16286             AddGameToBook(TRUE);
16287             DoEvents();
16288         }
16289         lg = (ListGame *) lg->node.succ;
16290     }
16291
16292     creatingBook = FALSE;
16293     FlushBook();
16294 }
16295
16296 void
16297 BookEvent ()
16298 {
16299     if (appData.noChessProgram) return;
16300     switch (gameMode) {
16301       case MachinePlaysWhite:
16302         if (WhiteOnMove(forwardMostMove)) {
16303             DisplayError(_("Wait until your turn."), 0);
16304             return;
16305         }
16306         break;
16307       case BeginningOfGame:
16308       case MachinePlaysBlack:
16309         if (!WhiteOnMove(forwardMostMove)) {
16310             DisplayError(_("Wait until your turn."), 0);
16311             return;
16312         }
16313         break;
16314       case EditPosition:
16315         EditPositionDone(TRUE);
16316         break;
16317       case TwoMachinesPlay:
16318         return;
16319       default:
16320         break;
16321     }
16322     SendToProgram("bk\n", &first);
16323     bookOutput[0] = NULLCHAR;
16324     bookRequested = TRUE;
16325 }
16326
16327 void
16328 AboutGameEvent ()
16329 {
16330     char *tags = PGNTags(&gameInfo);
16331     TagsPopUp(tags, CmailMsg());
16332     free(tags);
16333 }
16334
16335 /* end button procedures */
16336
16337 void
16338 PrintPosition (FILE *fp, int move)
16339 {
16340     int i, j;
16341
16342     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16343         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16344             char c = PieceToChar(boards[move][i][j]);
16345             fputc(c == '?' ? '.' : c, fp);
16346             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16347         }
16348     }
16349     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16350       fprintf(fp, "white to play\n");
16351     else
16352       fprintf(fp, "black to play\n");
16353 }
16354
16355 void
16356 PrintOpponents (FILE *fp)
16357 {
16358     if (gameInfo.white != NULL) {
16359         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16360     } else {
16361         fprintf(fp, "\n");
16362     }
16363 }
16364
16365 /* Find last component of program's own name, using some heuristics */
16366 void
16367 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16368 {
16369     char *p, *q, c;
16370     int local = (strcmp(host, "localhost") == 0);
16371     while (!local && (p = strchr(prog, ';')) != NULL) {
16372         p++;
16373         while (*p == ' ') p++;
16374         prog = p;
16375     }
16376     if (*prog == '"' || *prog == '\'') {
16377         q = strchr(prog + 1, *prog);
16378     } else {
16379         q = strchr(prog, ' ');
16380     }
16381     if (q == NULL) q = prog + strlen(prog);
16382     p = q;
16383     while (p >= prog && *p != '/' && *p != '\\') p--;
16384     p++;
16385     if(p == prog && *p == '"') p++;
16386     c = *q; *q = 0;
16387     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16388     memcpy(buf, p, q - p);
16389     buf[q - p] = NULLCHAR;
16390     if (!local) {
16391         strcat(buf, "@");
16392         strcat(buf, host);
16393     }
16394 }
16395
16396 char *
16397 TimeControlTagValue ()
16398 {
16399     char buf[MSG_SIZ];
16400     if (!appData.clockMode) {
16401       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16402     } else if (movesPerSession > 0) {
16403       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16404     } else if (timeIncrement == 0) {
16405       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16406     } else {
16407       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16408     }
16409     return StrSave(buf);
16410 }
16411
16412 void
16413 SetGameInfo ()
16414 {
16415     /* This routine is used only for certain modes */
16416     VariantClass v = gameInfo.variant;
16417     ChessMove r = GameUnfinished;
16418     char *p = NULL;
16419
16420     if(keepInfo) return;
16421
16422     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16423         r = gameInfo.result;
16424         p = gameInfo.resultDetails;
16425         gameInfo.resultDetails = NULL;
16426     }
16427     ClearGameInfo(&gameInfo);
16428     gameInfo.variant = v;
16429
16430     switch (gameMode) {
16431       case MachinePlaysWhite:
16432         gameInfo.event = StrSave( appData.pgnEventHeader );
16433         gameInfo.site = StrSave(HostName());
16434         gameInfo.date = PGNDate();
16435         gameInfo.round = StrSave("-");
16436         gameInfo.white = StrSave(first.tidy);
16437         gameInfo.black = StrSave(UserName());
16438         gameInfo.timeControl = TimeControlTagValue();
16439         break;
16440
16441       case MachinePlaysBlack:
16442         gameInfo.event = StrSave( appData.pgnEventHeader );
16443         gameInfo.site = StrSave(HostName());
16444         gameInfo.date = PGNDate();
16445         gameInfo.round = StrSave("-");
16446         gameInfo.white = StrSave(UserName());
16447         gameInfo.black = StrSave(first.tidy);
16448         gameInfo.timeControl = TimeControlTagValue();
16449         break;
16450
16451       case TwoMachinesPlay:
16452         gameInfo.event = StrSave( appData.pgnEventHeader );
16453         gameInfo.site = StrSave(HostName());
16454         gameInfo.date = PGNDate();
16455         if (roundNr > 0) {
16456             char buf[MSG_SIZ];
16457             snprintf(buf, MSG_SIZ, "%d", roundNr);
16458             gameInfo.round = StrSave(buf);
16459         } else {
16460             gameInfo.round = StrSave("-");
16461         }
16462         if (first.twoMachinesColor[0] == 'w') {
16463             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16464             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16465         } else {
16466             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16467             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16468         }
16469         gameInfo.timeControl = TimeControlTagValue();
16470         break;
16471
16472       case EditGame:
16473         gameInfo.event = StrSave("Edited game");
16474         gameInfo.site = StrSave(HostName());
16475         gameInfo.date = PGNDate();
16476         gameInfo.round = StrSave("-");
16477         gameInfo.white = StrSave("-");
16478         gameInfo.black = StrSave("-");
16479         gameInfo.result = r;
16480         gameInfo.resultDetails = p;
16481         break;
16482
16483       case EditPosition:
16484         gameInfo.event = StrSave("Edited position");
16485         gameInfo.site = StrSave(HostName());
16486         gameInfo.date = PGNDate();
16487         gameInfo.round = StrSave("-");
16488         gameInfo.white = StrSave("-");
16489         gameInfo.black = StrSave("-");
16490         break;
16491
16492       case IcsPlayingWhite:
16493       case IcsPlayingBlack:
16494       case IcsObserving:
16495       case IcsExamining:
16496         break;
16497
16498       case PlayFromGameFile:
16499         gameInfo.event = StrSave("Game from non-PGN file");
16500         gameInfo.site = StrSave(HostName());
16501         gameInfo.date = PGNDate();
16502         gameInfo.round = StrSave("-");
16503         gameInfo.white = StrSave("?");
16504         gameInfo.black = StrSave("?");
16505         break;
16506
16507       default:
16508         break;
16509     }
16510 }
16511
16512 void
16513 ReplaceComment (int index, char *text)
16514 {
16515     int len;
16516     char *p;
16517     float score;
16518
16519     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16520        pvInfoList[index-1].depth == len &&
16521        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16522        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16523     while (*text == '\n') text++;
16524     len = strlen(text);
16525     while (len > 0 && text[len - 1] == '\n') len--;
16526
16527     if (commentList[index] != NULL)
16528       free(commentList[index]);
16529
16530     if (len == 0) {
16531         commentList[index] = NULL;
16532         return;
16533     }
16534   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16535       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16536       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16537     commentList[index] = (char *) malloc(len + 2);
16538     strncpy(commentList[index], text, len);
16539     commentList[index][len] = '\n';
16540     commentList[index][len + 1] = NULLCHAR;
16541   } else {
16542     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16543     char *p;
16544     commentList[index] = (char *) malloc(len + 7);
16545     safeStrCpy(commentList[index], "{\n", 3);
16546     safeStrCpy(commentList[index]+2, text, len+1);
16547     commentList[index][len+2] = NULLCHAR;
16548     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16549     strcat(commentList[index], "\n}\n");
16550   }
16551 }
16552
16553 void
16554 CrushCRs (char *text)
16555 {
16556   char *p = text;
16557   char *q = text;
16558   char ch;
16559
16560   do {
16561     ch = *p++;
16562     if (ch == '\r') continue;
16563     *q++ = ch;
16564   } while (ch != '\0');
16565 }
16566
16567 void
16568 AppendComment (int index, char *text, Boolean addBraces)
16569 /* addBraces  tells if we should add {} */
16570 {
16571     int oldlen, len;
16572     char *old;
16573
16574 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16575     if(addBraces == 3) addBraces = 0; else // force appending literally
16576     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16577
16578     CrushCRs(text);
16579     while (*text == '\n') text++;
16580     len = strlen(text);
16581     while (len > 0 && text[len - 1] == '\n') len--;
16582     text[len] = NULLCHAR;
16583
16584     if (len == 0) return;
16585
16586     if (commentList[index] != NULL) {
16587       Boolean addClosingBrace = addBraces;
16588         old = commentList[index];
16589         oldlen = strlen(old);
16590         while(commentList[index][oldlen-1] ==  '\n')
16591           commentList[index][--oldlen] = NULLCHAR;
16592         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16593         safeStrCpy(commentList[index], old, oldlen + len + 6);
16594         free(old);
16595         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16596         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16597           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16598           while (*text == '\n') { text++; len--; }
16599           commentList[index][--oldlen] = NULLCHAR;
16600       }
16601         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16602         else          strcat(commentList[index], "\n");
16603         strcat(commentList[index], text);
16604         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16605         else          strcat(commentList[index], "\n");
16606     } else {
16607         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16608         if(addBraces)
16609           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16610         else commentList[index][0] = NULLCHAR;
16611         strcat(commentList[index], text);
16612         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16613         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16614     }
16615 }
16616
16617 static char *
16618 FindStr (char * text, char * sub_text)
16619 {
16620     char * result = strstr( text, sub_text );
16621
16622     if( result != NULL ) {
16623         result += strlen( sub_text );
16624     }
16625
16626     return result;
16627 }
16628
16629 /* [AS] Try to extract PV info from PGN comment */
16630 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16631 char *
16632 GetInfoFromComment (int index, char * text)
16633 {
16634     char * sep = text, *p;
16635
16636     if( text != NULL && index > 0 ) {
16637         int score = 0;
16638         int depth = 0;
16639         int time = -1, sec = 0, deci;
16640         char * s_eval = FindStr( text, "[%eval " );
16641         char * s_emt = FindStr( text, "[%emt " );
16642 #if 0
16643         if( s_eval != NULL || s_emt != NULL ) {
16644 #else
16645         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16646 #endif
16647             /* New style */
16648             char delim;
16649
16650             if( s_eval != NULL ) {
16651                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16652                     return text;
16653                 }
16654
16655                 if( delim != ']' ) {
16656                     return text;
16657                 }
16658             }
16659
16660             if( s_emt != NULL ) {
16661             }
16662                 return text;
16663         }
16664         else {
16665             /* We expect something like: [+|-]nnn.nn/dd */
16666             int score_lo = 0;
16667
16668             if(*text != '{') return text; // [HGM] braces: must be normal comment
16669
16670             sep = strchr( text, '/' );
16671             if( sep == NULL || sep < (text+4) ) {
16672                 return text;
16673             }
16674
16675             p = text;
16676             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16677             if(p[1] == '(') { // comment starts with PV
16678                p = strchr(p, ')'); // locate end of PV
16679                if(p == NULL || sep < p+5) return text;
16680                // at this point we have something like "{(.*) +0.23/6 ..."
16681                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16682                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16683                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16684             }
16685             time = -1; sec = -1; deci = -1;
16686             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16687                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16688                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16689                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16690                 return text;
16691             }
16692
16693             if( score_lo < 0 || score_lo >= 100 ) {
16694                 return text;
16695             }
16696
16697             if(sec >= 0) time = 600*time + 10*sec; else
16698             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16699
16700             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16701
16702             /* [HGM] PV time: now locate end of PV info */
16703             while( *++sep >= '0' && *sep <= '9'); // strip depth
16704             if(time >= 0)
16705             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16706             if(sec >= 0)
16707             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16708             if(deci >= 0)
16709             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16710             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16711         }
16712
16713         if( depth <= 0 ) {
16714             return text;
16715         }
16716
16717         if( time < 0 ) {
16718             time = -1;
16719         }
16720
16721         pvInfoList[index-1].depth = depth;
16722         pvInfoList[index-1].score = score;
16723         pvInfoList[index-1].time  = 10*time; // centi-sec
16724         if(*sep == '}') *sep = 0; else *--sep = '{';
16725         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16726     }
16727     return sep;
16728 }
16729
16730 void
16731 SendToProgram (char *message, ChessProgramState *cps)
16732 {
16733     int count, outCount, error;
16734     char buf[MSG_SIZ];
16735
16736     if (cps->pr == NoProc) return;
16737     Attention(cps);
16738
16739     if (appData.debugMode) {
16740         TimeMark now;
16741         GetTimeMark(&now);
16742         fprintf(debugFP, "%ld >%-6s: %s",
16743                 SubtractTimeMarks(&now, &programStartTime),
16744                 cps->which, message);
16745         if(serverFP)
16746             fprintf(serverFP, "%ld >%-6s: %s",
16747                 SubtractTimeMarks(&now, &programStartTime),
16748                 cps->which, message), fflush(serverFP);
16749     }
16750
16751     count = strlen(message);
16752     outCount = OutputToProcess(cps->pr, message, count, &error);
16753     if (outCount < count && !exiting
16754                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16755       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16756       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16757         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16758             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16759                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16760                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16761                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16762             } else {
16763                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16764                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16765                 gameInfo.result = res;
16766             }
16767             gameInfo.resultDetails = StrSave(buf);
16768         }
16769         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16770         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16771     }
16772 }
16773
16774 void
16775 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16776 {
16777     char *end_str;
16778     char buf[MSG_SIZ];
16779     ChessProgramState *cps = (ChessProgramState *)closure;
16780
16781     if (isr != cps->isr) return; /* Killed intentionally */
16782     if (count <= 0) {
16783         if (count == 0) {
16784             RemoveInputSource(cps->isr);
16785             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16786                     _(cps->which), cps->program);
16787             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16788             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16789                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16790                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16791                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16792                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16793                 } else {
16794                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16795                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16796                     gameInfo.result = res;
16797                 }
16798                 gameInfo.resultDetails = StrSave(buf);
16799             }
16800             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16801             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16802         } else {
16803             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16804                     _(cps->which), cps->program);
16805             RemoveInputSource(cps->isr);
16806
16807             /* [AS] Program is misbehaving badly... kill it */
16808             if( count == -2 ) {
16809                 DestroyChildProcess( cps->pr, 9 );
16810                 cps->pr = NoProc;
16811             }
16812
16813             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16814         }
16815         return;
16816     }
16817
16818     if ((end_str = strchr(message, '\r')) != NULL)
16819       *end_str = NULLCHAR;
16820     if ((end_str = strchr(message, '\n')) != NULL)
16821       *end_str = NULLCHAR;
16822
16823     if (appData.debugMode) {
16824         TimeMark now; int print = 1;
16825         char *quote = ""; char c; int i;
16826
16827         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16828                 char start = message[0];
16829                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16830                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16831                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16832                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16833                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16834                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16835                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16836                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16837                    sscanf(message, "hint: %c", &c)!=1 &&
16838                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16839                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16840                     print = (appData.engineComments >= 2);
16841                 }
16842                 message[0] = start; // restore original message
16843         }
16844         if(print) {
16845                 GetTimeMark(&now);
16846                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16847                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16848                         quote,
16849                         message);
16850                 if(serverFP)
16851                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16852                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16853                         quote,
16854                         message), fflush(serverFP);
16855         }
16856     }
16857
16858     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16859     if (appData.icsEngineAnalyze) {
16860         if (strstr(message, "whisper") != NULL ||
16861              strstr(message, "kibitz") != NULL ||
16862             strstr(message, "tellics") != NULL) return;
16863     }
16864
16865     HandleMachineMove(message, cps);
16866 }
16867
16868
16869 void
16870 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16871 {
16872     char buf[MSG_SIZ];
16873     int seconds;
16874
16875     if( timeControl_2 > 0 ) {
16876         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16877             tc = timeControl_2;
16878         }
16879     }
16880     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16881     inc /= cps->timeOdds;
16882     st  /= cps->timeOdds;
16883
16884     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16885
16886     if (st > 0) {
16887       /* Set exact time per move, normally using st command */
16888       if (cps->stKludge) {
16889         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16890         seconds = st % 60;
16891         if (seconds == 0) {
16892           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16893         } else {
16894           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16895         }
16896       } else {
16897         snprintf(buf, MSG_SIZ, "st %d\n", st);
16898       }
16899     } else {
16900       /* Set conventional or incremental time control, using level command */
16901       if (seconds == 0) {
16902         /* Note old gnuchess bug -- minutes:seconds used to not work.
16903            Fixed in later versions, but still avoid :seconds
16904            when seconds is 0. */
16905         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16906       } else {
16907         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16908                  seconds, inc/1000.);
16909       }
16910     }
16911     SendToProgram(buf, cps);
16912
16913     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16914     /* Orthogonally, limit search to given depth */
16915     if (sd > 0) {
16916       if (cps->sdKludge) {
16917         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16918       } else {
16919         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16920       }
16921       SendToProgram(buf, cps);
16922     }
16923
16924     if(cps->nps >= 0) { /* [HGM] nps */
16925         if(cps->supportsNPS == FALSE)
16926           cps->nps = -1; // don't use if engine explicitly says not supported!
16927         else {
16928           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16929           SendToProgram(buf, cps);
16930         }
16931     }
16932 }
16933
16934 ChessProgramState *
16935 WhitePlayer ()
16936 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16937 {
16938     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16939        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16940         return &second;
16941     return &first;
16942 }
16943
16944 void
16945 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16946 {
16947     char message[MSG_SIZ];
16948     long time, otime;
16949
16950     /* Note: this routine must be called when the clocks are stopped
16951        or when they have *just* been set or switched; otherwise
16952        it will be off by the time since the current tick started.
16953     */
16954     if (machineWhite) {
16955         time = whiteTimeRemaining / 10;
16956         otime = blackTimeRemaining / 10;
16957     } else {
16958         time = blackTimeRemaining / 10;
16959         otime = whiteTimeRemaining / 10;
16960     }
16961     /* [HGM] translate opponent's time by time-odds factor */
16962     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16963
16964     if (time <= 0) time = 1;
16965     if (otime <= 0) otime = 1;
16966
16967     snprintf(message, MSG_SIZ, "time %ld\n", time);
16968     SendToProgram(message, cps);
16969
16970     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16971     SendToProgram(message, cps);
16972 }
16973
16974 char *
16975 EngineDefinedVariant (ChessProgramState *cps, int n)
16976 {   // return name of n-th unknown variant that engine supports
16977     static char buf[MSG_SIZ];
16978     char *p, *s = cps->variants;
16979     if(!s) return NULL;
16980     do { // parse string from variants feature
16981       VariantClass v;
16982         p = strchr(s, ',');
16983         if(p) *p = NULLCHAR;
16984       v = StringToVariant(s);
16985       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16986         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16987             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16988                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16989                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16990                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16991             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16992         }
16993         if(p) *p++ = ',';
16994         if(n < 0) return buf;
16995     } while(s = p);
16996     return NULL;
16997 }
16998
16999 int
17000 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17001 {
17002   char buf[MSG_SIZ];
17003   int len = strlen(name);
17004   int val;
17005
17006   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17007     (*p) += len + 1;
17008     sscanf(*p, "%d", &val);
17009     *loc = (val != 0);
17010     while (**p && **p != ' ')
17011       (*p)++;
17012     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17013     SendToProgram(buf, cps);
17014     return TRUE;
17015   }
17016   return FALSE;
17017 }
17018
17019 int
17020 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17021 {
17022   char buf[MSG_SIZ];
17023   int len = strlen(name);
17024   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17025     (*p) += len + 1;
17026     sscanf(*p, "%d", loc);
17027     while (**p && **p != ' ') (*p)++;
17028     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17029     SendToProgram(buf, cps);
17030     return TRUE;
17031   }
17032   return FALSE;
17033 }
17034
17035 int
17036 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17037 {
17038   char buf[MSG_SIZ];
17039   int len = strlen(name);
17040   if (strncmp((*p), name, len) == 0
17041       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17042     (*p) += len + 2;
17043     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17044     sscanf(*p, "%[^\"]", *loc);
17045     while (**p && **p != '\"') (*p)++;
17046     if (**p == '\"') (*p)++;
17047     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17048     SendToProgram(buf, cps);
17049     return TRUE;
17050   }
17051   return FALSE;
17052 }
17053
17054 int
17055 ParseOption (Option *opt, ChessProgramState *cps)
17056 // [HGM] options: process the string that defines an engine option, and determine
17057 // name, type, default value, and allowed value range
17058 {
17059         char *p, *q, buf[MSG_SIZ];
17060         int n, min = (-1)<<31, max = 1<<31, def;
17061
17062         opt->target = &opt->value;   // OK for spin/slider and checkbox
17063         if(p = strstr(opt->name, " -spin ")) {
17064             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17065             if(max < min) max = min; // enforce consistency
17066             if(def < min) def = min;
17067             if(def > max) def = max;
17068             opt->value = def;
17069             opt->min = min;
17070             opt->max = max;
17071             opt->type = Spin;
17072         } else if((p = strstr(opt->name, " -slider "))) {
17073             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17074             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17075             if(max < min) max = min; // enforce consistency
17076             if(def < min) def = min;
17077             if(def > max) def = max;
17078             opt->value = def;
17079             opt->min = min;
17080             opt->max = max;
17081             opt->type = Spin; // Slider;
17082         } else if((p = strstr(opt->name, " -string "))) {
17083             opt->textValue = p+9;
17084             opt->type = TextBox;
17085             opt->target = &opt->textValue;
17086         } else if((p = strstr(opt->name, " -file "))) {
17087             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17088             opt->target = opt->textValue = p+7;
17089             opt->type = FileName; // FileName;
17090             opt->target = &opt->textValue;
17091         } else if((p = strstr(opt->name, " -path "))) {
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 = PathName; // PathName;
17095             opt->target = &opt->textValue;
17096         } else if(p = strstr(opt->name, " -check ")) {
17097             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17098             opt->value = (def != 0);
17099             opt->type = CheckBox;
17100         } else if(p = strstr(opt->name, " -combo ")) {
17101             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17102             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17103             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17104             opt->value = n = 0;
17105             while(q = StrStr(q, " /// ")) {
17106                 n++; *q = 0;    // count choices, and null-terminate each of them
17107                 q += 5;
17108                 if(*q == '*') { // remember default, which is marked with * prefix
17109                     q++;
17110                     opt->value = n;
17111                 }
17112                 cps->comboList[cps->comboCnt++] = q;
17113             }
17114             cps->comboList[cps->comboCnt++] = NULL;
17115             opt->max = n + 1;
17116             opt->type = ComboBox;
17117         } else if(p = strstr(opt->name, " -button")) {
17118             opt->type = Button;
17119         } else if(p = strstr(opt->name, " -save")) {
17120             opt->type = SaveButton;
17121         } else return FALSE;
17122         *p = 0; // terminate option name
17123         // now look if the command-line options define a setting for this engine option.
17124         if(cps->optionSettings && cps->optionSettings[0])
17125             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17126         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17127           snprintf(buf, MSG_SIZ, "option %s", p);
17128                 if(p = strstr(buf, ",")) *p = 0;
17129                 if(q = strchr(buf, '=')) switch(opt->type) {
17130                     case ComboBox:
17131                         for(n=0; n<opt->max; n++)
17132                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17133                         break;
17134                     case TextBox:
17135                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17136                         break;
17137                     case Spin:
17138                     case CheckBox:
17139                         opt->value = atoi(q+1);
17140                     default:
17141                         break;
17142                 }
17143                 strcat(buf, "\n");
17144                 SendToProgram(buf, cps);
17145         }
17146         return TRUE;
17147 }
17148
17149 void
17150 FeatureDone (ChessProgramState *cps, int val)
17151 {
17152   DelayedEventCallback cb = GetDelayedEvent();
17153   if ((cb == InitBackEnd3 && cps == &first) ||
17154       (cb == SettingsMenuIfReady && cps == &second) ||
17155       (cb == LoadEngine) ||
17156       (cb == TwoMachinesEventIfReady)) {
17157     CancelDelayedEvent();
17158     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17159   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17160   cps->initDone = val;
17161   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17162 }
17163
17164 /* Parse feature command from engine */
17165 void
17166 ParseFeatures (char *args, ChessProgramState *cps)
17167 {
17168   char *p = args;
17169   char *q = NULL;
17170   int val;
17171   char buf[MSG_SIZ];
17172
17173   for (;;) {
17174     while (*p == ' ') p++;
17175     if (*p == NULLCHAR) return;
17176
17177     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17178     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17179     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17180     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17181     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17182     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17183     if (BoolFeature(&p, "reuse", &val, cps)) {
17184       /* Engine can disable reuse, but can't enable it if user said no */
17185       if (!val) cps->reuse = FALSE;
17186       continue;
17187     }
17188     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17189     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17190       if (gameMode == TwoMachinesPlay) {
17191         DisplayTwoMachinesTitle();
17192       } else {
17193         DisplayTitle("");
17194       }
17195       continue;
17196     }
17197     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17198     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17199     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17200     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17201     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17202     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17203     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17204     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17205     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17206     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17207     if (IntFeature(&p, "done", &val, cps)) {
17208       FeatureDone(cps, val);
17209       continue;
17210     }
17211     /* Added by Tord: */
17212     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17213     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17214     /* End of additions by Tord */
17215
17216     /* [HGM] added features: */
17217     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17218     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17219     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17220     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17221     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17222     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17223     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17224     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17225         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17226         FREE(cps->option[cps->nrOptions].name);
17227         cps->option[cps->nrOptions].name = q; q = NULL;
17228         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17229           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17230             SendToProgram(buf, cps);
17231             continue;
17232         }
17233         if(cps->nrOptions >= MAX_OPTIONS) {
17234             cps->nrOptions--;
17235             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17236             DisplayError(buf, 0);
17237         }
17238         continue;
17239     }
17240     /* End of additions by HGM */
17241
17242     /* unknown feature: complain and skip */
17243     q = p;
17244     while (*q && *q != '=') q++;
17245     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17246     SendToProgram(buf, cps);
17247     p = q;
17248     if (*p == '=') {
17249       p++;
17250       if (*p == '\"') {
17251         p++;
17252         while (*p && *p != '\"') p++;
17253         if (*p == '\"') p++;
17254       } else {
17255         while (*p && *p != ' ') p++;
17256       }
17257     }
17258   }
17259
17260 }
17261
17262 void
17263 PeriodicUpdatesEvent (int newState)
17264 {
17265     if (newState == appData.periodicUpdates)
17266       return;
17267
17268     appData.periodicUpdates=newState;
17269
17270     /* Display type changes, so update it now */
17271 //    DisplayAnalysis();
17272
17273     /* Get the ball rolling again... */
17274     if (newState) {
17275         AnalysisPeriodicEvent(1);
17276         StartAnalysisClock();
17277     }
17278 }
17279
17280 void
17281 PonderNextMoveEvent (int newState)
17282 {
17283     if (newState == appData.ponderNextMove) return;
17284     if (gameMode == EditPosition) EditPositionDone(TRUE);
17285     if (newState) {
17286         SendToProgram("hard\n", &first);
17287         if (gameMode == TwoMachinesPlay) {
17288             SendToProgram("hard\n", &second);
17289         }
17290     } else {
17291         SendToProgram("easy\n", &first);
17292         thinkOutput[0] = NULLCHAR;
17293         if (gameMode == TwoMachinesPlay) {
17294             SendToProgram("easy\n", &second);
17295         }
17296     }
17297     appData.ponderNextMove = newState;
17298 }
17299
17300 void
17301 NewSettingEvent (int option, int *feature, char *command, int value)
17302 {
17303     char buf[MSG_SIZ];
17304
17305     if (gameMode == EditPosition) EditPositionDone(TRUE);
17306     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17307     if(feature == NULL || *feature) SendToProgram(buf, &first);
17308     if (gameMode == TwoMachinesPlay) {
17309         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17310     }
17311 }
17312
17313 void
17314 ShowThinkingEvent ()
17315 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17316 {
17317     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17318     int newState = appData.showThinking
17319         // [HGM] thinking: other features now need thinking output as well
17320         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17321
17322     if (oldState == newState) return;
17323     oldState = newState;
17324     if (gameMode == EditPosition) EditPositionDone(TRUE);
17325     if (oldState) {
17326         SendToProgram("post\n", &first);
17327         if (gameMode == TwoMachinesPlay) {
17328             SendToProgram("post\n", &second);
17329         }
17330     } else {
17331         SendToProgram("nopost\n", &first);
17332         thinkOutput[0] = NULLCHAR;
17333         if (gameMode == TwoMachinesPlay) {
17334             SendToProgram("nopost\n", &second);
17335         }
17336     }
17337 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17338 }
17339
17340 void
17341 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17342 {
17343   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17344   if (pr == NoProc) return;
17345   AskQuestion(title, question, replyPrefix, pr);
17346 }
17347
17348 void
17349 TypeInEvent (char firstChar)
17350 {
17351     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17352         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17353         gameMode == AnalyzeMode || gameMode == EditGame ||
17354         gameMode == EditPosition || gameMode == IcsExamining ||
17355         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17356         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17357                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17358                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17359         gameMode == Training) PopUpMoveDialog(firstChar);
17360 }
17361
17362 void
17363 TypeInDoneEvent (char *move)
17364 {
17365         Board board;
17366         int n, fromX, fromY, toX, toY;
17367         char promoChar;
17368         ChessMove moveType;
17369
17370         // [HGM] FENedit
17371         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17372                 EditPositionPasteFEN(move);
17373                 return;
17374         }
17375         // [HGM] movenum: allow move number to be typed in any mode
17376         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17377           ToNrEvent(2*n-1);
17378           return;
17379         }
17380         // undocumented kludge: allow command-line option to be typed in!
17381         // (potentially fatal, and does not implement the effect of the option.)
17382         // should only be used for options that are values on which future decisions will be made,
17383         // and definitely not on options that would be used during initialization.
17384         if(strstr(move, "!!! -") == move) {
17385             ParseArgsFromString(move+4);
17386             return;
17387         }
17388
17389       if (gameMode != EditGame && currentMove != forwardMostMove &&
17390         gameMode != Training) {
17391         DisplayMoveError(_("Displayed move is not current"));
17392       } else {
17393         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17394           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17395         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17396         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17397           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17398           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17399         } else {
17400           DisplayMoveError(_("Could not parse move"));
17401         }
17402       }
17403 }
17404
17405 void
17406 DisplayMove (int moveNumber)
17407 {
17408     char message[MSG_SIZ];
17409     char res[MSG_SIZ];
17410     char cpThinkOutput[MSG_SIZ];
17411
17412     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17413
17414     if (moveNumber == forwardMostMove - 1 ||
17415         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17416
17417         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17418
17419         if (strchr(cpThinkOutput, '\n')) {
17420             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17421         }
17422     } else {
17423         *cpThinkOutput = NULLCHAR;
17424     }
17425
17426     /* [AS] Hide thinking from human user */
17427     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17428         *cpThinkOutput = NULLCHAR;
17429         if( thinkOutput[0] != NULLCHAR ) {
17430             int i;
17431
17432             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17433                 cpThinkOutput[i] = '.';
17434             }
17435             cpThinkOutput[i] = NULLCHAR;
17436             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17437         }
17438     }
17439
17440     if (moveNumber == forwardMostMove - 1 &&
17441         gameInfo.resultDetails != NULL) {
17442         if (gameInfo.resultDetails[0] == NULLCHAR) {
17443           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17444         } else {
17445           snprintf(res, MSG_SIZ, " {%s} %s",
17446                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17447         }
17448     } else {
17449         res[0] = NULLCHAR;
17450     }
17451
17452     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17453         DisplayMessage(res, cpThinkOutput);
17454     } else {
17455       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17456                 WhiteOnMove(moveNumber) ? " " : ".. ",
17457                 parseList[moveNumber], res);
17458         DisplayMessage(message, cpThinkOutput);
17459     }
17460 }
17461
17462 void
17463 DisplayComment (int moveNumber, char *text)
17464 {
17465     char title[MSG_SIZ];
17466
17467     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17468       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17469     } else {
17470       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17471               WhiteOnMove(moveNumber) ? " " : ".. ",
17472               parseList[moveNumber]);
17473     }
17474     if (text != NULL && (appData.autoDisplayComment || commentUp))
17475         CommentPopUp(title, text);
17476 }
17477
17478 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17479  * might be busy thinking or pondering.  It can be omitted if your
17480  * gnuchess is configured to stop thinking immediately on any user
17481  * input.  However, that gnuchess feature depends on the FIONREAD
17482  * ioctl, which does not work properly on some flavors of Unix.
17483  */
17484 void
17485 Attention (ChessProgramState *cps)
17486 {
17487 #if ATTENTION
17488     if (!cps->useSigint) return;
17489     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17490     switch (gameMode) {
17491       case MachinePlaysWhite:
17492       case MachinePlaysBlack:
17493       case TwoMachinesPlay:
17494       case IcsPlayingWhite:
17495       case IcsPlayingBlack:
17496       case AnalyzeMode:
17497       case AnalyzeFile:
17498         /* Skip if we know it isn't thinking */
17499         if (!cps->maybeThinking) return;
17500         if (appData.debugMode)
17501           fprintf(debugFP, "Interrupting %s\n", cps->which);
17502         InterruptChildProcess(cps->pr);
17503         cps->maybeThinking = FALSE;
17504         break;
17505       default:
17506         break;
17507     }
17508 #endif /*ATTENTION*/
17509 }
17510
17511 int
17512 CheckFlags ()
17513 {
17514     if (whiteTimeRemaining <= 0) {
17515         if (!whiteFlag) {
17516             whiteFlag = TRUE;
17517             if (appData.icsActive) {
17518                 if (appData.autoCallFlag &&
17519                     gameMode == IcsPlayingBlack && !blackFlag) {
17520                   SendToICS(ics_prefix);
17521                   SendToICS("flag\n");
17522                 }
17523             } else {
17524                 if (blackFlag) {
17525                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17526                 } else {
17527                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17528                     if (appData.autoCallFlag) {
17529                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17530                         return TRUE;
17531                     }
17532                 }
17533             }
17534         }
17535     }
17536     if (blackTimeRemaining <= 0) {
17537         if (!blackFlag) {
17538             blackFlag = TRUE;
17539             if (appData.icsActive) {
17540                 if (appData.autoCallFlag &&
17541                     gameMode == IcsPlayingWhite && !whiteFlag) {
17542                   SendToICS(ics_prefix);
17543                   SendToICS("flag\n");
17544                 }
17545             } else {
17546                 if (whiteFlag) {
17547                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17548                 } else {
17549                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17550                     if (appData.autoCallFlag) {
17551                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17552                         return TRUE;
17553                     }
17554                 }
17555             }
17556         }
17557     }
17558     return FALSE;
17559 }
17560
17561 void
17562 CheckTimeControl ()
17563 {
17564     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17565         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17566
17567     /*
17568      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17569      */
17570     if ( !WhiteOnMove(forwardMostMove) ) {
17571         /* White made time control */
17572         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17573         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17574         /* [HGM] time odds: correct new time quota for time odds! */
17575                                             / WhitePlayer()->timeOdds;
17576         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17577     } else {
17578         lastBlack -= blackTimeRemaining;
17579         /* Black made time control */
17580         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17581                                             / WhitePlayer()->other->timeOdds;
17582         lastWhite = whiteTimeRemaining;
17583     }
17584 }
17585
17586 void
17587 DisplayBothClocks ()
17588 {
17589     int wom = gameMode == EditPosition ?
17590       !blackPlaysFirst : WhiteOnMove(currentMove);
17591     DisplayWhiteClock(whiteTimeRemaining, wom);
17592     DisplayBlackClock(blackTimeRemaining, !wom);
17593 }
17594
17595
17596 /* Timekeeping seems to be a portability nightmare.  I think everyone
17597    has ftime(), but I'm really not sure, so I'm including some ifdefs
17598    to use other calls if you don't.  Clocks will be less accurate if
17599    you have neither ftime nor gettimeofday.
17600 */
17601
17602 /* VS 2008 requires the #include outside of the function */
17603 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17604 #include <sys/timeb.h>
17605 #endif
17606
17607 /* Get the current time as a TimeMark */
17608 void
17609 GetTimeMark (TimeMark *tm)
17610 {
17611 #if HAVE_GETTIMEOFDAY
17612
17613     struct timeval timeVal;
17614     struct timezone timeZone;
17615
17616     gettimeofday(&timeVal, &timeZone);
17617     tm->sec = (long) timeVal.tv_sec;
17618     tm->ms = (int) (timeVal.tv_usec / 1000L);
17619
17620 #else /*!HAVE_GETTIMEOFDAY*/
17621 #if HAVE_FTIME
17622
17623 // include <sys/timeb.h> / moved to just above start of function
17624     struct timeb timeB;
17625
17626     ftime(&timeB);
17627     tm->sec = (long) timeB.time;
17628     tm->ms = (int) timeB.millitm;
17629
17630 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17631     tm->sec = (long) time(NULL);
17632     tm->ms = 0;
17633 #endif
17634 #endif
17635 }
17636
17637 /* Return the difference in milliseconds between two
17638    time marks.  We assume the difference will fit in a long!
17639 */
17640 long
17641 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17642 {
17643     return 1000L*(tm2->sec - tm1->sec) +
17644            (long) (tm2->ms - tm1->ms);
17645 }
17646
17647
17648 /*
17649  * Code to manage the game clocks.
17650  *
17651  * In tournament play, black starts the clock and then white makes a move.
17652  * We give the human user a slight advantage if he is playing white---the
17653  * clocks don't run until he makes his first move, so it takes zero time.
17654  * Also, we don't account for network lag, so we could get out of sync
17655  * with GNU Chess's clock -- but then, referees are always right.
17656  */
17657
17658 static TimeMark tickStartTM;
17659 static long intendedTickLength;
17660
17661 long
17662 NextTickLength (long timeRemaining)
17663 {
17664     long nominalTickLength, nextTickLength;
17665
17666     if (timeRemaining > 0L && timeRemaining <= 10000L)
17667       nominalTickLength = 100L;
17668     else
17669       nominalTickLength = 1000L;
17670     nextTickLength = timeRemaining % nominalTickLength;
17671     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17672
17673     return nextTickLength;
17674 }
17675
17676 /* Adjust clock one minute up or down */
17677 void
17678 AdjustClock (Boolean which, int dir)
17679 {
17680     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17681     if(which) blackTimeRemaining += 60000*dir;
17682     else      whiteTimeRemaining += 60000*dir;
17683     DisplayBothClocks();
17684     adjustedClock = TRUE;
17685 }
17686
17687 /* Stop clocks and reset to a fresh time control */
17688 void
17689 ResetClocks ()
17690 {
17691     (void) StopClockTimer();
17692     if (appData.icsActive) {
17693         whiteTimeRemaining = blackTimeRemaining = 0;
17694     } else if (searchTime) {
17695         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17696         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17697     } else { /* [HGM] correct new time quote for time odds */
17698         whiteTC = blackTC = fullTimeControlString;
17699         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17700         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17701     }
17702     if (whiteFlag || blackFlag) {
17703         DisplayTitle("");
17704         whiteFlag = blackFlag = FALSE;
17705     }
17706     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17707     DisplayBothClocks();
17708     adjustedClock = FALSE;
17709 }
17710
17711 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17712
17713 /* Decrement running clock by amount of time that has passed */
17714 void
17715 DecrementClocks ()
17716 {
17717     long timeRemaining;
17718     long lastTickLength, fudge;
17719     TimeMark now;
17720
17721     if (!appData.clockMode) return;
17722     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17723
17724     GetTimeMark(&now);
17725
17726     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17727
17728     /* Fudge if we woke up a little too soon */
17729     fudge = intendedTickLength - lastTickLength;
17730     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17731
17732     if (WhiteOnMove(forwardMostMove)) {
17733         if(whiteNPS >= 0) lastTickLength = 0;
17734         timeRemaining = whiteTimeRemaining -= lastTickLength;
17735         if(timeRemaining < 0 && !appData.icsActive) {
17736             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17737             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17738                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17739                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17740             }
17741         }
17742         DisplayWhiteClock(whiteTimeRemaining - fudge,
17743                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17744     } else {
17745         if(blackNPS >= 0) lastTickLength = 0;
17746         timeRemaining = blackTimeRemaining -= lastTickLength;
17747         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17748             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17749             if(suddenDeath) {
17750                 blackStartMove = forwardMostMove;
17751                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17752             }
17753         }
17754         DisplayBlackClock(blackTimeRemaining - fudge,
17755                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17756     }
17757     if (CheckFlags()) return;
17758
17759     if(twoBoards) { // count down secondary board's clocks as well
17760         activePartnerTime -= lastTickLength;
17761         partnerUp = 1;
17762         if(activePartner == 'W')
17763             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17764         else
17765             DisplayBlackClock(activePartnerTime, TRUE);
17766         partnerUp = 0;
17767     }
17768
17769     tickStartTM = now;
17770     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17771     StartClockTimer(intendedTickLength);
17772
17773     /* if the time remaining has fallen below the alarm threshold, sound the
17774      * alarm. if the alarm has sounded and (due to a takeback or time control
17775      * with increment) the time remaining has increased to a level above the
17776      * threshold, reset the alarm so it can sound again.
17777      */
17778
17779     if (appData.icsActive && appData.icsAlarm) {
17780
17781         /* make sure we are dealing with the user's clock */
17782         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17783                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17784            )) return;
17785
17786         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17787             alarmSounded = FALSE;
17788         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17789             PlayAlarmSound();
17790             alarmSounded = TRUE;
17791         }
17792     }
17793 }
17794
17795
17796 /* A player has just moved, so stop the previously running
17797    clock and (if in clock mode) start the other one.
17798    We redisplay both clocks in case we're in ICS mode, because
17799    ICS gives us an update to both clocks after every move.
17800    Note that this routine is called *after* forwardMostMove
17801    is updated, so the last fractional tick must be subtracted
17802    from the color that is *not* on move now.
17803 */
17804 void
17805 SwitchClocks (int newMoveNr)
17806 {
17807     long lastTickLength;
17808     TimeMark now;
17809     int flagged = FALSE;
17810
17811     GetTimeMark(&now);
17812
17813     if (StopClockTimer() && appData.clockMode) {
17814         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17815         if (!WhiteOnMove(forwardMostMove)) {
17816             if(blackNPS >= 0) lastTickLength = 0;
17817             blackTimeRemaining -= lastTickLength;
17818            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17819 //         if(pvInfoList[forwardMostMove].time == -1)
17820                  pvInfoList[forwardMostMove].time =               // use GUI time
17821                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17822         } else {
17823            if(whiteNPS >= 0) lastTickLength = 0;
17824            whiteTimeRemaining -= lastTickLength;
17825            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17826 //         if(pvInfoList[forwardMostMove].time == -1)
17827                  pvInfoList[forwardMostMove].time =
17828                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17829         }
17830         flagged = CheckFlags();
17831     }
17832     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17833     CheckTimeControl();
17834
17835     if (flagged || !appData.clockMode) return;
17836
17837     switch (gameMode) {
17838       case MachinePlaysBlack:
17839       case MachinePlaysWhite:
17840       case BeginningOfGame:
17841         if (pausing) return;
17842         break;
17843
17844       case EditGame:
17845       case PlayFromGameFile:
17846       case IcsExamining:
17847         return;
17848
17849       default:
17850         break;
17851     }
17852
17853     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17854         if(WhiteOnMove(forwardMostMove))
17855              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17856         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17857     }
17858
17859     tickStartTM = now;
17860     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17861       whiteTimeRemaining : blackTimeRemaining);
17862     StartClockTimer(intendedTickLength);
17863 }
17864
17865
17866 /* Stop both clocks */
17867 void
17868 StopClocks ()
17869 {
17870     long lastTickLength;
17871     TimeMark now;
17872
17873     if (!StopClockTimer()) return;
17874     if (!appData.clockMode) return;
17875
17876     GetTimeMark(&now);
17877
17878     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17879     if (WhiteOnMove(forwardMostMove)) {
17880         if(whiteNPS >= 0) lastTickLength = 0;
17881         whiteTimeRemaining -= lastTickLength;
17882         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17883     } else {
17884         if(blackNPS >= 0) lastTickLength = 0;
17885         blackTimeRemaining -= lastTickLength;
17886         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17887     }
17888     CheckFlags();
17889 }
17890
17891 /* Start clock of player on move.  Time may have been reset, so
17892    if clock is already running, stop and restart it. */
17893 void
17894 StartClocks ()
17895 {
17896     (void) StopClockTimer(); /* in case it was running already */
17897     DisplayBothClocks();
17898     if (CheckFlags()) return;
17899
17900     if (!appData.clockMode) return;
17901     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17902
17903     GetTimeMark(&tickStartTM);
17904     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17905       whiteTimeRemaining : blackTimeRemaining);
17906
17907    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17908     whiteNPS = blackNPS = -1;
17909     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17910        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17911         whiteNPS = first.nps;
17912     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17913        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17914         blackNPS = first.nps;
17915     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17916         whiteNPS = second.nps;
17917     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17918         blackNPS = second.nps;
17919     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17920
17921     StartClockTimer(intendedTickLength);
17922 }
17923
17924 char *
17925 TimeString (long ms)
17926 {
17927     long second, minute, hour, day;
17928     char *sign = "";
17929     static char buf[32];
17930
17931     if (ms > 0 && ms <= 9900) {
17932       /* convert milliseconds to tenths, rounding up */
17933       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17934
17935       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17936       return buf;
17937     }
17938
17939     /* convert milliseconds to seconds, rounding up */
17940     /* use floating point to avoid strangeness of integer division
17941        with negative dividends on many machines */
17942     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17943
17944     if (second < 0) {
17945         sign = "-";
17946         second = -second;
17947     }
17948
17949     day = second / (60 * 60 * 24);
17950     second = second % (60 * 60 * 24);
17951     hour = second / (60 * 60);
17952     second = second % (60 * 60);
17953     minute = second / 60;
17954     second = second % 60;
17955
17956     if (day > 0)
17957       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17958               sign, day, hour, minute, second);
17959     else if (hour > 0)
17960       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17961     else
17962       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17963
17964     return buf;
17965 }
17966
17967
17968 /*
17969  * This is necessary because some C libraries aren't ANSI C compliant yet.
17970  */
17971 char *
17972 StrStr (char *string, char *match)
17973 {
17974     int i, length;
17975
17976     length = strlen(match);
17977
17978     for (i = strlen(string) - length; i >= 0; i--, string++)
17979       if (!strncmp(match, string, length))
17980         return string;
17981
17982     return NULL;
17983 }
17984
17985 char *
17986 StrCaseStr (char *string, char *match)
17987 {
17988     int i, j, length;
17989
17990     length = strlen(match);
17991
17992     for (i = strlen(string) - length; i >= 0; i--, string++) {
17993         for (j = 0; j < length; j++) {
17994             if (ToLower(match[j]) != ToLower(string[j]))
17995               break;
17996         }
17997         if (j == length) return string;
17998     }
17999
18000     return NULL;
18001 }
18002
18003 #ifndef _amigados
18004 int
18005 StrCaseCmp (char *s1, char *s2)
18006 {
18007     char c1, c2;
18008
18009     for (;;) {
18010         c1 = ToLower(*s1++);
18011         c2 = ToLower(*s2++);
18012         if (c1 > c2) return 1;
18013         if (c1 < c2) return -1;
18014         if (c1 == NULLCHAR) return 0;
18015     }
18016 }
18017
18018
18019 int
18020 ToLower (int c)
18021 {
18022     return isupper(c) ? tolower(c) : c;
18023 }
18024
18025
18026 int
18027 ToUpper (int c)
18028 {
18029     return islower(c) ? toupper(c) : c;
18030 }
18031 #endif /* !_amigados    */
18032
18033 char *
18034 StrSave (char *s)
18035 {
18036   char *ret;
18037
18038   if ((ret = (char *) malloc(strlen(s) + 1)))
18039     {
18040       safeStrCpy(ret, s, strlen(s)+1);
18041     }
18042   return ret;
18043 }
18044
18045 char *
18046 StrSavePtr (char *s, char **savePtr)
18047 {
18048     if (*savePtr) {
18049         free(*savePtr);
18050     }
18051     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18052       safeStrCpy(*savePtr, s, strlen(s)+1);
18053     }
18054     return(*savePtr);
18055 }
18056
18057 char *
18058 PGNDate ()
18059 {
18060     time_t clock;
18061     struct tm *tm;
18062     char buf[MSG_SIZ];
18063
18064     clock = time((time_t *)NULL);
18065     tm = localtime(&clock);
18066     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18067             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18068     return StrSave(buf);
18069 }
18070
18071
18072 char *
18073 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18074 {
18075     int i, j, fromX, fromY, toX, toY;
18076     int whiteToPlay, haveRights = nrCastlingRights;
18077     char buf[MSG_SIZ];
18078     char *p, *q;
18079     int emptycount;
18080     ChessSquare piece;
18081
18082     whiteToPlay = (gameMode == EditPosition) ?
18083       !blackPlaysFirst : (move % 2 == 0);
18084     p = buf;
18085
18086     /* Piece placement data */
18087     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18088         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18089         emptycount = 0;
18090         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18091             if (boards[move][i][j] == EmptySquare) {
18092                 emptycount++;
18093             } else { ChessSquare piece = boards[move][i][j];
18094                 if (emptycount > 0) {
18095                     if(emptycount<10) /* [HGM] can be >= 10 */
18096                         *p++ = '0' + emptycount;
18097                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18098                     emptycount = 0;
18099                 }
18100                 if(PieceToChar(piece) == '+') {
18101                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18102                     *p++ = '+';
18103                     piece = (ChessSquare)(CHUDEMOTED(piece));
18104                 }
18105                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18106                 if(*p = PieceSuffix(piece)) p++;
18107                 if(p[-1] == '~') {
18108                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18109                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18110                     *p++ = '~';
18111                 }
18112             }
18113         }
18114         if (emptycount > 0) {
18115             if(emptycount<10) /* [HGM] can be >= 10 */
18116                 *p++ = '0' + emptycount;
18117             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18118             emptycount = 0;
18119         }
18120         *p++ = '/';
18121     }
18122     *(p - 1) = ' ';
18123
18124     /* [HGM] print Crazyhouse or Shogi holdings */
18125     if( gameInfo.holdingsWidth ) {
18126         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18127         q = p;
18128         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18129             piece = boards[move][i][BOARD_WIDTH-1];
18130             if( piece != EmptySquare )
18131               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18132                   *p++ = PieceToChar(piece);
18133         }
18134         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18135             piece = boards[move][BOARD_HEIGHT-i-1][0];
18136             if( piece != EmptySquare )
18137               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18138                   *p++ = PieceToChar(piece);
18139         }
18140
18141         if( q == p ) *p++ = '-';
18142         *p++ = ']';
18143         *p++ = ' ';
18144     }
18145
18146     /* Active color */
18147     *p++ = whiteToPlay ? 'w' : 'b';
18148     *p++ = ' ';
18149
18150   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18151     haveRights = 0; q = p;
18152     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18153       piece = boards[move][0][i];
18154       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18155         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18156       }
18157     }
18158     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18159       piece = boards[move][BOARD_HEIGHT-1][i];
18160       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18161         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18162       }
18163     }
18164     if(p == q) *p++ = '-';
18165     *p++ = ' ';
18166   }
18167
18168   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18169     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18170   } else {
18171   if(haveRights) {
18172      int handW=0, handB=0;
18173      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18174         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18175         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18176      }
18177      q = p;
18178      if(appData.fischerCastling) {
18179         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18180            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18181                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18182         } else {
18183        /* [HGM] write directly from rights */
18184            if(boards[move][CASTLING][2] != NoRights &&
18185               boards[move][CASTLING][0] != NoRights   )
18186                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18187            if(boards[move][CASTLING][2] != NoRights &&
18188               boards[move][CASTLING][1] != NoRights   )
18189                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18190         }
18191         if(handB) {
18192            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18193                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18194         } else {
18195            if(boards[move][CASTLING][5] != NoRights &&
18196               boards[move][CASTLING][3] != NoRights   )
18197                 *p++ = boards[move][CASTLING][3] + AAA;
18198            if(boards[move][CASTLING][5] != NoRights &&
18199               boards[move][CASTLING][4] != NoRights   )
18200                 *p++ = boards[move][CASTLING][4] + AAA;
18201         }
18202      } else {
18203
18204         /* [HGM] write true castling rights */
18205         if( nrCastlingRights == 6 ) {
18206             int q, k=0;
18207             if(boards[move][CASTLING][0] != NoRights &&
18208                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18209             q = (boards[move][CASTLING][1] != NoRights &&
18210                  boards[move][CASTLING][2] != NoRights  );
18211             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18212                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18213                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18214                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18215             }
18216             if(q) *p++ = 'Q';
18217             k = 0;
18218             if(boards[move][CASTLING][3] != NoRights &&
18219                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18220             q = (boards[move][CASTLING][4] != NoRights &&
18221                  boards[move][CASTLING][5] != NoRights  );
18222             if(handB) {
18223                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18224                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18225                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18226             }
18227             if(q) *p++ = 'q';
18228         }
18229      }
18230      if (q == p) *p++ = '-'; /* No castling rights */
18231      *p++ = ' ';
18232   }
18233
18234   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18235      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18236      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18237     /* En passant target square */
18238     if (move > backwardMostMove) {
18239         fromX = moveList[move - 1][0] - AAA;
18240         fromY = moveList[move - 1][1] - ONE;
18241         toX = moveList[move - 1][2] - AAA;
18242         toY = moveList[move - 1][3] - ONE;
18243         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18244             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18245             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18246             fromX == toX) {
18247             /* 2-square pawn move just happened */
18248             *p++ = toX + AAA;
18249             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18250         } else {
18251             *p++ = '-';
18252         }
18253     } else if(move == backwardMostMove) {
18254         // [HGM] perhaps we should always do it like this, and forget the above?
18255         if((signed char)boards[move][EP_STATUS] >= 0) {
18256             *p++ = boards[move][EP_STATUS] + AAA;
18257             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18258         } else {
18259             *p++ = '-';
18260         }
18261     } else {
18262         *p++ = '-';
18263     }
18264     *p++ = ' ';
18265   }
18266   }
18267
18268     if(moveCounts)
18269     {   int i = 0, j=move;
18270
18271         /* [HGM] find reversible plies */
18272         if (appData.debugMode) { int k;
18273             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18274             for(k=backwardMostMove; k<=forwardMostMove; k++)
18275                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18276
18277         }
18278
18279         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18280         if( j == backwardMostMove ) i += initialRulePlies;
18281         sprintf(p, "%d ", i);
18282         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18283
18284         /* Fullmove number */
18285         sprintf(p, "%d", (move / 2) + 1);
18286     } else *--p = NULLCHAR;
18287
18288     return StrSave(buf);
18289 }
18290
18291 Boolean
18292 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18293 {
18294     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18295     char *p, c;
18296     int emptycount, virgin[BOARD_FILES];
18297     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18298
18299     p = fen;
18300
18301     /* Piece placement data */
18302     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18303         j = 0;
18304         for (;;) {
18305             if (*p == '/' || *p == ' ' || *p == '[' ) {
18306                 if(j > w) w = j;
18307                 emptycount = gameInfo.boardWidth - j;
18308                 while (emptycount--)
18309                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18310                 if (*p == '/') p++;
18311                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18312                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18313                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18314                     }
18315                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18316                 }
18317                 break;
18318 #if(BOARD_FILES >= 10)*0
18319             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18320                 p++; emptycount=10;
18321                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18322                 while (emptycount--)
18323                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18324 #endif
18325             } else if (*p == '*') {
18326                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18327             } else if (isdigit(*p)) {
18328                 emptycount = *p++ - '0';
18329                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18330                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18331                 while (emptycount--)
18332                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18333             } else if (*p == '<') {
18334                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18335                 else if (i != 0 || !shuffle) return FALSE;
18336                 p++;
18337             } else if (shuffle && *p == '>') {
18338                 p++; // for now ignore closing shuffle range, and assume rank-end
18339             } else if (*p == '?') {
18340                 if (j >= gameInfo.boardWidth) return FALSE;
18341                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18342                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18343             } else if (*p == '+' || isalpha(*p)) {
18344                 char *q, *s = SUFFIXES;
18345                 if (j >= gameInfo.boardWidth) return FALSE;
18346                 if(*p=='+') {
18347                     char c = *++p;
18348                     if(q = strchr(s, p[1])) p++;
18349                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18350                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18351                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18352                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18353                 } else {
18354                     char c = *p++;
18355                     if(q = strchr(s, *p)) p++;
18356                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18357                 }
18358
18359                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18360                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18361                     piece = (ChessSquare) (PROMOTED(piece));
18362                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18363                     p++;
18364                 }
18365                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18366                 if(piece == king) wKingRank = i;
18367                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18368             } else {
18369                 return FALSE;
18370             }
18371         }
18372     }
18373     while (*p == '/' || *p == ' ') p++;
18374
18375     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18376
18377     /* [HGM] by default clear Crazyhouse holdings, if present */
18378     if(gameInfo.holdingsWidth) {
18379        for(i=0; i<BOARD_HEIGHT; i++) {
18380            board[i][0]             = EmptySquare; /* black holdings */
18381            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18382            board[i][1]             = (ChessSquare) 0; /* black counts */
18383            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18384        }
18385     }
18386
18387     /* [HGM] look for Crazyhouse holdings here */
18388     while(*p==' ') p++;
18389     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18390         int swap=0, wcnt=0, bcnt=0;
18391         if(*p == '[') p++;
18392         if(*p == '<') swap++, p++;
18393         if(*p == '-' ) p++; /* empty holdings */ else {
18394             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18395             /* if we would allow FEN reading to set board size, we would   */
18396             /* have to add holdings and shift the board read so far here   */
18397             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18398                 p++;
18399                 if((int) piece >= (int) BlackPawn ) {
18400                     i = (int)piece - (int)BlackPawn;
18401                     i = PieceToNumber((ChessSquare)i);
18402                     if( i >= gameInfo.holdingsSize ) return FALSE;
18403                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18404                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18405                     bcnt++;
18406                 } else {
18407                     i = (int)piece - (int)WhitePawn;
18408                     i = PieceToNumber((ChessSquare)i);
18409                     if( i >= gameInfo.holdingsSize ) return FALSE;
18410                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18411                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18412                     wcnt++;
18413                 }
18414             }
18415             if(subst) { // substitute back-rank question marks by holdings pieces
18416                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18417                     int k, m, n = bcnt + 1;
18418                     if(board[0][j] == ClearBoard) {
18419                         if(!wcnt) return FALSE;
18420                         n = rand() % wcnt;
18421                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18422                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18423                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18424                             break;
18425                         }
18426                     }
18427                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18428                         if(!bcnt) return FALSE;
18429                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18430                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18431                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18432                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18433                             break;
18434                         }
18435                     }
18436                 }
18437                 subst = 0;
18438             }
18439         }
18440         if(*p == ']') p++;
18441     }
18442
18443     if(subst) return FALSE; // substitution requested, but no holdings
18444
18445     while(*p == ' ') p++;
18446
18447     /* Active color */
18448     c = *p++;
18449     if(appData.colorNickNames) {
18450       if( c == appData.colorNickNames[0] ) c = 'w'; else
18451       if( c == appData.colorNickNames[1] ) c = 'b';
18452     }
18453     switch (c) {
18454       case 'w':
18455         *blackPlaysFirst = FALSE;
18456         break;
18457       case 'b':
18458         *blackPlaysFirst = TRUE;
18459         break;
18460       default:
18461         return FALSE;
18462     }
18463
18464     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18465     /* return the extra info in global variiables             */
18466
18467     while(*p==' ') p++;
18468
18469     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18470         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18471         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18472     }
18473
18474     /* set defaults in case FEN is incomplete */
18475     board[EP_STATUS] = EP_UNKNOWN;
18476     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18477     for(i=0; i<nrCastlingRights; i++ ) {
18478         board[CASTLING][i] =
18479             appData.fischerCastling ? NoRights : initialRights[i];
18480     }   /* assume possible unless obviously impossible */
18481     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18482     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18483     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18484                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18485     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18486     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18487     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18488                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18489     FENrulePlies = 0;
18490
18491     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18492       char *q = p;
18493       int w=0, b=0;
18494       while(isalpha(*p)) {
18495         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18496         if(islower(*p)) b |= 1 << (*p++ - 'a');
18497       }
18498       if(*p == '-') p++;
18499       if(p != q) {
18500         board[TOUCHED_W] = ~w;
18501         board[TOUCHED_B] = ~b;
18502         while(*p == ' ') p++;
18503       }
18504     } else
18505
18506     if(nrCastlingRights) {
18507       int fischer = 0;
18508       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18509       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18510           /* castling indicator present, so default becomes no castlings */
18511           for(i=0; i<nrCastlingRights; i++ ) {
18512                  board[CASTLING][i] = NoRights;
18513           }
18514       }
18515       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18516              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18517              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18518              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18519         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18520
18521         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18522             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18523             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18524         }
18525         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18526             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18527         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18528                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18529         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18530                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18531         switch(c) {
18532           case'K':
18533               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18534               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18535               board[CASTLING][2] = whiteKingFile;
18536               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18537               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18538               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18539               break;
18540           case'Q':
18541               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18542               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18543               board[CASTLING][2] = whiteKingFile;
18544               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18545               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18546               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18547               break;
18548           case'k':
18549               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18550               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18551               board[CASTLING][5] = blackKingFile;
18552               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18553               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18554               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18555               break;
18556           case'q':
18557               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18558               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18559               board[CASTLING][5] = blackKingFile;
18560               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18561               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18562               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18563           case '-':
18564               break;
18565           default: /* FRC castlings */
18566               if(c >= 'a') { /* black rights */
18567                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18568                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18569                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18570                   if(i == BOARD_RGHT) break;
18571                   board[CASTLING][5] = i;
18572                   c -= AAA;
18573                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18574                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18575                   if(c > i)
18576                       board[CASTLING][3] = c;
18577                   else
18578                       board[CASTLING][4] = c;
18579               } else { /* white rights */
18580                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18581                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18582                     if(board[0][i] == WhiteKing) break;
18583                   if(i == BOARD_RGHT) break;
18584                   board[CASTLING][2] = i;
18585                   c -= AAA - 'a' + 'A';
18586                   if(board[0][c] >= WhiteKing) break;
18587                   if(c > i)
18588                       board[CASTLING][0] = c;
18589                   else
18590                       board[CASTLING][1] = c;
18591               }
18592         }
18593       }
18594       for(i=0; i<nrCastlingRights; i++)
18595         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18596       if(gameInfo.variant == VariantSChess)
18597         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18598       if(fischer && shuffle) appData.fischerCastling = TRUE;
18599     if (appData.debugMode) {
18600         fprintf(debugFP, "FEN castling rights:");
18601         for(i=0; i<nrCastlingRights; i++)
18602         fprintf(debugFP, " %d", board[CASTLING][i]);
18603         fprintf(debugFP, "\n");
18604     }
18605
18606       while(*p==' ') p++;
18607     }
18608
18609     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18610
18611     /* read e.p. field in games that know e.p. capture */
18612     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18613        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18614        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18615       if(*p=='-') {
18616         p++; board[EP_STATUS] = EP_NONE;
18617       } else {
18618          char c = *p++ - AAA;
18619
18620          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18621          if(*p >= '0' && *p <='9') p++;
18622          board[EP_STATUS] = c;
18623       }
18624     }
18625
18626
18627     if(sscanf(p, "%d", &i) == 1) {
18628         FENrulePlies = i; /* 50-move ply counter */
18629         /* (The move number is still ignored)    */
18630     }
18631
18632     return TRUE;
18633 }
18634
18635 void
18636 EditPositionPasteFEN (char *fen)
18637 {
18638   if (fen != NULL) {
18639     Board initial_position;
18640
18641     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18642       DisplayError(_("Bad FEN position in clipboard"), 0);
18643       return ;
18644     } else {
18645       int savedBlackPlaysFirst = blackPlaysFirst;
18646       EditPositionEvent();
18647       blackPlaysFirst = savedBlackPlaysFirst;
18648       CopyBoard(boards[0], initial_position);
18649       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18650       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18651       DisplayBothClocks();
18652       DrawPosition(FALSE, boards[currentMove]);
18653     }
18654   }
18655 }
18656
18657 static char cseq[12] = "\\   ";
18658
18659 Boolean
18660 set_cont_sequence (char *new_seq)
18661 {
18662     int len;
18663     Boolean ret;
18664
18665     // handle bad attempts to set the sequence
18666         if (!new_seq)
18667                 return 0; // acceptable error - no debug
18668
18669     len = strlen(new_seq);
18670     ret = (len > 0) && (len < sizeof(cseq));
18671     if (ret)
18672       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18673     else if (appData.debugMode)
18674       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18675     return ret;
18676 }
18677
18678 /*
18679     reformat a source message so words don't cross the width boundary.  internal
18680     newlines are not removed.  returns the wrapped size (no null character unless
18681     included in source message).  If dest is NULL, only calculate the size required
18682     for the dest buffer.  lp argument indicats line position upon entry, and it's
18683     passed back upon exit.
18684 */
18685 int
18686 wrap (char *dest, char *src, int count, int width, int *lp)
18687 {
18688     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18689
18690     cseq_len = strlen(cseq);
18691     old_line = line = *lp;
18692     ansi = len = clen = 0;
18693
18694     for (i=0; i < count; i++)
18695     {
18696         if (src[i] == '\033')
18697             ansi = 1;
18698
18699         // if we hit the width, back up
18700         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18701         {
18702             // store i & len in case the word is too long
18703             old_i = i, old_len = len;
18704
18705             // find the end of the last word
18706             while (i && src[i] != ' ' && src[i] != '\n')
18707             {
18708                 i--;
18709                 len--;
18710             }
18711
18712             // word too long?  restore i & len before splitting it
18713             if ((old_i-i+clen) >= width)
18714             {
18715                 i = old_i;
18716                 len = old_len;
18717             }
18718
18719             // extra space?
18720             if (i && src[i-1] == ' ')
18721                 len--;
18722
18723             if (src[i] != ' ' && src[i] != '\n')
18724             {
18725                 i--;
18726                 if (len)
18727                     len--;
18728             }
18729
18730             // now append the newline and continuation sequence
18731             if (dest)
18732                 dest[len] = '\n';
18733             len++;
18734             if (dest)
18735                 strncpy(dest+len, cseq, cseq_len);
18736             len += cseq_len;
18737             line = cseq_len;
18738             clen = cseq_len;
18739             continue;
18740         }
18741
18742         if (dest)
18743             dest[len] = src[i];
18744         len++;
18745         if (!ansi)
18746             line++;
18747         if (src[i] == '\n')
18748             line = 0;
18749         if (src[i] == 'm')
18750             ansi = 0;
18751     }
18752     if (dest && appData.debugMode)
18753     {
18754         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18755             count, width, line, len, *lp);
18756         show_bytes(debugFP, src, count);
18757         fprintf(debugFP, "\ndest: ");
18758         show_bytes(debugFP, dest, len);
18759         fprintf(debugFP, "\n");
18760     }
18761     *lp = dest ? line : old_line;
18762
18763     return len;
18764 }
18765
18766 // [HGM] vari: routines for shelving variations
18767 Boolean modeRestore = FALSE;
18768
18769 void
18770 PushInner (int firstMove, int lastMove)
18771 {
18772         int i, j, nrMoves = lastMove - firstMove;
18773
18774         // push current tail of game on stack
18775         savedResult[storedGames] = gameInfo.result;
18776         savedDetails[storedGames] = gameInfo.resultDetails;
18777         gameInfo.resultDetails = NULL;
18778         savedFirst[storedGames] = firstMove;
18779         savedLast [storedGames] = lastMove;
18780         savedFramePtr[storedGames] = framePtr;
18781         framePtr -= nrMoves; // reserve space for the boards
18782         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18783             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18784             for(j=0; j<MOVE_LEN; j++)
18785                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18786             for(j=0; j<2*MOVE_LEN; j++)
18787                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18788             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18789             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18790             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18791             pvInfoList[firstMove+i-1].depth = 0;
18792             commentList[framePtr+i] = commentList[firstMove+i];
18793             commentList[firstMove+i] = NULL;
18794         }
18795
18796         storedGames++;
18797         forwardMostMove = firstMove; // truncate game so we can start variation
18798 }
18799
18800 void
18801 PushTail (int firstMove, int lastMove)
18802 {
18803         if(appData.icsActive) { // only in local mode
18804                 forwardMostMove = currentMove; // mimic old ICS behavior
18805                 return;
18806         }
18807         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18808
18809         PushInner(firstMove, lastMove);
18810         if(storedGames == 1) GreyRevert(FALSE);
18811         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18812 }
18813
18814 void
18815 PopInner (Boolean annotate)
18816 {
18817         int i, j, nrMoves;
18818         char buf[8000], moveBuf[20];
18819
18820         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18821         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18822         nrMoves = savedLast[storedGames] - currentMove;
18823         if(annotate) {
18824                 int cnt = 10;
18825                 if(!WhiteOnMove(currentMove))
18826                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18827                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18828                 for(i=currentMove; i<forwardMostMove; i++) {
18829                         if(WhiteOnMove(i))
18830                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18831                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18832                         strcat(buf, moveBuf);
18833                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18834                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18835                 }
18836                 strcat(buf, ")");
18837         }
18838         for(i=1; i<=nrMoves; i++) { // copy last variation back
18839             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18840             for(j=0; j<MOVE_LEN; j++)
18841                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18842             for(j=0; j<2*MOVE_LEN; j++)
18843                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18844             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18845             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18846             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18847             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18848             commentList[currentMove+i] = commentList[framePtr+i];
18849             commentList[framePtr+i] = NULL;
18850         }
18851         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18852         framePtr = savedFramePtr[storedGames];
18853         gameInfo.result = savedResult[storedGames];
18854         if(gameInfo.resultDetails != NULL) {
18855             free(gameInfo.resultDetails);
18856       }
18857         gameInfo.resultDetails = savedDetails[storedGames];
18858         forwardMostMove = currentMove + nrMoves;
18859 }
18860
18861 Boolean
18862 PopTail (Boolean annotate)
18863 {
18864         if(appData.icsActive) return FALSE; // only in local mode
18865         if(!storedGames) return FALSE; // sanity
18866         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18867
18868         PopInner(annotate);
18869         if(currentMove < forwardMostMove) ForwardEvent(); else
18870         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18871
18872         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18873         return TRUE;
18874 }
18875
18876 void
18877 CleanupTail ()
18878 {       // remove all shelved variations
18879         int i;
18880         for(i=0; i<storedGames; i++) {
18881             if(savedDetails[i])
18882                 free(savedDetails[i]);
18883             savedDetails[i] = NULL;
18884         }
18885         for(i=framePtr; i<MAX_MOVES; i++) {
18886                 if(commentList[i]) free(commentList[i]);
18887                 commentList[i] = NULL;
18888         }
18889         framePtr = MAX_MOVES-1;
18890         storedGames = 0;
18891 }
18892
18893 void
18894 LoadVariation (int index, char *text)
18895 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18896         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18897         int level = 0, move;
18898
18899         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18900         // first find outermost bracketing variation
18901         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18902             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18903                 if(*p == '{') wait = '}'; else
18904                 if(*p == '[') wait = ']'; else
18905                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18906                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18907             }
18908             if(*p == wait) wait = NULLCHAR; // closing ]} found
18909             p++;
18910         }
18911         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18912         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18913         end[1] = NULLCHAR; // clip off comment beyond variation
18914         ToNrEvent(currentMove-1);
18915         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18916         // kludge: use ParsePV() to append variation to game
18917         move = currentMove;
18918         ParsePV(start, TRUE, TRUE);
18919         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18920         ClearPremoveHighlights();
18921         CommentPopDown();
18922         ToNrEvent(currentMove+1);
18923 }
18924
18925 void
18926 LoadTheme ()
18927 {
18928     char *p, *q, buf[MSG_SIZ];
18929     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18930         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18931         ParseArgsFromString(buf);
18932         ActivateTheme(TRUE); // also redo colors
18933         return;
18934     }
18935     p = nickName;
18936     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18937     {
18938         int len;
18939         q = appData.themeNames;
18940         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18941       if(appData.useBitmaps) {
18942         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18943                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18944                 appData.liteBackTextureMode,
18945                 appData.darkBackTextureMode );
18946       } else {
18947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18948                 Col2Text(2),   // lightSquareColor
18949                 Col2Text(3) ); // darkSquareColor
18950       }
18951       if(appData.useBorder) {
18952         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18953                 appData.border);
18954       } else {
18955         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18956       }
18957       if(appData.useFont) {
18958         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18959                 appData.renderPiecesWithFont,
18960                 appData.fontToPieceTable,
18961                 Col2Text(9),    // appData.fontBackColorWhite
18962                 Col2Text(10) ); // appData.fontForeColorBlack
18963       } else {
18964         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18965                 appData.pieceDirectory);
18966         if(!appData.pieceDirectory[0])
18967           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18968                 Col2Text(0),   // whitePieceColor
18969                 Col2Text(1) ); // blackPieceColor
18970       }
18971       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18972                 Col2Text(4),   // highlightSquareColor
18973                 Col2Text(5) ); // premoveHighlightColor
18974         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18975         if(insert != q) insert[-1] = NULLCHAR;
18976         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18977         if(q)   free(q);
18978     }
18979     ActivateTheme(FALSE);
18980 }