Allow writing text on pieces
[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 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
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
299 /* States for ics_getting_history */
300 #define H_FALSE 0
301 #define H_REQUESTED 1
302 #define H_GOT_REQ_HEADER 2
303 #define H_GOT_UNREQ_HEADER 3
304 #define H_GETTING_MOVES 4
305 #define H_GOT_UNWANTED_HEADER 5
306
307 /* whosays values for GameEnds */
308 #define GE_ICS 0
309 #define GE_ENGINE 1
310 #define GE_PLAYER 2
311 #define GE_FILE 3
312 #define GE_XBOARD 4
313 #define GE_ENGINE1 5
314 #define GE_ENGINE2 6
315
316 /* Maximum number of games in a cmail message */
317 #define CMAIL_MAX_GAMES 20
318
319 /* Different types of move when calling RegisterMove */
320 #define CMAIL_MOVE   0
321 #define CMAIL_RESIGN 1
322 #define CMAIL_DRAW   2
323 #define CMAIL_ACCEPT 3
324
325 /* Different types of result to remember for each game */
326 #define CMAIL_NOT_RESULT 0
327 #define CMAIL_OLD_RESULT 1
328 #define CMAIL_NEW_RESULT 2
329
330 /* Telnet protocol constants */
331 #define TN_WILL 0373
332 #define TN_WONT 0374
333 #define TN_DO   0375
334 #define TN_DONT 0376
335 #define TN_IAC  0377
336 #define TN_ECHO 0001
337 #define TN_SGA  0003
338 #define TN_PORT 23
339
340 char*
341 safeStrCpy (char *dst, const char *src, size_t count)
342 { // [HGM] made safe
343   int i;
344   assert( dst != NULL );
345   assert( src != NULL );
346   assert( count > 0 );
347
348   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
349   if(  i == count && dst[count-1] != NULLCHAR)
350     {
351       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
352       if(appData.debugMode)
353         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
354     }
355
356   return dst;
357 }
358
359 /* Some compiler can't cast u64 to double
360  * This function do the job for us:
361
362  * We use the highest bit for cast, this only
363  * works if the highest bit is not
364  * in use (This should not happen)
365  *
366  * We used this for all compiler
367  */
368 double
369 u64ToDouble (u64 value)
370 {
371   double r;
372   u64 tmp = value & u64Const(0x7fffffffffffffff);
373   r = (double)(s64)tmp;
374   if (value & u64Const(0x8000000000000000))
375        r +=  9.2233720368547758080e18; /* 2^63 */
376  return r;
377 }
378
379 /* Fake up flags for now, as we aren't keeping track of castling
380    availability yet. [HGM] Change of logic: the flag now only
381    indicates the type of castlings allowed by the rule of the game.
382    The actual rights themselves are maintained in the array
383    castlingRights, as part of the game history, and are not probed
384    by this function.
385  */
386 int
387 PosFlags (index)
388 {
389   int flags = F_ALL_CASTLE_OK;
390   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
391   switch (gameInfo.variant) {
392   case VariantSuicide:
393     flags &= ~F_ALL_CASTLE_OK;
394   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
395     flags |= F_IGNORE_CHECK;
396   case VariantLosers:
397     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
398     break;
399   case VariantAtomic:
400     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
401     break;
402   case VariantKriegspiel:
403     flags |= F_KRIEGSPIEL_CAPTURE;
404     break;
405   case VariantCapaRandom:
406   case VariantFischeRandom:
407     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
408   case VariantNoCastle:
409   case VariantShatranj:
410   case VariantCourier:
411   case VariantMakruk:
412   case VariantASEAN:
413   case VariantGrand:
414     flags &= ~F_ALL_CASTLE_OK;
415     break;
416   default:
417     break;
418   }
419   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
420   return flags;
421 }
422
423 FILE *gameFileFP, *debugFP, *serverFP;
424 char *currentDebugFile; // [HGM] debug split: to remember name
425
426 /*
427     [AS] Note: sometimes, the sscanf() function is used to parse the input
428     into a fixed-size buffer. Because of this, we must be prepared to
429     receive strings as long as the size of the input buffer, which is currently
430     set to 4K for Windows and 8K for the rest.
431     So, we must either allocate sufficiently large buffers here, or
432     reduce the size of the input buffer in the input reading part.
433 */
434
435 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
436 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
437 char thinkOutput1[MSG_SIZ*10];
438
439 ChessProgramState first, second, pairing;
440
441 /* premove variables */
442 int premoveToX = 0;
443 int premoveToY = 0;
444 int premoveFromX = 0;
445 int premoveFromY = 0;
446 int premovePromoChar = 0;
447 int gotPremove = 0;
448 Boolean alarmSounded;
449 /* end premove variables */
450
451 char *ics_prefix = "$";
452 enum ICS_TYPE ics_type = ICS_GENERIC;
453
454 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
455 int pauseExamForwardMostMove = 0;
456 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
457 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
458 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
459 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
460 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
461 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
462 int whiteFlag = FALSE, blackFlag = FALSE;
463 int userOfferedDraw = FALSE;
464 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
465 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
466 int cmailMoveType[CMAIL_MAX_GAMES];
467 long ics_clock_paused = 0;
468 ProcRef icsPR = NoProc, cmailPR = NoProc;
469 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
470 GameMode gameMode = BeginningOfGame;
471 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
472 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
473 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
474 int hiddenThinkOutputState = 0; /* [AS] */
475 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
476 int adjudicateLossPlies = 6;
477 char white_holding[64], black_holding[64];
478 TimeMark lastNodeCountTime;
479 long lastNodeCount=0;
480 int shiftKey, controlKey; // [HGM] set by mouse handler
481
482 int have_sent_ICS_logon = 0;
483 int movesPerSession;
484 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
485 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
486 Boolean adjustedClock;
487 long timeControl_2; /* [AS] Allow separate time controls */
488 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
489 long timeRemaining[2][MAX_MOVES];
490 int matchGame = 0, nextGame = 0, roundNr = 0;
491 Boolean waitingForGame = FALSE, startingEngine = FALSE;
492 TimeMark programStartTime, pauseStart;
493 char ics_handle[MSG_SIZ];
494 int have_set_title = 0;
495
496 /* animateTraining preserves the state of appData.animate
497  * when Training mode is activated. This allows the
498  * response to be animated when appData.animate == TRUE and
499  * appData.animateDragging == TRUE.
500  */
501 Boolean animateTraining;
502
503 GameInfo gameInfo;
504
505 AppData appData;
506
507 Board boards[MAX_MOVES];
508 /* [HGM] Following 7 needed for accurate legality tests: */
509 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
510 signed char  initialRights[BOARD_FILES];
511 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
512 int   initialRulePlies, FENrulePlies;
513 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
514 int loadFlag = 0;
515 Boolean shuffleOpenings;
516 int mute; // mute all sounds
517
518 // [HGM] vari: next 12 to save and restore variations
519 #define MAX_VARIATIONS 10
520 int framePtr = MAX_MOVES-1; // points to free stack entry
521 int storedGames = 0;
522 int savedFirst[MAX_VARIATIONS];
523 int savedLast[MAX_VARIATIONS];
524 int savedFramePtr[MAX_VARIATIONS];
525 char *savedDetails[MAX_VARIATIONS];
526 ChessMove savedResult[MAX_VARIATIONS];
527
528 void PushTail P((int firstMove, int lastMove));
529 Boolean PopTail P((Boolean annotate));
530 void PushInner P((int firstMove, int lastMove));
531 void PopInner P((Boolean annotate));
532 void CleanupTail P((void));
533
534 ChessSquare  FIDEArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
538         BlackKing, BlackBishop, BlackKnight, BlackRook }
539 };
540
541 ChessSquare twoKingsArray[2][BOARD_FILES] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
545         BlackKing, BlackKing, BlackKnight, BlackRook }
546 };
547
548 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
550         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
551     { BlackRook, BlackMan, BlackBishop, BlackQueen,
552         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
553 };
554
555 ChessSquare SpartanArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
557         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
558     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
559         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
560 };
561
562 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
563     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
564         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
565     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
566         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
567 };
568
569 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
571         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
573         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 };
575
576 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
577     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
578         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackMan, BlackFerz,
580         BlackKing, BlackMan, BlackKnight, BlackRook }
581 };
582
583 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
584     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
585         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackMan, BlackFerz,
587         BlackKing, BlackMan, BlackKnight, BlackRook }
588 };
589
590 ChessSquare  lionArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
592         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
593     { BlackRook, BlackLion, BlackBishop, BlackQueen,
594         BlackKing, BlackBishop, BlackKnight, BlackRook }
595 };
596
597
598 #if (BOARD_FILES>=10)
599 ChessSquare ShogiArray[2][BOARD_FILES] = {
600     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
601         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
602     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
603         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
604 };
605
606 ChessSquare XiangqiArray[2][BOARD_FILES] = {
607     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
608         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
609     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
610         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
611 };
612
613 ChessSquare CapablancaArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
615         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
617         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
618 };
619
620 ChessSquare GreatArray[2][BOARD_FILES] = {
621     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
622         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
623     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
624         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
625 };
626
627 ChessSquare JanusArray[2][BOARD_FILES] = {
628     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
629         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
630     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
631         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
632 };
633
634 ChessSquare GrandArray[2][BOARD_FILES] = {
635     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
636         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
637     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
638         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
639 };
640
641 ChessSquare ChuChessArray[2][BOARD_FILES] = {
642     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
643         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
644     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
645         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
646 };
647
648 #ifdef GOTHIC
649 ChessSquare GothicArray[2][BOARD_FILES] = {
650     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
651         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
652     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
653         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
654 };
655 #else // !GOTHIC
656 #define GothicArray CapablancaArray
657 #endif // !GOTHIC
658
659 #ifdef FALCON
660 ChessSquare FalconArray[2][BOARD_FILES] = {
661     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
662         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
663     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
664         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
665 };
666 #else // !FALCON
667 #define FalconArray CapablancaArray
668 #endif // !FALCON
669
670 #else // !(BOARD_FILES>=10)
671 #define XiangqiPosition FIDEArray
672 #define CapablancaArray FIDEArray
673 #define GothicArray FIDEArray
674 #define GreatArray FIDEArray
675 #endif // !(BOARD_FILES>=10)
676
677 #if (BOARD_FILES>=12)
678 ChessSquare CourierArray[2][BOARD_FILES] = {
679     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
680         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
681     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
682         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
683 };
684 ChessSquare ChuArray[6][BOARD_FILES] = {
685     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
686       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
687     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
688       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
689     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
690       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
691     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
692       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
693     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
694       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
695     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
696       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
697 };
698 #else // !(BOARD_FILES>=12)
699 #define CourierArray CapablancaArray
700 #define ChuArray CapablancaArray
701 #endif // !(BOARD_FILES>=12)
702
703
704 Board initialPosition;
705
706
707 /* Convert str to a rating. Checks for special cases of "----",
708
709    "++++", etc. Also strips ()'s */
710 int
711 string_to_rating (char *str)
712 {
713   while(*str && !isdigit(*str)) ++str;
714   if (!*str)
715     return 0;   /* One of the special "no rating" cases */
716   else
717     return atoi(str);
718 }
719
720 void
721 ClearProgramStats ()
722 {
723     /* Init programStats */
724     programStats.movelist[0] = 0;
725     programStats.depth = 0;
726     programStats.nr_moves = 0;
727     programStats.moves_left = 0;
728     programStats.nodes = 0;
729     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
730     programStats.score = 0;
731     programStats.got_only_move = 0;
732     programStats.got_fail = 0;
733     programStats.line_is_book = 0;
734 }
735
736 void
737 CommonEngineInit ()
738 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
739     if (appData.firstPlaysBlack) {
740         first.twoMachinesColor = "black\n";
741         second.twoMachinesColor = "white\n";
742     } else {
743         first.twoMachinesColor = "white\n";
744         second.twoMachinesColor = "black\n";
745     }
746
747     first.other = &second;
748     second.other = &first;
749
750     { float norm = 1;
751         if(appData.timeOddsMode) {
752             norm = appData.timeOdds[0];
753             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
754         }
755         first.timeOdds  = appData.timeOdds[0]/norm;
756         second.timeOdds = appData.timeOdds[1]/norm;
757     }
758
759     if(programVersion) free(programVersion);
760     if (appData.noChessProgram) {
761         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
762         sprintf(programVersion, "%s", PACKAGE_STRING);
763     } else {
764       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
765       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
766       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
767     }
768 }
769
770 void
771 UnloadEngine (ChessProgramState *cps)
772 {
773         /* Kill off first chess program */
774         if (cps->isr != NULL)
775           RemoveInputSource(cps->isr);
776         cps->isr = NULL;
777
778         if (cps->pr != NoProc) {
779             ExitAnalyzeMode();
780             DoSleep( appData.delayBeforeQuit );
781             SendToProgram("quit\n", cps);
782             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
783         }
784         cps->pr = NoProc;
785         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
786 }
787
788 void
789 ClearOptions (ChessProgramState *cps)
790 {
791     int i;
792     cps->nrOptions = cps->comboCnt = 0;
793     for(i=0; i<MAX_OPTIONS; i++) {
794         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
795         cps->option[i].textValue = 0;
796     }
797 }
798
799 char *engineNames[] = {
800   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
801      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
802 N_("first"),
803   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
804      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
805 N_("second")
806 };
807
808 void
809 InitEngine (ChessProgramState *cps, int n)
810 {   // [HGM] all engine initialiation put in a function that does one engine
811
812     ClearOptions(cps);
813
814     cps->which = engineNames[n];
815     cps->maybeThinking = FALSE;
816     cps->pr = NoProc;
817     cps->isr = NULL;
818     cps->sendTime = 2;
819     cps->sendDrawOffers = 1;
820
821     cps->program = appData.chessProgram[n];
822     cps->host = appData.host[n];
823     cps->dir = appData.directory[n];
824     cps->initString = appData.engInitString[n];
825     cps->computerString = appData.computerString[n];
826     cps->useSigint  = TRUE;
827     cps->useSigterm = TRUE;
828     cps->reuse = appData.reuse[n];
829     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
830     cps->useSetboard = FALSE;
831     cps->useSAN = FALSE;
832     cps->usePing = FALSE;
833     cps->lastPing = 0;
834     cps->lastPong = 0;
835     cps->usePlayother = FALSE;
836     cps->useColors = TRUE;
837     cps->useUsermove = FALSE;
838     cps->sendICS = FALSE;
839     cps->sendName = appData.icsActive;
840     cps->sdKludge = FALSE;
841     cps->stKludge = FALSE;
842     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
843     TidyProgramName(cps->program, cps->host, cps->tidy);
844     cps->matchWins = 0;
845     ASSIGN(cps->variants, appData.variant);
846     cps->analysisSupport = 2; /* detect */
847     cps->analyzing = FALSE;
848     cps->initDone = FALSE;
849     cps->reload = FALSE;
850
851     /* New features added by Tord: */
852     cps->useFEN960 = FALSE;
853     cps->useOOCastle = TRUE;
854     /* End of new features added by Tord. */
855     cps->fenOverride  = appData.fenOverride[n];
856
857     /* [HGM] time odds: set factor for each machine */
858     cps->timeOdds  = appData.timeOdds[n];
859
860     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
861     cps->accumulateTC = appData.accumulateTC[n];
862     cps->maxNrOfSessions = 1;
863
864     /* [HGM] debug */
865     cps->debug = FALSE;
866
867     cps->drawDepth = appData.drawDepth[n];
868     cps->supportsNPS = UNKNOWN;
869     cps->memSize = FALSE;
870     cps->maxCores = FALSE;
871     ASSIGN(cps->egtFormats, "");
872
873     /* [HGM] options */
874     cps->optionSettings  = appData.engOptions[n];
875
876     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
877     cps->isUCI = appData.isUCI[n]; /* [AS] */
878     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
879     cps->highlight = 0;
880
881     if (appData.protocolVersion[n] > PROTOVER
882         || appData.protocolVersion[n] < 1)
883       {
884         char buf[MSG_SIZ];
885         int len;
886
887         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
888                        appData.protocolVersion[n]);
889         if( (len >= MSG_SIZ) && appData.debugMode )
890           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
891
892         DisplayFatalError(buf, 0, 2);
893       }
894     else
895       {
896         cps->protocolVersion = appData.protocolVersion[n];
897       }
898
899     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
900     ParseFeatures(appData.featureDefaults, cps);
901 }
902
903 ChessProgramState *savCps;
904
905 GameMode oldMode;
906
907 void
908 LoadEngine ()
909 {
910     int i;
911     if(WaitForEngine(savCps, LoadEngine)) return;
912     CommonEngineInit(); // recalculate time odds
913     if(gameInfo.variant != StringToVariant(appData.variant)) {
914         // we changed variant when loading the engine; this forces us to reset
915         Reset(TRUE, savCps != &first);
916         oldMode = BeginningOfGame; // to prevent restoring old mode
917     }
918     InitChessProgram(savCps, FALSE);
919     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
920     DisplayMessage("", "");
921     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
922     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
923     ThawUI();
924     SetGNUMode();
925     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
926 }
927
928 void
929 ReplaceEngine (ChessProgramState *cps, int n)
930 {
931     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
932     keepInfo = 1;
933     if(oldMode != BeginningOfGame) EditGameEvent();
934     keepInfo = 0;
935     UnloadEngine(cps);
936     appData.noChessProgram = FALSE;
937     appData.clockMode = TRUE;
938     InitEngine(cps, n);
939     UpdateLogos(TRUE);
940     if(n) return; // only startup first engine immediately; second can wait
941     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
942     LoadEngine();
943 }
944
945 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
946 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
947
948 static char resetOptions[] =
949         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
950         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
951         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
952         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
953
954 void
955 FloatToFront(char **list, char *engineLine)
956 {
957     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
958     int i=0;
959     if(appData.recentEngines <= 0) return;
960     TidyProgramName(engineLine, "localhost", tidy+1);
961     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
962     strncpy(buf+1, *list, MSG_SIZ-50);
963     if(p = strstr(buf, tidy)) { // tidy name appears in list
964         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
965         while(*p++ = *++q); // squeeze out
966     }
967     strcat(tidy, buf+1); // put list behind tidy name
968     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
969     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
970     ASSIGN(*list, tidy+1);
971 }
972
973 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
974
975 void
976 Load (ChessProgramState *cps, int i)
977 {
978     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
979     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
980         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
981         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
982         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
983         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
984         appData.firstProtocolVersion = PROTOVER;
985         ParseArgsFromString(buf);
986         SwapEngines(i);
987         ReplaceEngine(cps, i);
988         FloatToFront(&appData.recentEngineList, engineLine);
989         return;
990     }
991     p = engineName;
992     while(q = strchr(p, SLASH)) p = q+1;
993     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
994     if(engineDir[0] != NULLCHAR) {
995         ASSIGN(appData.directory[i], engineDir); p = engineName;
996     } else if(p != engineName) { // derive directory from engine path, when not given
997         p[-1] = 0;
998         ASSIGN(appData.directory[i], engineName);
999         p[-1] = SLASH;
1000         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1001     } else { ASSIGN(appData.directory[i], "."); }
1002     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1003     if(params[0]) {
1004         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1005         snprintf(command, MSG_SIZ, "%s %s", p, params);
1006         p = command;
1007     }
1008     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1009     ASSIGN(appData.chessProgram[i], p);
1010     appData.isUCI[i] = isUCI;
1011     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1012     appData.hasOwnBookUCI[i] = hasBook;
1013     if(!nickName[0]) useNick = FALSE;
1014     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1015     if(addToList) {
1016         int len;
1017         char quote;
1018         q = firstChessProgramNames;
1019         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1020         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1021         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1022                         quote, p, quote, appData.directory[i],
1023                         useNick ? " -fn \"" : "",
1024                         useNick ? nickName : "",
1025                         useNick ? "\"" : "",
1026                         v1 ? " -firstProtocolVersion 1" : "",
1027                         hasBook ? "" : " -fNoOwnBookUCI",
1028                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1029                         storeVariant ? " -variant " : "",
1030                         storeVariant ? VariantName(gameInfo.variant) : "");
1031         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1032         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1033         if(insert != q) insert[-1] = NULLCHAR;
1034         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1035         if(q)   free(q);
1036         FloatToFront(&appData.recentEngineList, buf);
1037     }
1038     ReplaceEngine(cps, i);
1039 }
1040
1041 void
1042 InitTimeControls ()
1043 {
1044     int matched, min, sec;
1045     /*
1046      * Parse timeControl resource
1047      */
1048     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1049                           appData.movesPerSession)) {
1050         char buf[MSG_SIZ];
1051         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1052         DisplayFatalError(buf, 0, 2);
1053     }
1054
1055     /*
1056      * Parse searchTime resource
1057      */
1058     if (*appData.searchTime != NULLCHAR) {
1059         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1060         if (matched == 1) {
1061             searchTime = min * 60;
1062         } else if (matched == 2) {
1063             searchTime = min * 60 + sec;
1064         } else {
1065             char buf[MSG_SIZ];
1066             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1067             DisplayFatalError(buf, 0, 2);
1068         }
1069     }
1070 }
1071
1072 void
1073 InitBackEnd1 ()
1074 {
1075
1076     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1077     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1078
1079     GetTimeMark(&programStartTime);
1080     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1081     appData.seedBase = random() + (random()<<15);
1082     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1083
1084     ClearProgramStats();
1085     programStats.ok_to_send = 1;
1086     programStats.seen_stat = 0;
1087
1088     /*
1089      * Initialize game list
1090      */
1091     ListNew(&gameList);
1092
1093
1094     /*
1095      * Internet chess server status
1096      */
1097     if (appData.icsActive) {
1098         appData.matchMode = FALSE;
1099         appData.matchGames = 0;
1100 #if ZIPPY
1101         appData.noChessProgram = !appData.zippyPlay;
1102 #else
1103         appData.zippyPlay = FALSE;
1104         appData.zippyTalk = FALSE;
1105         appData.noChessProgram = TRUE;
1106 #endif
1107         if (*appData.icsHelper != NULLCHAR) {
1108             appData.useTelnet = TRUE;
1109             appData.telnetProgram = appData.icsHelper;
1110         }
1111     } else {
1112         appData.zippyTalk = appData.zippyPlay = FALSE;
1113     }
1114
1115     /* [AS] Initialize pv info list [HGM] and game state */
1116     {
1117         int i, j;
1118
1119         for( i=0; i<=framePtr; i++ ) {
1120             pvInfoList[i].depth = -1;
1121             boards[i][EP_STATUS] = EP_NONE;
1122             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1123         }
1124     }
1125
1126     InitTimeControls();
1127
1128     /* [AS] Adjudication threshold */
1129     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1130
1131     InitEngine(&first, 0);
1132     InitEngine(&second, 1);
1133     CommonEngineInit();
1134
1135     pairing.which = "pairing"; // pairing engine
1136     pairing.pr = NoProc;
1137     pairing.isr = NULL;
1138     pairing.program = appData.pairingEngine;
1139     pairing.host = "localhost";
1140     pairing.dir = ".";
1141
1142     if (appData.icsActive) {
1143         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1144     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1145         appData.clockMode = FALSE;
1146         first.sendTime = second.sendTime = 0;
1147     }
1148
1149 #if ZIPPY
1150     /* Override some settings from environment variables, for backward
1151        compatibility.  Unfortunately it's not feasible to have the env
1152        vars just set defaults, at least in xboard.  Ugh.
1153     */
1154     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1155       ZippyInit();
1156     }
1157 #endif
1158
1159     if (!appData.icsActive) {
1160       char buf[MSG_SIZ];
1161       int len;
1162
1163       /* Check for variants that are supported only in ICS mode,
1164          or not at all.  Some that are accepted here nevertheless
1165          have bugs; see comments below.
1166       */
1167       VariantClass variant = StringToVariant(appData.variant);
1168       switch (variant) {
1169       case VariantBughouse:     /* need four players and two boards */
1170       case VariantKriegspiel:   /* need to hide pieces and move details */
1171         /* case VariantFischeRandom: (Fabien: moved below) */
1172         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1173         if( (len >= MSG_SIZ) && appData.debugMode )
1174           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1175
1176         DisplayFatalError(buf, 0, 2);
1177         return;
1178
1179       case VariantUnknown:
1180       case VariantLoadable:
1181       case Variant29:
1182       case Variant30:
1183       case Variant31:
1184       case Variant32:
1185       case Variant33:
1186       case Variant34:
1187       case Variant35:
1188       case Variant36:
1189       default:
1190         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1191         if( (len >= MSG_SIZ) && appData.debugMode )
1192           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1193
1194         DisplayFatalError(buf, 0, 2);
1195         return;
1196
1197       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1198       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1199       case VariantGothic:     /* [HGM] should work */
1200       case VariantCapablanca: /* [HGM] should work */
1201       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1202       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1203       case VariantChu:        /* [HGM] experimental */
1204       case VariantKnightmate: /* [HGM] should work */
1205       case VariantCylinder:   /* [HGM] untested */
1206       case VariantFalcon:     /* [HGM] untested */
1207       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1208                                  offboard interposition not understood */
1209       case VariantNormal:     /* definitely works! */
1210       case VariantWildCastle: /* pieces not automatically shuffled */
1211       case VariantNoCastle:   /* pieces not automatically shuffled */
1212       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1213       case VariantLosers:     /* should work except for win condition,
1214                                  and doesn't know captures are mandatory */
1215       case VariantSuicide:    /* should work except for win condition,
1216                                  and doesn't know captures are mandatory */
1217       case VariantGiveaway:   /* should work except for win condition,
1218                                  and doesn't know captures are mandatory */
1219       case VariantTwoKings:   /* should work */
1220       case VariantAtomic:     /* should work except for win condition */
1221       case Variant3Check:     /* should work except for win condition */
1222       case VariantShatranj:   /* should work except for all win conditions */
1223       case VariantMakruk:     /* should work except for draw countdown */
1224       case VariantASEAN :     /* should work except for draw countdown */
1225       case VariantBerolina:   /* might work if TestLegality is off */
1226       case VariantCapaRandom: /* should work */
1227       case VariantJanus:      /* should work */
1228       case VariantSuper:      /* experimental */
1229       case VariantGreat:      /* experimental, requires legality testing to be off */
1230       case VariantSChess:     /* S-Chess, should work */
1231       case VariantGrand:      /* should work */
1232       case VariantSpartan:    /* should work */
1233       case VariantLion:       /* should work */
1234       case VariantChuChess:   /* should work */
1235         break;
1236       }
1237     }
1238
1239 }
1240
1241 int
1242 NextIntegerFromString (char ** str, long * value)
1243 {
1244     int result = -1;
1245     char * s = *str;
1246
1247     while( *s == ' ' || *s == '\t' ) {
1248         s++;
1249     }
1250
1251     *value = 0;
1252
1253     if( *s >= '0' && *s <= '9' ) {
1254         while( *s >= '0' && *s <= '9' ) {
1255             *value = *value * 10 + (*s - '0');
1256             s++;
1257         }
1258
1259         result = 0;
1260     }
1261
1262     *str = s;
1263
1264     return result;
1265 }
1266
1267 int
1268 NextTimeControlFromString (char ** str, long * value)
1269 {
1270     long temp;
1271     int result = NextIntegerFromString( str, &temp );
1272
1273     if( result == 0 ) {
1274         *value = temp * 60; /* Minutes */
1275         if( **str == ':' ) {
1276             (*str)++;
1277             result = NextIntegerFromString( str, &temp );
1278             *value += temp; /* Seconds */
1279         }
1280     }
1281
1282     return result;
1283 }
1284
1285 int
1286 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1287 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1288     int result = -1, type = 0; long temp, temp2;
1289
1290     if(**str != ':') return -1; // old params remain in force!
1291     (*str)++;
1292     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1293     if( NextIntegerFromString( str, &temp ) ) return -1;
1294     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1295
1296     if(**str != '/') {
1297         /* time only: incremental or sudden-death time control */
1298         if(**str == '+') { /* increment follows; read it */
1299             (*str)++;
1300             if(**str == '!') type = *(*str)++; // Bronstein TC
1301             if(result = NextIntegerFromString( str, &temp2)) return -1;
1302             *inc = temp2 * 1000;
1303             if(**str == '.') { // read fraction of increment
1304                 char *start = ++(*str);
1305                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1306                 temp2 *= 1000;
1307                 while(start++ < *str) temp2 /= 10;
1308                 *inc += temp2;
1309             }
1310         } else *inc = 0;
1311         *moves = 0; *tc = temp * 1000; *incType = type;
1312         return 0;
1313     }
1314
1315     (*str)++; /* classical time control */
1316     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1317
1318     if(result == 0) {
1319         *moves = temp;
1320         *tc    = temp2 * 1000;
1321         *inc   = 0;
1322         *incType = type;
1323     }
1324     return result;
1325 }
1326
1327 int
1328 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1329 {   /* [HGM] get time to add from the multi-session time-control string */
1330     int incType, moves=1; /* kludge to force reading of first session */
1331     long time, increment;
1332     char *s = tcString;
1333
1334     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1335     do {
1336         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1337         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1338         if(movenr == -1) return time;    /* last move before new session     */
1339         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1340         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1341         if(!moves) return increment;     /* current session is incremental   */
1342         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1343     } while(movenr >= -1);               /* try again for next session       */
1344
1345     return 0; // no new time quota on this move
1346 }
1347
1348 int
1349 ParseTimeControl (char *tc, float ti, int mps)
1350 {
1351   long tc1;
1352   long tc2;
1353   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1354   int min, sec=0;
1355
1356   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1357   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1358       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1359   if(ti > 0) {
1360
1361     if(mps)
1362       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1363     else
1364       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1365   } else {
1366     if(mps)
1367       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1368     else
1369       snprintf(buf, MSG_SIZ, ":%s", mytc);
1370   }
1371   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1372
1373   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1374     return FALSE;
1375   }
1376
1377   if( *tc == '/' ) {
1378     /* Parse second time control */
1379     tc++;
1380
1381     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1382       return FALSE;
1383     }
1384
1385     if( tc2 == 0 ) {
1386       return FALSE;
1387     }
1388
1389     timeControl_2 = tc2 * 1000;
1390   }
1391   else {
1392     timeControl_2 = 0;
1393   }
1394
1395   if( tc1 == 0 ) {
1396     return FALSE;
1397   }
1398
1399   timeControl = tc1 * 1000;
1400
1401   if (ti >= 0) {
1402     timeIncrement = ti * 1000;  /* convert to ms */
1403     movesPerSession = 0;
1404   } else {
1405     timeIncrement = 0;
1406     movesPerSession = mps;
1407   }
1408   return TRUE;
1409 }
1410
1411 void
1412 InitBackEnd2 ()
1413 {
1414     if (appData.debugMode) {
1415 #    ifdef __GIT_VERSION
1416       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1417 #    else
1418       fprintf(debugFP, "Version: %s\n", programVersion);
1419 #    endif
1420     }
1421     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1422
1423     set_cont_sequence(appData.wrapContSeq);
1424     if (appData.matchGames > 0) {
1425         appData.matchMode = TRUE;
1426     } else if (appData.matchMode) {
1427         appData.matchGames = 1;
1428     }
1429     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1430         appData.matchGames = appData.sameColorGames;
1431     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1432         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1433         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1434     }
1435     Reset(TRUE, FALSE);
1436     if (appData.noChessProgram || first.protocolVersion == 1) {
1437       InitBackEnd3();
1438     } else {
1439       /* kludge: allow timeout for initial "feature" commands */
1440       FreezeUI();
1441       DisplayMessage("", _("Starting chess program"));
1442       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1443     }
1444 }
1445
1446 int
1447 CalculateIndex (int index, int gameNr)
1448 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1449     int res;
1450     if(index > 0) return index; // fixed nmber
1451     if(index == 0) return 1;
1452     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1453     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1454     return res;
1455 }
1456
1457 int
1458 LoadGameOrPosition (int gameNr)
1459 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1460     if (*appData.loadGameFile != NULLCHAR) {
1461         if (!LoadGameFromFile(appData.loadGameFile,
1462                 CalculateIndex(appData.loadGameIndex, gameNr),
1463                               appData.loadGameFile, FALSE)) {
1464             DisplayFatalError(_("Bad game file"), 0, 1);
1465             return 0;
1466         }
1467     } else if (*appData.loadPositionFile != NULLCHAR) {
1468         if (!LoadPositionFromFile(appData.loadPositionFile,
1469                 CalculateIndex(appData.loadPositionIndex, gameNr),
1470                                   appData.loadPositionFile)) {
1471             DisplayFatalError(_("Bad position file"), 0, 1);
1472             return 0;
1473         }
1474     }
1475     return 1;
1476 }
1477
1478 void
1479 ReserveGame (int gameNr, char resChar)
1480 {
1481     FILE *tf = fopen(appData.tourneyFile, "r+");
1482     char *p, *q, c, buf[MSG_SIZ];
1483     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1484     safeStrCpy(buf, lastMsg, MSG_SIZ);
1485     DisplayMessage(_("Pick new game"), "");
1486     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1487     ParseArgsFromFile(tf);
1488     p = q = appData.results;
1489     if(appData.debugMode) {
1490       char *r = appData.participants;
1491       fprintf(debugFP, "results = '%s'\n", p);
1492       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1493       fprintf(debugFP, "\n");
1494     }
1495     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1496     nextGame = q - p;
1497     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1498     safeStrCpy(q, p, strlen(p) + 2);
1499     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1500     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1501     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1502         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1503         q[nextGame] = '*';
1504     }
1505     fseek(tf, -(strlen(p)+4), SEEK_END);
1506     c = fgetc(tf);
1507     if(c != '"') // depending on DOS or Unix line endings we can be one off
1508          fseek(tf, -(strlen(p)+2), SEEK_END);
1509     else fseek(tf, -(strlen(p)+3), SEEK_END);
1510     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1511     DisplayMessage(buf, "");
1512     free(p); appData.results = q;
1513     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1514        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1515       int round = appData.defaultMatchGames * appData.tourneyType;
1516       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1517          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1518         UnloadEngine(&first);  // next game belongs to other pairing;
1519         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1520     }
1521     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1522 }
1523
1524 void
1525 MatchEvent (int mode)
1526 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1527         int dummy;
1528         if(matchMode) { // already in match mode: switch it off
1529             abortMatch = TRUE;
1530             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1531             return;
1532         }
1533 //      if(gameMode != BeginningOfGame) {
1534 //          DisplayError(_("You can only start a match from the initial position."), 0);
1535 //          return;
1536 //      }
1537         abortMatch = FALSE;
1538         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1539         /* Set up machine vs. machine match */
1540         nextGame = 0;
1541         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1542         if(appData.tourneyFile[0]) {
1543             ReserveGame(-1, 0);
1544             if(nextGame > appData.matchGames) {
1545                 char buf[MSG_SIZ];
1546                 if(strchr(appData.results, '*') == NULL) {
1547                     FILE *f;
1548                     appData.tourneyCycles++;
1549                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1550                         fclose(f);
1551                         NextTourneyGame(-1, &dummy);
1552                         ReserveGame(-1, 0);
1553                         if(nextGame <= appData.matchGames) {
1554                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1555                             matchMode = mode;
1556                             ScheduleDelayedEvent(NextMatchGame, 10000);
1557                             return;
1558                         }
1559                     }
1560                 }
1561                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1562                 DisplayError(buf, 0);
1563                 appData.tourneyFile[0] = 0;
1564                 return;
1565             }
1566         } else
1567         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1568             DisplayFatalError(_("Can't have a match with no chess programs"),
1569                               0, 2);
1570             return;
1571         }
1572         matchMode = mode;
1573         matchGame = roundNr = 1;
1574         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1575         NextMatchGame();
1576 }
1577
1578 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1579
1580 void
1581 InitBackEnd3 P((void))
1582 {
1583     GameMode initialMode;
1584     char buf[MSG_SIZ];
1585     int err, len;
1586
1587     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1588        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1589         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1590        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1591        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1592         char c, *q = first.variants, *p = strchr(q, ',');
1593         if(p) *p = NULLCHAR;
1594         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1595             int w, h, s;
1596             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1597                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1598             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1599             Reset(TRUE, FALSE);         // and re-initialize
1600         }
1601         if(p) *p = ',';
1602     }
1603
1604     InitChessProgram(&first, startedFromSetupPosition);
1605
1606     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1607         free(programVersion);
1608         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1609         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1610         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1611     }
1612
1613     if (appData.icsActive) {
1614 #ifdef WIN32
1615         /* [DM] Make a console window if needed [HGM] merged ifs */
1616         ConsoleCreate();
1617 #endif
1618         err = establish();
1619         if (err != 0)
1620           {
1621             if (*appData.icsCommPort != NULLCHAR)
1622               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1623                              appData.icsCommPort);
1624             else
1625               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1626                         appData.icsHost, appData.icsPort);
1627
1628             if( (len >= MSG_SIZ) && appData.debugMode )
1629               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1630
1631             DisplayFatalError(buf, err, 1);
1632             return;
1633         }
1634         SetICSMode();
1635         telnetISR =
1636           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1637         fromUserISR =
1638           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1639         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1640             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1641     } else if (appData.noChessProgram) {
1642         SetNCPMode();
1643     } else {
1644         SetGNUMode();
1645     }
1646
1647     if (*appData.cmailGameName != NULLCHAR) {
1648         SetCmailMode();
1649         OpenLoopback(&cmailPR);
1650         cmailISR =
1651           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1652     }
1653
1654     ThawUI();
1655     DisplayMessage("", "");
1656     if (StrCaseCmp(appData.initialMode, "") == 0) {
1657       initialMode = BeginningOfGame;
1658       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1659         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1660         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1661         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1662         ModeHighlight();
1663       }
1664     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1665       initialMode = TwoMachinesPlay;
1666     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1667       initialMode = AnalyzeFile;
1668     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1669       initialMode = AnalyzeMode;
1670     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1671       initialMode = MachinePlaysWhite;
1672     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1673       initialMode = MachinePlaysBlack;
1674     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1675       initialMode = EditGame;
1676     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1677       initialMode = EditPosition;
1678     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1679       initialMode = Training;
1680     } else {
1681       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1682       if( (len >= MSG_SIZ) && appData.debugMode )
1683         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1684
1685       DisplayFatalError(buf, 0, 2);
1686       return;
1687     }
1688
1689     if (appData.matchMode) {
1690         if(appData.tourneyFile[0]) { // start tourney from command line
1691             FILE *f;
1692             if(f = fopen(appData.tourneyFile, "r")) {
1693                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1694                 fclose(f);
1695                 appData.clockMode = TRUE;
1696                 SetGNUMode();
1697             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1698         }
1699         MatchEvent(TRUE);
1700     } else if (*appData.cmailGameName != NULLCHAR) {
1701         /* Set up cmail mode */
1702         ReloadCmailMsgEvent(TRUE);
1703     } else {
1704         /* Set up other modes */
1705         if (initialMode == AnalyzeFile) {
1706           if (*appData.loadGameFile == NULLCHAR) {
1707             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1708             return;
1709           }
1710         }
1711         if (*appData.loadGameFile != NULLCHAR) {
1712             (void) LoadGameFromFile(appData.loadGameFile,
1713                                     appData.loadGameIndex,
1714                                     appData.loadGameFile, TRUE);
1715         } else if (*appData.loadPositionFile != NULLCHAR) {
1716             (void) LoadPositionFromFile(appData.loadPositionFile,
1717                                         appData.loadPositionIndex,
1718                                         appData.loadPositionFile);
1719             /* [HGM] try to make self-starting even after FEN load */
1720             /* to allow automatic setup of fairy variants with wtm */
1721             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1722                 gameMode = BeginningOfGame;
1723                 setboardSpoiledMachineBlack = 1;
1724             }
1725             /* [HGM] loadPos: make that every new game uses the setup */
1726             /* from file as long as we do not switch variant          */
1727             if(!blackPlaysFirst) {
1728                 startedFromPositionFile = TRUE;
1729                 CopyBoard(filePosition, boards[0]);
1730             }
1731         }
1732         if (initialMode == AnalyzeMode) {
1733           if (appData.noChessProgram) {
1734             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1735             return;
1736           }
1737           if (appData.icsActive) {
1738             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1739             return;
1740           }
1741           AnalyzeModeEvent();
1742         } else if (initialMode == AnalyzeFile) {
1743           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1744           ShowThinkingEvent();
1745           AnalyzeFileEvent();
1746           AnalysisPeriodicEvent(1);
1747         } else if (initialMode == MachinePlaysWhite) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1750                               0, 2);
1751             return;
1752           }
1753           if (appData.icsActive) {
1754             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1755                               0, 2);
1756             return;
1757           }
1758           MachineWhiteEvent();
1759         } else if (initialMode == MachinePlaysBlack) {
1760           if (appData.noChessProgram) {
1761             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1762                               0, 2);
1763             return;
1764           }
1765           if (appData.icsActive) {
1766             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1767                               0, 2);
1768             return;
1769           }
1770           MachineBlackEvent();
1771         } else if (initialMode == TwoMachinesPlay) {
1772           if (appData.noChessProgram) {
1773             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1774                               0, 2);
1775             return;
1776           }
1777           if (appData.icsActive) {
1778             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1779                               0, 2);
1780             return;
1781           }
1782           TwoMachinesEvent();
1783         } else if (initialMode == EditGame) {
1784           EditGameEvent();
1785         } else if (initialMode == EditPosition) {
1786           EditPositionEvent();
1787         } else if (initialMode == Training) {
1788           if (*appData.loadGameFile == NULLCHAR) {
1789             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1790             return;
1791           }
1792           TrainingEvent();
1793         }
1794     }
1795 }
1796
1797 void
1798 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1799 {
1800     DisplayBook(current+1);
1801
1802     MoveHistorySet( movelist, first, last, current, pvInfoList );
1803
1804     EvalGraphSet( first, last, current, pvInfoList );
1805
1806     MakeEngineOutputTitle();
1807 }
1808
1809 /*
1810  * Establish will establish a contact to a remote host.port.
1811  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1812  *  used to talk to the host.
1813  * Returns 0 if okay, error code if not.
1814  */
1815 int
1816 establish ()
1817 {
1818     char buf[MSG_SIZ];
1819
1820     if (*appData.icsCommPort != NULLCHAR) {
1821         /* Talk to the host through a serial comm port */
1822         return OpenCommPort(appData.icsCommPort, &icsPR);
1823
1824     } else if (*appData.gateway != NULLCHAR) {
1825         if (*appData.remoteShell == NULLCHAR) {
1826             /* Use the rcmd protocol to run telnet program on a gateway host */
1827             snprintf(buf, sizeof(buf), "%s %s %s",
1828                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1829             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1830
1831         } else {
1832             /* Use the rsh program to run telnet program on a gateway host */
1833             if (*appData.remoteUser == NULLCHAR) {
1834                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1835                         appData.gateway, appData.telnetProgram,
1836                         appData.icsHost, appData.icsPort);
1837             } else {
1838                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1839                         appData.remoteShell, appData.gateway,
1840                         appData.remoteUser, appData.telnetProgram,
1841                         appData.icsHost, appData.icsPort);
1842             }
1843             return StartChildProcess(buf, "", &icsPR);
1844
1845         }
1846     } else if (appData.useTelnet) {
1847         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1848
1849     } else {
1850         /* TCP socket interface differs somewhat between
1851            Unix and NT; handle details in the front end.
1852            */
1853         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1854     }
1855 }
1856
1857 void
1858 EscapeExpand (char *p, char *q)
1859 {       // [HGM] initstring: routine to shape up string arguments
1860         while(*p++ = *q++) if(p[-1] == '\\')
1861             switch(*q++) {
1862                 case 'n': p[-1] = '\n'; break;
1863                 case 'r': p[-1] = '\r'; break;
1864                 case 't': p[-1] = '\t'; break;
1865                 case '\\': p[-1] = '\\'; break;
1866                 case 0: *p = 0; return;
1867                 default: p[-1] = q[-1]; break;
1868             }
1869 }
1870
1871 void
1872 show_bytes (FILE *fp, char *buf, int count)
1873 {
1874     while (count--) {
1875         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1876             fprintf(fp, "\\%03o", *buf & 0xff);
1877         } else {
1878             putc(*buf, fp);
1879         }
1880         buf++;
1881     }
1882     fflush(fp);
1883 }
1884
1885 /* Returns an errno value */
1886 int
1887 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1888 {
1889     char buf[8192], *p, *q, *buflim;
1890     int left, newcount, outcount;
1891
1892     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1893         *appData.gateway != NULLCHAR) {
1894         if (appData.debugMode) {
1895             fprintf(debugFP, ">ICS: ");
1896             show_bytes(debugFP, message, count);
1897             fprintf(debugFP, "\n");
1898         }
1899         return OutputToProcess(pr, message, count, outError);
1900     }
1901
1902     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1903     p = message;
1904     q = buf;
1905     left = count;
1906     newcount = 0;
1907     while (left) {
1908         if (q >= buflim) {
1909             if (appData.debugMode) {
1910                 fprintf(debugFP, ">ICS: ");
1911                 show_bytes(debugFP, buf, newcount);
1912                 fprintf(debugFP, "\n");
1913             }
1914             outcount = OutputToProcess(pr, buf, newcount, outError);
1915             if (outcount < newcount) return -1; /* to be sure */
1916             q = buf;
1917             newcount = 0;
1918         }
1919         if (*p == '\n') {
1920             *q++ = '\r';
1921             newcount++;
1922         } else if (((unsigned char) *p) == TN_IAC) {
1923             *q++ = (char) TN_IAC;
1924             newcount ++;
1925         }
1926         *q++ = *p++;
1927         newcount++;
1928         left--;
1929     }
1930     if (appData.debugMode) {
1931         fprintf(debugFP, ">ICS: ");
1932         show_bytes(debugFP, buf, newcount);
1933         fprintf(debugFP, "\n");
1934     }
1935     outcount = OutputToProcess(pr, buf, newcount, outError);
1936     if (outcount < newcount) return -1; /* to be sure */
1937     return count;
1938 }
1939
1940 void
1941 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1942 {
1943     int outError, outCount;
1944     static int gotEof = 0;
1945     static FILE *ini;
1946
1947     /* Pass data read from player on to ICS */
1948     if (count > 0) {
1949         gotEof = 0;
1950         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1951         if (outCount < count) {
1952             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1953         }
1954         if(have_sent_ICS_logon == 2) {
1955           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1956             fprintf(ini, "%s", message);
1957             have_sent_ICS_logon = 3;
1958           } else
1959             have_sent_ICS_logon = 1;
1960         } else if(have_sent_ICS_logon == 3) {
1961             fprintf(ini, "%s", message);
1962             fclose(ini);
1963           have_sent_ICS_logon = 1;
1964         }
1965     } else if (count < 0) {
1966         RemoveInputSource(isr);
1967         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1968     } else if (gotEof++ > 0) {
1969         RemoveInputSource(isr);
1970         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1971     }
1972 }
1973
1974 void
1975 KeepAlive ()
1976 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1977     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1978     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1979     SendToICS("date\n");
1980     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1981 }
1982
1983 /* added routine for printf style output to ics */
1984 void
1985 ics_printf (char *format, ...)
1986 {
1987     char buffer[MSG_SIZ];
1988     va_list args;
1989
1990     va_start(args, format);
1991     vsnprintf(buffer, sizeof(buffer), format, args);
1992     buffer[sizeof(buffer)-1] = '\0';
1993     SendToICS(buffer);
1994     va_end(args);
1995 }
1996
1997 void
1998 SendToICS (char *s)
1999 {
2000     int count, outCount, outError;
2001
2002     if (icsPR == NoProc) return;
2003
2004     count = strlen(s);
2005     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2006     if (outCount < count) {
2007         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2008     }
2009 }
2010
2011 /* This is used for sending logon scripts to the ICS. Sending
2012    without a delay causes problems when using timestamp on ICC
2013    (at least on my machine). */
2014 void
2015 SendToICSDelayed (char *s, long msdelay)
2016 {
2017     int count, outCount, outError;
2018
2019     if (icsPR == NoProc) return;
2020
2021     count = strlen(s);
2022     if (appData.debugMode) {
2023         fprintf(debugFP, ">ICS: ");
2024         show_bytes(debugFP, s, count);
2025         fprintf(debugFP, "\n");
2026     }
2027     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2028                                       msdelay);
2029     if (outCount < count) {
2030         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2031     }
2032 }
2033
2034
2035 /* Remove all highlighting escape sequences in s
2036    Also deletes any suffix starting with '('
2037    */
2038 char *
2039 StripHighlightAndTitle (char *s)
2040 {
2041     static char retbuf[MSG_SIZ];
2042     char *p = retbuf;
2043
2044     while (*s != NULLCHAR) {
2045         while (*s == '\033') {
2046             while (*s != NULLCHAR && !isalpha(*s)) s++;
2047             if (*s != NULLCHAR) s++;
2048         }
2049         while (*s != NULLCHAR && *s != '\033') {
2050             if (*s == '(' || *s == '[') {
2051                 *p = NULLCHAR;
2052                 return retbuf;
2053             }
2054             *p++ = *s++;
2055         }
2056     }
2057     *p = NULLCHAR;
2058     return retbuf;
2059 }
2060
2061 /* Remove all highlighting escape sequences in s */
2062 char *
2063 StripHighlight (char *s)
2064 {
2065     static char retbuf[MSG_SIZ];
2066     char *p = retbuf;
2067
2068     while (*s != NULLCHAR) {
2069         while (*s == '\033') {
2070             while (*s != NULLCHAR && !isalpha(*s)) s++;
2071             if (*s != NULLCHAR) s++;
2072         }
2073         while (*s != NULLCHAR && *s != '\033') {
2074             *p++ = *s++;
2075         }
2076     }
2077     *p = NULLCHAR;
2078     return retbuf;
2079 }
2080
2081 char engineVariant[MSG_SIZ];
2082 char *variantNames[] = VARIANT_NAMES;
2083 char *
2084 VariantName (VariantClass v)
2085 {
2086     if(v == VariantUnknown || *engineVariant) return engineVariant;
2087     return variantNames[v];
2088 }
2089
2090
2091 /* Identify a variant from the strings the chess servers use or the
2092    PGN Variant tag names we use. */
2093 VariantClass
2094 StringToVariant (char *e)
2095 {
2096     char *p;
2097     int wnum = -1;
2098     VariantClass v = VariantNormal;
2099     int i, found = FALSE;
2100     char buf[MSG_SIZ];
2101     int len;
2102
2103     if (!e) return v;
2104
2105     /* [HGM] skip over optional board-size prefixes */
2106     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2107         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2108         while( *e++ != '_');
2109     }
2110
2111     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2112         v = VariantNormal;
2113         found = TRUE;
2114     } else
2115     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2116       if (p = StrCaseStr(e, variantNames[i])) {
2117         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2118         v = (VariantClass) i;
2119         found = TRUE;
2120         break;
2121       }
2122     }
2123
2124     if (!found) {
2125       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2126           || StrCaseStr(e, "wild/fr")
2127           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2128         v = VariantFischeRandom;
2129       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2130                  (i = 1, p = StrCaseStr(e, "w"))) {
2131         p += i;
2132         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2133         if (isdigit(*p)) {
2134           wnum = atoi(p);
2135         } else {
2136           wnum = -1;
2137         }
2138         switch (wnum) {
2139         case 0: /* FICS only, actually */
2140         case 1:
2141           /* Castling legal even if K starts on d-file */
2142           v = VariantWildCastle;
2143           break;
2144         case 2:
2145         case 3:
2146         case 4:
2147           /* Castling illegal even if K & R happen to start in
2148              normal positions. */
2149           v = VariantNoCastle;
2150           break;
2151         case 5:
2152         case 7:
2153         case 8:
2154         case 10:
2155         case 11:
2156         case 12:
2157         case 13:
2158         case 14:
2159         case 15:
2160         case 18:
2161         case 19:
2162           /* Castling legal iff K & R start in normal positions */
2163           v = VariantNormal;
2164           break;
2165         case 6:
2166         case 20:
2167         case 21:
2168           /* Special wilds for position setup; unclear what to do here */
2169           v = VariantLoadable;
2170           break;
2171         case 9:
2172           /* Bizarre ICC game */
2173           v = VariantTwoKings;
2174           break;
2175         case 16:
2176           v = VariantKriegspiel;
2177           break;
2178         case 17:
2179           v = VariantLosers;
2180           break;
2181         case 22:
2182           v = VariantFischeRandom;
2183           break;
2184         case 23:
2185           v = VariantCrazyhouse;
2186           break;
2187         case 24:
2188           v = VariantBughouse;
2189           break;
2190         case 25:
2191           v = Variant3Check;
2192           break;
2193         case 26:
2194           /* Not quite the same as FICS suicide! */
2195           v = VariantGiveaway;
2196           break;
2197         case 27:
2198           v = VariantAtomic;
2199           break;
2200         case 28:
2201           v = VariantShatranj;
2202           break;
2203
2204         /* Temporary names for future ICC types.  The name *will* change in
2205            the next xboard/WinBoard release after ICC defines it. */
2206         case 29:
2207           v = Variant29;
2208           break;
2209         case 30:
2210           v = Variant30;
2211           break;
2212         case 31:
2213           v = Variant31;
2214           break;
2215         case 32:
2216           v = Variant32;
2217           break;
2218         case 33:
2219           v = Variant33;
2220           break;
2221         case 34:
2222           v = Variant34;
2223           break;
2224         case 35:
2225           v = Variant35;
2226           break;
2227         case 36:
2228           v = Variant36;
2229           break;
2230         case 37:
2231           v = VariantShogi;
2232           break;
2233         case 38:
2234           v = VariantXiangqi;
2235           break;
2236         case 39:
2237           v = VariantCourier;
2238           break;
2239         case 40:
2240           v = VariantGothic;
2241           break;
2242         case 41:
2243           v = VariantCapablanca;
2244           break;
2245         case 42:
2246           v = VariantKnightmate;
2247           break;
2248         case 43:
2249           v = VariantFairy;
2250           break;
2251         case 44:
2252           v = VariantCylinder;
2253           break;
2254         case 45:
2255           v = VariantFalcon;
2256           break;
2257         case 46:
2258           v = VariantCapaRandom;
2259           break;
2260         case 47:
2261           v = VariantBerolina;
2262           break;
2263         case 48:
2264           v = VariantJanus;
2265           break;
2266         case 49:
2267           v = VariantSuper;
2268           break;
2269         case 50:
2270           v = VariantGreat;
2271           break;
2272         case -1:
2273           /* Found "wild" or "w" in the string but no number;
2274              must assume it's normal chess. */
2275           v = VariantNormal;
2276           break;
2277         default:
2278           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2279           if( (len >= MSG_SIZ) && appData.debugMode )
2280             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2281
2282           DisplayError(buf, 0);
2283           v = VariantUnknown;
2284           break;
2285         }
2286       }
2287     }
2288     if (appData.debugMode) {
2289       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2290               e, wnum, VariantName(v));
2291     }
2292     return v;
2293 }
2294
2295 static int leftover_start = 0, leftover_len = 0;
2296 char star_match[STAR_MATCH_N][MSG_SIZ];
2297
2298 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2299    advance *index beyond it, and set leftover_start to the new value of
2300    *index; else return FALSE.  If pattern contains the character '*', it
2301    matches any sequence of characters not containing '\r', '\n', or the
2302    character following the '*' (if any), and the matched sequence(s) are
2303    copied into star_match.
2304    */
2305 int
2306 looking_at ( char *buf, int *index, char *pattern)
2307 {
2308     char *bufp = &buf[*index], *patternp = pattern;
2309     int star_count = 0;
2310     char *matchp = star_match[0];
2311
2312     for (;;) {
2313         if (*patternp == NULLCHAR) {
2314             *index = leftover_start = bufp - buf;
2315             *matchp = NULLCHAR;
2316             return TRUE;
2317         }
2318         if (*bufp == NULLCHAR) return FALSE;
2319         if (*patternp == '*') {
2320             if (*bufp == *(patternp + 1)) {
2321                 *matchp = NULLCHAR;
2322                 matchp = star_match[++star_count];
2323                 patternp += 2;
2324                 bufp++;
2325                 continue;
2326             } else if (*bufp == '\n' || *bufp == '\r') {
2327                 patternp++;
2328                 if (*patternp == NULLCHAR)
2329                   continue;
2330                 else
2331                   return FALSE;
2332             } else {
2333                 *matchp++ = *bufp++;
2334                 continue;
2335             }
2336         }
2337         if (*patternp != *bufp) return FALSE;
2338         patternp++;
2339         bufp++;
2340     }
2341 }
2342
2343 void
2344 SendToPlayer (char *data, int length)
2345 {
2346     int error, outCount;
2347     outCount = OutputToProcess(NoProc, data, length, &error);
2348     if (outCount < length) {
2349         DisplayFatalError(_("Error writing to display"), error, 1);
2350     }
2351 }
2352
2353 void
2354 PackHolding (char packed[], char *holding)
2355 {
2356     char *p = holding;
2357     char *q = packed;
2358     int runlength = 0;
2359     int curr = 9999;
2360     do {
2361         if (*p == curr) {
2362             runlength++;
2363         } else {
2364             switch (runlength) {
2365               case 0:
2366                 break;
2367               case 1:
2368                 *q++ = curr;
2369                 break;
2370               case 2:
2371                 *q++ = curr;
2372                 *q++ = curr;
2373                 break;
2374               default:
2375                 sprintf(q, "%d", runlength);
2376                 while (*q) q++;
2377                 *q++ = curr;
2378                 break;
2379             }
2380             runlength = 1;
2381             curr = *p;
2382         }
2383     } while (*p++);
2384     *q = NULLCHAR;
2385 }
2386
2387 /* Telnet protocol requests from the front end */
2388 void
2389 TelnetRequest (unsigned char ddww, unsigned char option)
2390 {
2391     unsigned char msg[3];
2392     int outCount, outError;
2393
2394     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2395
2396     if (appData.debugMode) {
2397         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2398         switch (ddww) {
2399           case TN_DO:
2400             ddwwStr = "DO";
2401             break;
2402           case TN_DONT:
2403             ddwwStr = "DONT";
2404             break;
2405           case TN_WILL:
2406             ddwwStr = "WILL";
2407             break;
2408           case TN_WONT:
2409             ddwwStr = "WONT";
2410             break;
2411           default:
2412             ddwwStr = buf1;
2413             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2414             break;
2415         }
2416         switch (option) {
2417           case TN_ECHO:
2418             optionStr = "ECHO";
2419             break;
2420           default:
2421             optionStr = buf2;
2422             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2423             break;
2424         }
2425         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2426     }
2427     msg[0] = TN_IAC;
2428     msg[1] = ddww;
2429     msg[2] = option;
2430     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2431     if (outCount < 3) {
2432         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2433     }
2434 }
2435
2436 void
2437 DoEcho ()
2438 {
2439     if (!appData.icsActive) return;
2440     TelnetRequest(TN_DO, TN_ECHO);
2441 }
2442
2443 void
2444 DontEcho ()
2445 {
2446     if (!appData.icsActive) return;
2447     TelnetRequest(TN_DONT, TN_ECHO);
2448 }
2449
2450 void
2451 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2452 {
2453     /* put the holdings sent to us by the server on the board holdings area */
2454     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2455     char p;
2456     ChessSquare piece;
2457
2458     if(gameInfo.holdingsWidth < 2)  return;
2459     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2460         return; // prevent overwriting by pre-board holdings
2461
2462     if( (int)lowestPiece >= BlackPawn ) {
2463         holdingsColumn = 0;
2464         countsColumn = 1;
2465         holdingsStartRow = BOARD_HEIGHT-1;
2466         direction = -1;
2467     } else {
2468         holdingsColumn = BOARD_WIDTH-1;
2469         countsColumn = BOARD_WIDTH-2;
2470         holdingsStartRow = 0;
2471         direction = 1;
2472     }
2473
2474     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2475         board[i][holdingsColumn] = EmptySquare;
2476         board[i][countsColumn]   = (ChessSquare) 0;
2477     }
2478     while( (p=*holdings++) != NULLCHAR ) {
2479         piece = CharToPiece( ToUpper(p) );
2480         if(piece == EmptySquare) continue;
2481         /*j = (int) piece - (int) WhitePawn;*/
2482         j = PieceToNumber(piece);
2483         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2484         if(j < 0) continue;               /* should not happen */
2485         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2486         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2487         board[holdingsStartRow+j*direction][countsColumn]++;
2488     }
2489 }
2490
2491
2492 void
2493 VariantSwitch (Board board, VariantClass newVariant)
2494 {
2495    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2496    static Board oldBoard;
2497
2498    startedFromPositionFile = FALSE;
2499    if(gameInfo.variant == newVariant) return;
2500
2501    /* [HGM] This routine is called each time an assignment is made to
2502     * gameInfo.variant during a game, to make sure the board sizes
2503     * are set to match the new variant. If that means adding or deleting
2504     * holdings, we shift the playing board accordingly
2505     * This kludge is needed because in ICS observe mode, we get boards
2506     * of an ongoing game without knowing the variant, and learn about the
2507     * latter only later. This can be because of the move list we requested,
2508     * in which case the game history is refilled from the beginning anyway,
2509     * but also when receiving holdings of a crazyhouse game. In the latter
2510     * case we want to add those holdings to the already received position.
2511     */
2512
2513
2514    if (appData.debugMode) {
2515      fprintf(debugFP, "Switch board from %s to %s\n",
2516              VariantName(gameInfo.variant), VariantName(newVariant));
2517      setbuf(debugFP, NULL);
2518    }
2519    shuffleOpenings = 0;       /* [HGM] shuffle */
2520    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2521    switch(newVariant)
2522      {
2523      case VariantShogi:
2524        newWidth = 9;  newHeight = 9;
2525        gameInfo.holdingsSize = 7;
2526      case VariantBughouse:
2527      case VariantCrazyhouse:
2528        newHoldingsWidth = 2; break;
2529      case VariantGreat:
2530        newWidth = 10;
2531      case VariantSuper:
2532        newHoldingsWidth = 2;
2533        gameInfo.holdingsSize = 8;
2534        break;
2535      case VariantGothic:
2536      case VariantCapablanca:
2537      case VariantCapaRandom:
2538        newWidth = 10;
2539      default:
2540        newHoldingsWidth = gameInfo.holdingsSize = 0;
2541      };
2542
2543    if(newWidth  != gameInfo.boardWidth  ||
2544       newHeight != gameInfo.boardHeight ||
2545       newHoldingsWidth != gameInfo.holdingsWidth ) {
2546
2547      /* shift position to new playing area, if needed */
2548      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2549        for(i=0; i<BOARD_HEIGHT; i++)
2550          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2551            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2552              board[i][j];
2553        for(i=0; i<newHeight; i++) {
2554          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2555          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2556        }
2557      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2558        for(i=0; i<BOARD_HEIGHT; i++)
2559          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2560            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2561              board[i][j];
2562      }
2563      board[HOLDINGS_SET] = 0;
2564      gameInfo.boardWidth  = newWidth;
2565      gameInfo.boardHeight = newHeight;
2566      gameInfo.holdingsWidth = newHoldingsWidth;
2567      gameInfo.variant = newVariant;
2568      InitDrawingSizes(-2, 0);
2569    } else gameInfo.variant = newVariant;
2570    CopyBoard(oldBoard, board);   // remember correctly formatted board
2571      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2572    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2573 }
2574
2575 static int loggedOn = FALSE;
2576
2577 /*-- Game start info cache: --*/
2578 int gs_gamenum;
2579 char gs_kind[MSG_SIZ];
2580 static char player1Name[128] = "";
2581 static char player2Name[128] = "";
2582 static char cont_seq[] = "\n\\   ";
2583 static int player1Rating = -1;
2584 static int player2Rating = -1;
2585 /*----------------------------*/
2586
2587 ColorClass curColor = ColorNormal;
2588 int suppressKibitz = 0;
2589
2590 // [HGM] seekgraph
2591 Boolean soughtPending = FALSE;
2592 Boolean seekGraphUp;
2593 #define MAX_SEEK_ADS 200
2594 #define SQUARE 0x80
2595 char *seekAdList[MAX_SEEK_ADS];
2596 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2597 float tcList[MAX_SEEK_ADS];
2598 char colorList[MAX_SEEK_ADS];
2599 int nrOfSeekAds = 0;
2600 int minRating = 1010, maxRating = 2800;
2601 int hMargin = 10, vMargin = 20, h, w;
2602 extern int squareSize, lineGap;
2603
2604 void
2605 PlotSeekAd (int i)
2606 {
2607         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2608         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2609         if(r < minRating+100 && r >=0 ) r = minRating+100;
2610         if(r > maxRating) r = maxRating;
2611         if(tc < 1.f) tc = 1.f;
2612         if(tc > 95.f) tc = 95.f;
2613         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2614         y = ((double)r - minRating)/(maxRating - minRating)
2615             * (h-vMargin-squareSize/8-1) + vMargin;
2616         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2617         if(strstr(seekAdList[i], " u ")) color = 1;
2618         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2619            !strstr(seekAdList[i], "bullet") &&
2620            !strstr(seekAdList[i], "blitz") &&
2621            !strstr(seekAdList[i], "standard") ) color = 2;
2622         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2623         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2624 }
2625
2626 void
2627 PlotSingleSeekAd (int i)
2628 {
2629         PlotSeekAd(i);
2630 }
2631
2632 void
2633 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2634 {
2635         char buf[MSG_SIZ], *ext = "";
2636         VariantClass v = StringToVariant(type);
2637         if(strstr(type, "wild")) {
2638             ext = type + 4; // append wild number
2639             if(v == VariantFischeRandom) type = "chess960"; else
2640             if(v == VariantLoadable) type = "setup"; else
2641             type = VariantName(v);
2642         }
2643         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2644         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2645             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2646             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2647             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2648             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2649             seekNrList[nrOfSeekAds] = nr;
2650             zList[nrOfSeekAds] = 0;
2651             seekAdList[nrOfSeekAds++] = StrSave(buf);
2652             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2653         }
2654 }
2655
2656 void
2657 EraseSeekDot (int i)
2658 {
2659     int x = xList[i], y = yList[i], d=squareSize/4, k;
2660     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2661     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2662     // now replot every dot that overlapped
2663     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2664         int xx = xList[k], yy = yList[k];
2665         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2666             DrawSeekDot(xx, yy, colorList[k]);
2667     }
2668 }
2669
2670 void
2671 RemoveSeekAd (int nr)
2672 {
2673         int i;
2674         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2675             EraseSeekDot(i);
2676             if(seekAdList[i]) free(seekAdList[i]);
2677             seekAdList[i] = seekAdList[--nrOfSeekAds];
2678             seekNrList[i] = seekNrList[nrOfSeekAds];
2679             ratingList[i] = ratingList[nrOfSeekAds];
2680             colorList[i]  = colorList[nrOfSeekAds];
2681             tcList[i] = tcList[nrOfSeekAds];
2682             xList[i]  = xList[nrOfSeekAds];
2683             yList[i]  = yList[nrOfSeekAds];
2684             zList[i]  = zList[nrOfSeekAds];
2685             seekAdList[nrOfSeekAds] = NULL;
2686             break;
2687         }
2688 }
2689
2690 Boolean
2691 MatchSoughtLine (char *line)
2692 {
2693     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2694     int nr, base, inc, u=0; char dummy;
2695
2696     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2697        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2698        (u=1) &&
2699        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2700         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2701         // match: compact and save the line
2702         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2703         return TRUE;
2704     }
2705     return FALSE;
2706 }
2707
2708 int
2709 DrawSeekGraph ()
2710 {
2711     int i;
2712     if(!seekGraphUp) return FALSE;
2713     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2714     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2715
2716     DrawSeekBackground(0, 0, w, h);
2717     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2718     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2719     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2720         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2721         yy = h-1-yy;
2722         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2723         if(i%500 == 0) {
2724             char buf[MSG_SIZ];
2725             snprintf(buf, MSG_SIZ, "%d", i);
2726             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2727         }
2728     }
2729     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2730     for(i=1; i<100; i+=(i<10?1:5)) {
2731         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2732         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2733         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2734             char buf[MSG_SIZ];
2735             snprintf(buf, MSG_SIZ, "%d", i);
2736             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2737         }
2738     }
2739     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2740     return TRUE;
2741 }
2742
2743 int
2744 SeekGraphClick (ClickType click, int x, int y, int moving)
2745 {
2746     static int lastDown = 0, displayed = 0, lastSecond;
2747     if(y < 0) return FALSE;
2748     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2749         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2750         if(!seekGraphUp) return FALSE;
2751         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2752         DrawPosition(TRUE, NULL);
2753         return TRUE;
2754     }
2755     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2756         if(click == Release || moving) return FALSE;
2757         nrOfSeekAds = 0;
2758         soughtPending = TRUE;
2759         SendToICS(ics_prefix);
2760         SendToICS("sought\n"); // should this be "sought all"?
2761     } else { // issue challenge based on clicked ad
2762         int dist = 10000; int i, closest = 0, second = 0;
2763         for(i=0; i<nrOfSeekAds; i++) {
2764             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2765             if(d < dist) { dist = d; closest = i; }
2766             second += (d - zList[i] < 120); // count in-range ads
2767             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2768         }
2769         if(dist < 120) {
2770             char buf[MSG_SIZ];
2771             second = (second > 1);
2772             if(displayed != closest || second != lastSecond) {
2773                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2774                 lastSecond = second; displayed = closest;
2775             }
2776             if(click == Press) {
2777                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2778                 lastDown = closest;
2779                 return TRUE;
2780             } // on press 'hit', only show info
2781             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2782             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2783             SendToICS(ics_prefix);
2784             SendToICS(buf);
2785             return TRUE; // let incoming board of started game pop down the graph
2786         } else if(click == Release) { // release 'miss' is ignored
2787             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2788             if(moving == 2) { // right up-click
2789                 nrOfSeekAds = 0; // refresh graph
2790                 soughtPending = TRUE;
2791                 SendToICS(ics_prefix);
2792                 SendToICS("sought\n"); // should this be "sought all"?
2793             }
2794             return TRUE;
2795         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2796         // press miss or release hit 'pop down' seek graph
2797         seekGraphUp = FALSE;
2798         DrawPosition(TRUE, NULL);
2799     }
2800     return TRUE;
2801 }
2802
2803 void
2804 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2805 {
2806 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2807 #define STARTED_NONE 0
2808 #define STARTED_MOVES 1
2809 #define STARTED_BOARD 2
2810 #define STARTED_OBSERVE 3
2811 #define STARTED_HOLDINGS 4
2812 #define STARTED_CHATTER 5
2813 #define STARTED_COMMENT 6
2814 #define STARTED_MOVES_NOHIDE 7
2815
2816     static int started = STARTED_NONE;
2817     static char parse[20000];
2818     static int parse_pos = 0;
2819     static char buf[BUF_SIZE + 1];
2820     static int firstTime = TRUE, intfSet = FALSE;
2821     static ColorClass prevColor = ColorNormal;
2822     static int savingComment = FALSE;
2823     static int cmatch = 0; // continuation sequence match
2824     char *bp;
2825     char str[MSG_SIZ];
2826     int i, oldi;
2827     int buf_len;
2828     int next_out;
2829     int tkind;
2830     int backup;    /* [DM] For zippy color lines */
2831     char *p;
2832     char talker[MSG_SIZ]; // [HGM] chat
2833     int channel, collective=0;
2834
2835     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2836
2837     if (appData.debugMode) {
2838       if (!error) {
2839         fprintf(debugFP, "<ICS: ");
2840         show_bytes(debugFP, data, count);
2841         fprintf(debugFP, "\n");
2842       }
2843     }
2844
2845     if (appData.debugMode) { int f = forwardMostMove;
2846         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2847                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2848                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2849     }
2850     if (count > 0) {
2851         /* If last read ended with a partial line that we couldn't parse,
2852            prepend it to the new read and try again. */
2853         if (leftover_len > 0) {
2854             for (i=0; i<leftover_len; i++)
2855               buf[i] = buf[leftover_start + i];
2856         }
2857
2858     /* copy new characters into the buffer */
2859     bp = buf + leftover_len;
2860     buf_len=leftover_len;
2861     for (i=0; i<count; i++)
2862     {
2863         // ignore these
2864         if (data[i] == '\r')
2865             continue;
2866
2867         // join lines split by ICS?
2868         if (!appData.noJoin)
2869         {
2870             /*
2871                 Joining just consists of finding matches against the
2872                 continuation sequence, and discarding that sequence
2873                 if found instead of copying it.  So, until a match
2874                 fails, there's nothing to do since it might be the
2875                 complete sequence, and thus, something we don't want
2876                 copied.
2877             */
2878             if (data[i] == cont_seq[cmatch])
2879             {
2880                 cmatch++;
2881                 if (cmatch == strlen(cont_seq))
2882                 {
2883                     cmatch = 0; // complete match.  just reset the counter
2884
2885                     /*
2886                         it's possible for the ICS to not include the space
2887                         at the end of the last word, making our [correct]
2888                         join operation fuse two separate words.  the server
2889                         does this when the space occurs at the width setting.
2890                     */
2891                     if (!buf_len || buf[buf_len-1] != ' ')
2892                     {
2893                         *bp++ = ' ';
2894                         buf_len++;
2895                     }
2896                 }
2897                 continue;
2898             }
2899             else if (cmatch)
2900             {
2901                 /*
2902                     match failed, so we have to copy what matched before
2903                     falling through and copying this character.  In reality,
2904                     this will only ever be just the newline character, but
2905                     it doesn't hurt to be precise.
2906                 */
2907                 strncpy(bp, cont_seq, cmatch);
2908                 bp += cmatch;
2909                 buf_len += cmatch;
2910                 cmatch = 0;
2911             }
2912         }
2913
2914         // copy this char
2915         *bp++ = data[i];
2916         buf_len++;
2917     }
2918
2919         buf[buf_len] = NULLCHAR;
2920 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2921         next_out = 0;
2922         leftover_start = 0;
2923
2924         i = 0;
2925         while (i < buf_len) {
2926             /* Deal with part of the TELNET option negotiation
2927                protocol.  We refuse to do anything beyond the
2928                defaults, except that we allow the WILL ECHO option,
2929                which ICS uses to turn off password echoing when we are
2930                directly connected to it.  We reject this option
2931                if localLineEditing mode is on (always on in xboard)
2932                and we are talking to port 23, which might be a real
2933                telnet server that will try to keep WILL ECHO on permanently.
2934              */
2935             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2936                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2937                 unsigned char option;
2938                 oldi = i;
2939                 switch ((unsigned char) buf[++i]) {
2940                   case TN_WILL:
2941                     if (appData.debugMode)
2942                       fprintf(debugFP, "\n<WILL ");
2943                     switch (option = (unsigned char) buf[++i]) {
2944                       case TN_ECHO:
2945                         if (appData.debugMode)
2946                           fprintf(debugFP, "ECHO ");
2947                         /* Reply only if this is a change, according
2948                            to the protocol rules. */
2949                         if (remoteEchoOption) break;
2950                         if (appData.localLineEditing &&
2951                             atoi(appData.icsPort) == TN_PORT) {
2952                             TelnetRequest(TN_DONT, TN_ECHO);
2953                         } else {
2954                             EchoOff();
2955                             TelnetRequest(TN_DO, TN_ECHO);
2956                             remoteEchoOption = TRUE;
2957                         }
2958                         break;
2959                       default:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "%d ", option);
2962                         /* Whatever this is, we don't want it. */
2963                         TelnetRequest(TN_DONT, option);
2964                         break;
2965                     }
2966                     break;
2967                   case TN_WONT:
2968                     if (appData.debugMode)
2969                       fprintf(debugFP, "\n<WONT ");
2970                     switch (option = (unsigned char) buf[++i]) {
2971                       case TN_ECHO:
2972                         if (appData.debugMode)
2973                           fprintf(debugFP, "ECHO ");
2974                         /* Reply only if this is a change, according
2975                            to the protocol rules. */
2976                         if (!remoteEchoOption) break;
2977                         EchoOn();
2978                         TelnetRequest(TN_DONT, TN_ECHO);
2979                         remoteEchoOption = FALSE;
2980                         break;
2981                       default:
2982                         if (appData.debugMode)
2983                           fprintf(debugFP, "%d ", (unsigned char) option);
2984                         /* Whatever this is, it must already be turned
2985                            off, because we never agree to turn on
2986                            anything non-default, so according to the
2987                            protocol rules, we don't reply. */
2988                         break;
2989                     }
2990                     break;
2991                   case TN_DO:
2992                     if (appData.debugMode)
2993                       fprintf(debugFP, "\n<DO ");
2994                     switch (option = (unsigned char) buf[++i]) {
2995                       default:
2996                         /* Whatever this is, we refuse to do it. */
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", option);
2999                         TelnetRequest(TN_WONT, option);
3000                         break;
3001                     }
3002                     break;
3003                   case TN_DONT:
3004                     if (appData.debugMode)
3005                       fprintf(debugFP, "\n<DONT ");
3006                     switch (option = (unsigned char) buf[++i]) {
3007                       default:
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         /* Whatever this is, we are already not doing
3011                            it, because we never agree to do anything
3012                            non-default, so according to the protocol
3013                            rules, we don't reply. */
3014                         break;
3015                     }
3016                     break;
3017                   case TN_IAC:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<IAC ");
3020                     /* Doubled IAC; pass it through */
3021                     i--;
3022                     break;
3023                   default:
3024                     if (appData.debugMode)
3025                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3026                     /* Drop all other telnet commands on the floor */
3027                     break;
3028                 }
3029                 if (oldi > next_out)
3030                   SendToPlayer(&buf[next_out], oldi - next_out);
3031                 if (++i > next_out)
3032                   next_out = i;
3033                 continue;
3034             }
3035
3036             /* OK, this at least will *usually* work */
3037             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3038                 loggedOn = TRUE;
3039             }
3040
3041             if (loggedOn && !intfSet) {
3042                 if (ics_type == ICS_ICC) {
3043                   snprintf(str, MSG_SIZ,
3044                           "/set-quietly interface %s\n/set-quietly style 12\n",
3045                           programVersion);
3046                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3047                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3048                 } else if (ics_type == ICS_CHESSNET) {
3049                   snprintf(str, MSG_SIZ, "/style 12\n");
3050                 } else {
3051                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3052                   strcat(str, programVersion);
3053                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3054                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3055                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3056 #ifdef WIN32
3057                   strcat(str, "$iset nohighlight 1\n");
3058 #endif
3059                   strcat(str, "$iset lock 1\n$style 12\n");
3060                 }
3061                 SendToICS(str);
3062                 NotifyFrontendLogin();
3063                 intfSet = TRUE;
3064             }
3065
3066             if (started == STARTED_COMMENT) {
3067                 /* Accumulate characters in comment */
3068                 parse[parse_pos++] = buf[i];
3069                 if (buf[i] == '\n') {
3070                     parse[parse_pos] = NULLCHAR;
3071                     if(chattingPartner>=0) {
3072                         char mess[MSG_SIZ];
3073                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3074                         OutputChatMessage(chattingPartner, mess);
3075                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3076                             int p;
3077                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3078                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3079                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3080                                 OutputChatMessage(p, mess);
3081                                 break;
3082                             }
3083                         }
3084                         chattingPartner = -1;
3085                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3086                         collective = 0;
3087                     } else
3088                     if(!suppressKibitz) // [HGM] kibitz
3089                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3090                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3091                         int nrDigit = 0, nrAlph = 0, j;
3092                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3093                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3094                         parse[parse_pos] = NULLCHAR;
3095                         // try to be smart: if it does not look like search info, it should go to
3096                         // ICS interaction window after all, not to engine-output window.
3097                         for(j=0; j<parse_pos; j++) { // count letters and digits
3098                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3099                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3100                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3101                         }
3102                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3103                             int depth=0; float score;
3104                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3105                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3106                                 pvInfoList[forwardMostMove-1].depth = depth;
3107                                 pvInfoList[forwardMostMove-1].score = 100*score;
3108                             }
3109                             OutputKibitz(suppressKibitz, parse);
3110                         } else {
3111                             char tmp[MSG_SIZ];
3112                             if(gameMode == IcsObserving) // restore original ICS messages
3113                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3114                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3115                             else
3116                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3117                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3118                             SendToPlayer(tmp, strlen(tmp));
3119                         }
3120                         next_out = i+1; // [HGM] suppress printing in ICS window
3121                     }
3122                     started = STARTED_NONE;
3123                 } else {
3124                     /* Don't match patterns against characters in comment */
3125                     i++;
3126                     continue;
3127                 }
3128             }
3129             if (started == STARTED_CHATTER) {
3130                 if (buf[i] != '\n') {
3131                     /* Don't match patterns against characters in chatter */
3132                     i++;
3133                     continue;
3134                 }
3135                 started = STARTED_NONE;
3136                 if(suppressKibitz) next_out = i+1;
3137             }
3138
3139             /* Kludge to deal with rcmd protocol */
3140             if (firstTime && looking_at(buf, &i, "\001*")) {
3141                 DisplayFatalError(&buf[1], 0, 1);
3142                 continue;
3143             } else {
3144                 firstTime = FALSE;
3145             }
3146
3147             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3148                 ics_type = ICS_ICC;
3149                 ics_prefix = "/";
3150                 if (appData.debugMode)
3151                   fprintf(debugFP, "ics_type %d\n", ics_type);
3152                 continue;
3153             }
3154             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3155                 ics_type = ICS_FICS;
3156                 ics_prefix = "$";
3157                 if (appData.debugMode)
3158                   fprintf(debugFP, "ics_type %d\n", ics_type);
3159                 continue;
3160             }
3161             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3162                 ics_type = ICS_CHESSNET;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168
3169             if (!loggedOn &&
3170                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3171                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3172                  looking_at(buf, &i, "will be \"*\""))) {
3173               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3174               continue;
3175             }
3176
3177             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3178               char buf[MSG_SIZ];
3179               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3180               DisplayIcsInteractionTitle(buf);
3181               have_set_title = TRUE;
3182             }
3183
3184             /* skip finger notes */
3185             if (started == STARTED_NONE &&
3186                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3187                  (buf[i] == '1' && buf[i+1] == '0')) &&
3188                 buf[i+2] == ':' && buf[i+3] == ' ') {
3189               started = STARTED_CHATTER;
3190               i += 3;
3191               continue;
3192             }
3193
3194             oldi = i;
3195             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3196             if(appData.seekGraph) {
3197                 if(soughtPending && MatchSoughtLine(buf+i)) {
3198                     i = strstr(buf+i, "rated") - buf;
3199                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3200                     next_out = leftover_start = i;
3201                     started = STARTED_CHATTER;
3202                     suppressKibitz = TRUE;
3203                     continue;
3204                 }
3205                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3206                         && looking_at(buf, &i, "* ads displayed")) {
3207                     soughtPending = FALSE;
3208                     seekGraphUp = TRUE;
3209                     DrawSeekGraph();
3210                     continue;
3211                 }
3212                 if(appData.autoRefresh) {
3213                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3214                         int s = (ics_type == ICS_ICC); // ICC format differs
3215                         if(seekGraphUp)
3216                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3217                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3218                         looking_at(buf, &i, "*% "); // eat prompt
3219                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3220                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                         next_out = i; // suppress
3222                         continue;
3223                     }
3224                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3225                         char *p = star_match[0];
3226                         while(*p) {
3227                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3228                             while(*p && *p++ != ' '); // next
3229                         }
3230                         looking_at(buf, &i, "*% "); // eat prompt
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i;
3233                         continue;
3234                     }
3235                 }
3236             }
3237
3238             /* skip formula vars */
3239             if (started == STARTED_NONE &&
3240                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3241               started = STARTED_CHATTER;
3242               i += 3;
3243               continue;
3244             }
3245
3246             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3247             if (appData.autoKibitz && started == STARTED_NONE &&
3248                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3249                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3250                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3251                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3252                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3253                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3254                         suppressKibitz = TRUE;
3255                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3256                         next_out = i;
3257                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3258                                 && (gameMode == IcsPlayingWhite)) ||
3259                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3260                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3261                             started = STARTED_CHATTER; // own kibitz we simply discard
3262                         else {
3263                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3264                             parse_pos = 0; parse[0] = NULLCHAR;
3265                             savingComment = TRUE;
3266                             suppressKibitz = gameMode != IcsObserving ? 2 :
3267                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3268                         }
3269                         continue;
3270                 } else
3271                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3272                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3273                          && atoi(star_match[0])) {
3274                     // suppress the acknowledgements of our own autoKibitz
3275                     char *p;
3276                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3277                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3278                     SendToPlayer(star_match[0], strlen(star_match[0]));
3279                     if(looking_at(buf, &i, "*% ")) // eat prompt
3280                         suppressKibitz = FALSE;
3281                     next_out = i;
3282                     continue;
3283                 }
3284             } // [HGM] kibitz: end of patch
3285
3286             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3287
3288             // [HGM] chat: intercept tells by users for which we have an open chat window
3289             channel = -1;
3290             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3291                                            looking_at(buf, &i, "* whispers:") ||
3292                                            looking_at(buf, &i, "* kibitzes:") ||
3293                                            looking_at(buf, &i, "* shouts:") ||
3294                                            looking_at(buf, &i, "* c-shouts:") ||
3295                                            looking_at(buf, &i, "--> * ") ||
3296                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3297                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3298                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3299                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3300                 int p;
3301                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3302                 chattingPartner = -1; collective = 0;
3303
3304                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3305                 for(p=0; p<MAX_CHAT; p++) {
3306                     collective = 1;
3307                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3308                     talker[0] = '['; strcat(talker, "] ");
3309                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3310                     chattingPartner = p; break;
3311                     }
3312                 } else
3313                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3314                 for(p=0; p<MAX_CHAT; p++) {
3315                     collective = 1;
3316                     if(!strcmp("kibitzes", chatPartner[p])) {
3317                         talker[0] = '['; strcat(talker, "] ");
3318                         chattingPartner = p; break;
3319                     }
3320                 } else
3321                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3322                 for(p=0; p<MAX_CHAT; p++) {
3323                     collective = 1;
3324                     if(!strcmp("whispers", chatPartner[p])) {
3325                         talker[0] = '['; strcat(talker, "] ");
3326                         chattingPartner = p; break;
3327                     }
3328                 } else
3329                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3330                   if(buf[i-8] == '-' && buf[i-3] == 't')
3331                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3332                     collective = 1;
3333                     if(!strcmp("c-shouts", chatPartner[p])) {
3334                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3335                         chattingPartner = p; break;
3336                     }
3337                   }
3338                   if(chattingPartner < 0)
3339                   for(p=0; p<MAX_CHAT; p++) {
3340                     collective = 1;
3341                     if(!strcmp("shouts", chatPartner[p])) {
3342                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3343                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3344                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3345                         chattingPartner = p; break;
3346                     }
3347                   }
3348                 }
3349                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3350                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3351                     talker[0] = 0;
3352                     Colorize(ColorTell, FALSE);
3353                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3354                     collective |= 2;
3355                     chattingPartner = p; break;
3356                 }
3357                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3358                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3359                     started = STARTED_COMMENT;
3360                     parse_pos = 0; parse[0] = NULLCHAR;
3361                     savingComment = 3 + chattingPartner; // counts as TRUE
3362                     if(collective == 3) i = oldi; else {
3363                         suppressKibitz = TRUE;
3364                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3365                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3366                         continue;
3367                     }
3368                 }
3369             } // [HGM] chat: end of patch
3370
3371           backup = i;
3372             if (appData.zippyTalk || appData.zippyPlay) {
3373                 /* [DM] Backup address for color zippy lines */
3374 #if ZIPPY
3375                if (loggedOn == TRUE)
3376                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3377                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3378 #endif
3379             } // [DM] 'else { ' deleted
3380                 if (
3381                     /* Regular tells and says */
3382                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3383                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3384                     looking_at(buf, &i, "* says: ") ||
3385                     /* Don't color "message" or "messages" output */
3386                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3387                     looking_at(buf, &i, "*. * at *:*: ") ||
3388                     looking_at(buf, &i, "--* (*:*): ") ||
3389                     /* Message notifications (same color as tells) */
3390                     looking_at(buf, &i, "* has left a message ") ||
3391                     looking_at(buf, &i, "* just sent you a message:\n") ||
3392                     /* Whispers and kibitzes */
3393                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3394                     looking_at(buf, &i, "* kibitzes: ") ||
3395                     /* Channel tells */
3396                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3397
3398                   if (tkind == 1 && strchr(star_match[0], ':')) {
3399                       /* Avoid "tells you:" spoofs in channels */
3400                      tkind = 3;
3401                   }
3402                   if (star_match[0][0] == NULLCHAR ||
3403                       strchr(star_match[0], ' ') ||
3404                       (tkind == 3 && strchr(star_match[1], ' '))) {
3405                     /* Reject bogus matches */
3406                     i = oldi;
3407                   } else {
3408                     if (appData.colorize) {
3409                       if (oldi > next_out) {
3410                         SendToPlayer(&buf[next_out], oldi - next_out);
3411                         next_out = oldi;
3412                       }
3413                       switch (tkind) {
3414                       case 1:
3415                         Colorize(ColorTell, FALSE);
3416                         curColor = ColorTell;
3417                         break;
3418                       case 2:
3419                         Colorize(ColorKibitz, FALSE);
3420                         curColor = ColorKibitz;
3421                         break;
3422                       case 3:
3423                         p = strrchr(star_match[1], '(');
3424                         if (p == NULL) {
3425                           p = star_match[1];
3426                         } else {
3427                           p++;
3428                         }
3429                         if (atoi(p) == 1) {
3430                           Colorize(ColorChannel1, FALSE);
3431                           curColor = ColorChannel1;
3432                         } else {
3433                           Colorize(ColorChannel, FALSE);
3434                           curColor = ColorChannel;
3435                         }
3436                         break;
3437                       case 5:
3438                         curColor = ColorNormal;
3439                         break;
3440                       }
3441                     }
3442                     if (started == STARTED_NONE && appData.autoComment &&
3443                         (gameMode == IcsObserving ||
3444                          gameMode == IcsPlayingWhite ||
3445                          gameMode == IcsPlayingBlack)) {
3446                       parse_pos = i - oldi;
3447                       memcpy(parse, &buf[oldi], parse_pos);
3448                       parse[parse_pos] = NULLCHAR;
3449                       started = STARTED_COMMENT;
3450                       savingComment = TRUE;
3451                     } else if(collective != 3) {
3452                       started = STARTED_CHATTER;
3453                       savingComment = FALSE;
3454                     }
3455                     loggedOn = TRUE;
3456                     continue;
3457                   }
3458                 }
3459
3460                 if (looking_at(buf, &i, "* s-shouts: ") ||
3461                     looking_at(buf, &i, "* c-shouts: ")) {
3462                     if (appData.colorize) {
3463                         if (oldi > next_out) {
3464                             SendToPlayer(&buf[next_out], oldi - next_out);
3465                             next_out = oldi;
3466                         }
3467                         Colorize(ColorSShout, FALSE);
3468                         curColor = ColorSShout;
3469                     }
3470                     loggedOn = TRUE;
3471                     started = STARTED_CHATTER;
3472                     continue;
3473                 }
3474
3475                 if (looking_at(buf, &i, "--->")) {
3476                     loggedOn = TRUE;
3477                     continue;
3478                 }
3479
3480                 if (looking_at(buf, &i, "* shouts: ") ||
3481                     looking_at(buf, &i, "--> ")) {
3482                     if (appData.colorize) {
3483                         if (oldi > next_out) {
3484                             SendToPlayer(&buf[next_out], oldi - next_out);
3485                             next_out = oldi;
3486                         }
3487                         Colorize(ColorShout, FALSE);
3488                         curColor = ColorShout;
3489                     }
3490                     loggedOn = TRUE;
3491                     started = STARTED_CHATTER;
3492                     continue;
3493                 }
3494
3495                 if (looking_at( buf, &i, "Challenge:")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorChallenge, FALSE);
3502                         curColor = ColorChallenge;
3503                     }
3504                     loggedOn = TRUE;
3505                     continue;
3506                 }
3507
3508                 if (looking_at(buf, &i, "* offers you") ||
3509                     looking_at(buf, &i, "* offers to be") ||
3510                     looking_at(buf, &i, "* would like to") ||
3511                     looking_at(buf, &i, "* requests to") ||
3512                     looking_at(buf, &i, "Your opponent offers") ||
3513                     looking_at(buf, &i, "Your opponent requests")) {
3514
3515                     if (appData.colorize) {
3516                         if (oldi > next_out) {
3517                             SendToPlayer(&buf[next_out], oldi - next_out);
3518                             next_out = oldi;
3519                         }
3520                         Colorize(ColorRequest, FALSE);
3521                         curColor = ColorRequest;
3522                     }
3523                     continue;
3524                 }
3525
3526                 if (looking_at(buf, &i, "* (*) seeking")) {
3527                     if (appData.colorize) {
3528                         if (oldi > next_out) {
3529                             SendToPlayer(&buf[next_out], oldi - next_out);
3530                             next_out = oldi;
3531                         }
3532                         Colorize(ColorSeek, FALSE);
3533                         curColor = ColorSeek;
3534                     }
3535                     continue;
3536             }
3537
3538           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3539
3540             if (looking_at(buf, &i, "\\   ")) {
3541                 if (prevColor != ColorNormal) {
3542                     if (oldi > next_out) {
3543                         SendToPlayer(&buf[next_out], oldi - next_out);
3544                         next_out = oldi;
3545                     }
3546                     Colorize(prevColor, TRUE);
3547                     curColor = prevColor;
3548                 }
3549                 if (savingComment) {
3550                     parse_pos = i - oldi;
3551                     memcpy(parse, &buf[oldi], parse_pos);
3552                     parse[parse_pos] = NULLCHAR;
3553                     started = STARTED_COMMENT;
3554                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3555                         chattingPartner = savingComment - 3; // kludge to remember the box
3556                 } else {
3557                     started = STARTED_CHATTER;
3558                 }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i, "Black Strength :") ||
3563                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3564                 looking_at(buf, &i, "<10>") ||
3565                 looking_at(buf, &i, "#@#")) {
3566                 /* Wrong board style */
3567                 loggedOn = TRUE;
3568                 SendToICS(ics_prefix);
3569                 SendToICS("set style 12\n");
3570                 SendToICS(ics_prefix);
3571                 SendToICS("refresh\n");
3572                 continue;
3573             }
3574
3575             if (looking_at(buf, &i, "login:")) {
3576               if (!have_sent_ICS_logon) {
3577                 if(ICSInitScript())
3578                   have_sent_ICS_logon = 1;
3579                 else // no init script was found
3580                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3581               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3582                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3583               }
3584                 continue;
3585             }
3586
3587             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3588                 (looking_at(buf, &i, "\n<12> ") ||
3589                  looking_at(buf, &i, "<12> "))) {
3590                 loggedOn = TRUE;
3591                 if (oldi > next_out) {
3592                     SendToPlayer(&buf[next_out], oldi - next_out);
3593                 }
3594                 next_out = i;
3595                 started = STARTED_BOARD;
3596                 parse_pos = 0;
3597                 continue;
3598             }
3599
3600             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3601                 looking_at(buf, &i, "<b1> ")) {
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_HOLDINGS;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3612                 loggedOn = TRUE;
3613                 /* Header for a move list -- first line */
3614
3615                 switch (ics_getting_history) {
3616                   case H_FALSE:
3617                     switch (gameMode) {
3618                       case IcsIdle:
3619                       case BeginningOfGame:
3620                         /* User typed "moves" or "oldmoves" while we
3621                            were idle.  Pretend we asked for these
3622                            moves and soak them up so user can step
3623                            through them and/or save them.
3624                            */
3625                         Reset(FALSE, TRUE);
3626                         gameMode = IcsObserving;
3627                         ModeHighlight();
3628                         ics_gamenum = -1;
3629                         ics_getting_history = H_GOT_UNREQ_HEADER;
3630                         break;
3631                       case EditGame: /*?*/
3632                       case EditPosition: /*?*/
3633                         /* Should above feature work in these modes too? */
3634                         /* For now it doesn't */
3635                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3636                         break;
3637                       default:
3638                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3639                         break;
3640                     }
3641                     break;
3642                   case H_REQUESTED:
3643                     /* Is this the right one? */
3644                     if (gameInfo.white && gameInfo.black &&
3645                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3646                         strcmp(gameInfo.black, star_match[2]) == 0) {
3647                         /* All is well */
3648                         ics_getting_history = H_GOT_REQ_HEADER;
3649                     }
3650                     break;
3651                   case H_GOT_REQ_HEADER:
3652                   case H_GOT_UNREQ_HEADER:
3653                   case H_GOT_UNWANTED_HEADER:
3654                   case H_GETTING_MOVES:
3655                     /* Should not happen */
3656                     DisplayError(_("Error gathering move list: two headers"), 0);
3657                     ics_getting_history = H_FALSE;
3658                     break;
3659                 }
3660
3661                 /* Save player ratings into gameInfo if needed */
3662                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3663                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3664                     (gameInfo.whiteRating == -1 ||
3665                      gameInfo.blackRating == -1)) {
3666
3667                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3668                     gameInfo.blackRating = string_to_rating(star_match[3]);
3669                     if (appData.debugMode)
3670                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3671                               gameInfo.whiteRating, gameInfo.blackRating);
3672                 }
3673                 continue;
3674             }
3675
3676             if (looking_at(buf, &i,
3677               "* * match, initial time: * minute*, increment: * second")) {
3678                 /* Header for a move list -- second line */
3679                 /* Initial board will follow if this is a wild game */
3680                 if (gameInfo.event != NULL) free(gameInfo.event);
3681                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3682                 gameInfo.event = StrSave(str);
3683                 /* [HGM] we switched variant. Translate boards if needed. */
3684                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3685                 continue;
3686             }
3687
3688             if (looking_at(buf, &i, "Move  ")) {
3689                 /* Beginning of a move list */
3690                 switch (ics_getting_history) {
3691                   case H_FALSE:
3692                     /* Normally should not happen */
3693                     /* Maybe user hit reset while we were parsing */
3694                     break;
3695                   case H_REQUESTED:
3696                     /* Happens if we are ignoring a move list that is not
3697                      * the one we just requested.  Common if the user
3698                      * tries to observe two games without turning off
3699                      * getMoveList */
3700                     break;
3701                   case H_GETTING_MOVES:
3702                     /* Should not happen */
3703                     DisplayError(_("Error gathering move list: nested"), 0);
3704                     ics_getting_history = H_FALSE;
3705                     break;
3706                   case H_GOT_REQ_HEADER:
3707                     ics_getting_history = H_GETTING_MOVES;
3708                     started = STARTED_MOVES;
3709                     parse_pos = 0;
3710                     if (oldi > next_out) {
3711                         SendToPlayer(&buf[next_out], oldi - next_out);
3712                     }
3713                     break;
3714                   case H_GOT_UNREQ_HEADER:
3715                     ics_getting_history = H_GETTING_MOVES;
3716                     started = STARTED_MOVES_NOHIDE;
3717                     parse_pos = 0;
3718                     break;
3719                   case H_GOT_UNWANTED_HEADER:
3720                     ics_getting_history = H_FALSE;
3721                     break;
3722                 }
3723                 continue;
3724             }
3725
3726             if (looking_at(buf, &i, "% ") ||
3727                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3728                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3729                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3730                     soughtPending = FALSE;
3731                     seekGraphUp = TRUE;
3732                     DrawSeekGraph();
3733                 }
3734                 if(suppressKibitz) next_out = i;
3735                 savingComment = FALSE;
3736                 suppressKibitz = 0;
3737                 switch (started) {
3738                   case STARTED_MOVES:
3739                   case STARTED_MOVES_NOHIDE:
3740                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3741                     parse[parse_pos + i - oldi] = NULLCHAR;
3742                     ParseGameHistory(parse);
3743 #if ZIPPY
3744                     if (appData.zippyPlay && first.initDone) {
3745                         FeedMovesToProgram(&first, forwardMostMove);
3746                         if (gameMode == IcsPlayingWhite) {
3747                             if (WhiteOnMove(forwardMostMove)) {
3748                                 if (first.sendTime) {
3749                                   if (first.useColors) {
3750                                     SendToProgram("black\n", &first);
3751                                   }
3752                                   SendTimeRemaining(&first, TRUE);
3753                                 }
3754                                 if (first.useColors) {
3755                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3756                                 }
3757                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3758                                 first.maybeThinking = TRUE;
3759                             } else {
3760                                 if (first.usePlayother) {
3761                                   if (first.sendTime) {
3762                                     SendTimeRemaining(&first, TRUE);
3763                                   }
3764                                   SendToProgram("playother\n", &first);
3765                                   firstMove = FALSE;
3766                                 } else {
3767                                   firstMove = TRUE;
3768                                 }
3769                             }
3770                         } else if (gameMode == IcsPlayingBlack) {
3771                             if (!WhiteOnMove(forwardMostMove)) {
3772                                 if (first.sendTime) {
3773                                   if (first.useColors) {
3774                                     SendToProgram("white\n", &first);
3775                                   }
3776                                   SendTimeRemaining(&first, FALSE);
3777                                 }
3778                                 if (first.useColors) {
3779                                   SendToProgram("black\n", &first);
3780                                 }
3781                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3782                                 first.maybeThinking = TRUE;
3783                             } else {
3784                                 if (first.usePlayother) {
3785                                   if (first.sendTime) {
3786                                     SendTimeRemaining(&first, FALSE);
3787                                   }
3788                                   SendToProgram("playother\n", &first);
3789                                   firstMove = FALSE;
3790                                 } else {
3791                                   firstMove = TRUE;
3792                                 }
3793                             }
3794                         }
3795                     }
3796 #endif
3797                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3798                         /* Moves came from oldmoves or moves command
3799                            while we weren't doing anything else.
3800                            */
3801                         currentMove = forwardMostMove;
3802                         ClearHighlights();/*!!could figure this out*/
3803                         flipView = appData.flipView;
3804                         DrawPosition(TRUE, boards[currentMove]);
3805                         DisplayBothClocks();
3806                         snprintf(str, MSG_SIZ, "%s %s %s",
3807                                 gameInfo.white, _("vs."),  gameInfo.black);
3808                         DisplayTitle(str);
3809                         gameMode = IcsIdle;
3810                     } else {
3811                         /* Moves were history of an active game */
3812                         if (gameInfo.resultDetails != NULL) {
3813                             free(gameInfo.resultDetails);
3814                             gameInfo.resultDetails = NULL;
3815                         }
3816                     }
3817                     HistorySet(parseList, backwardMostMove,
3818                                forwardMostMove, currentMove-1);
3819                     DisplayMove(currentMove - 1);
3820                     if (started == STARTED_MOVES) next_out = i;
3821                     started = STARTED_NONE;
3822                     ics_getting_history = H_FALSE;
3823                     break;
3824
3825                   case STARTED_OBSERVE:
3826                     started = STARTED_NONE;
3827                     SendToICS(ics_prefix);
3828                     SendToICS("refresh\n");
3829                     break;
3830
3831                   default:
3832                     break;
3833                 }
3834                 if(bookHit) { // [HGM] book: simulate book reply
3835                     static char bookMove[MSG_SIZ]; // a bit generous?
3836
3837                     programStats.nodes = programStats.depth = programStats.time =
3838                     programStats.score = programStats.got_only_move = 0;
3839                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3840
3841                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3842                     strcat(bookMove, bookHit);
3843                     HandleMachineMove(bookMove, &first);
3844                 }
3845                 continue;
3846             }
3847
3848             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3849                  started == STARTED_HOLDINGS ||
3850                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3851                 /* Accumulate characters in move list or board */
3852                 parse[parse_pos++] = buf[i];
3853             }
3854
3855             /* Start of game messages.  Mostly we detect start of game
3856                when the first board image arrives.  On some versions
3857                of the ICS, though, we need to do a "refresh" after starting
3858                to observe in order to get the current board right away. */
3859             if (looking_at(buf, &i, "Adding game * to observation list")) {
3860                 started = STARTED_OBSERVE;
3861                 continue;
3862             }
3863
3864             /* Handle auto-observe */
3865             if (appData.autoObserve &&
3866                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3867                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3868                 char *player;
3869                 /* Choose the player that was highlighted, if any. */
3870                 if (star_match[0][0] == '\033' ||
3871                     star_match[1][0] != '\033') {
3872                     player = star_match[0];
3873                 } else {
3874                     player = star_match[2];
3875                 }
3876                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3877                         ics_prefix, StripHighlightAndTitle(player));
3878                 SendToICS(str);
3879
3880                 /* Save ratings from notify string */
3881                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3882                 player1Rating = string_to_rating(star_match[1]);
3883                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3884                 player2Rating = string_to_rating(star_match[3]);
3885
3886                 if (appData.debugMode)
3887                   fprintf(debugFP,
3888                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3889                           player1Name, player1Rating,
3890                           player2Name, player2Rating);
3891
3892                 continue;
3893             }
3894
3895             /* Deal with automatic examine mode after a game,
3896                and with IcsObserving -> IcsExamining transition */
3897             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3898                 looking_at(buf, &i, "has made you an examiner of game *")) {
3899
3900                 int gamenum = atoi(star_match[0]);
3901                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3902                     gamenum == ics_gamenum) {
3903                     /* We were already playing or observing this game;
3904                        no need to refetch history */
3905                     gameMode = IcsExamining;
3906                     if (pausing) {
3907                         pauseExamForwardMostMove = forwardMostMove;
3908                     } else if (currentMove < forwardMostMove) {
3909                         ForwardInner(forwardMostMove);
3910                     }
3911                 } else {
3912                     /* I don't think this case really can happen */
3913                     SendToICS(ics_prefix);
3914                     SendToICS("refresh\n");
3915                 }
3916                 continue;
3917             }
3918
3919             /* Error messages */
3920 //          if (ics_user_moved) {
3921             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3922                 if (looking_at(buf, &i, "Illegal move") ||
3923                     looking_at(buf, &i, "Not a legal move") ||
3924                     looking_at(buf, &i, "Your king is in check") ||
3925                     looking_at(buf, &i, "It isn't your turn") ||
3926                     looking_at(buf, &i, "It is not your move")) {
3927                     /* Illegal move */
3928                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3929                         currentMove = forwardMostMove-1;
3930                         DisplayMove(currentMove - 1); /* before DMError */
3931                         DrawPosition(FALSE, boards[currentMove]);
3932                         SwitchClocks(forwardMostMove-1); // [HGM] race
3933                         DisplayBothClocks();
3934                     }
3935                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3936                     ics_user_moved = 0;
3937                     continue;
3938                 }
3939             }
3940
3941             if (looking_at(buf, &i, "still have time") ||
3942                 looking_at(buf, &i, "not out of time") ||
3943                 looking_at(buf, &i, "either player is out of time") ||
3944                 looking_at(buf, &i, "has timeseal; checking")) {
3945                 /* We must have called his flag a little too soon */
3946                 whiteFlag = blackFlag = FALSE;
3947                 continue;
3948             }
3949
3950             if (looking_at(buf, &i, "added * seconds to") ||
3951                 looking_at(buf, &i, "seconds were added to")) {
3952                 /* Update the clocks */
3953                 SendToICS(ics_prefix);
3954                 SendToICS("refresh\n");
3955                 continue;
3956             }
3957
3958             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3959                 ics_clock_paused = TRUE;
3960                 StopClocks();
3961                 continue;
3962             }
3963
3964             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3965                 ics_clock_paused = FALSE;
3966                 StartClocks();
3967                 continue;
3968             }
3969
3970             /* Grab player ratings from the Creating: message.
3971                Note we have to check for the special case when
3972                the ICS inserts things like [white] or [black]. */
3973             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3974                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3975                 /* star_matches:
3976                    0    player 1 name (not necessarily white)
3977                    1    player 1 rating
3978                    2    empty, white, or black (IGNORED)
3979                    3    player 2 name (not necessarily black)
3980                    4    player 2 rating
3981
3982                    The names/ratings are sorted out when the game
3983                    actually starts (below).
3984                 */
3985                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3986                 player1Rating = string_to_rating(star_match[1]);
3987                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3988                 player2Rating = string_to_rating(star_match[4]);
3989
3990                 if (appData.debugMode)
3991                   fprintf(debugFP,
3992                           "Ratings from 'Creating:' %s %d, %s %d\n",
3993                           player1Name, player1Rating,
3994                           player2Name, player2Rating);
3995
3996                 continue;
3997             }
3998
3999             /* Improved generic start/end-of-game messages */
4000             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4001                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4002                 /* If tkind == 0: */
4003                 /* star_match[0] is the game number */
4004                 /*           [1] is the white player's name */
4005                 /*           [2] is the black player's name */
4006                 /* For end-of-game: */
4007                 /*           [3] is the reason for the game end */
4008                 /*           [4] is a PGN end game-token, preceded by " " */
4009                 /* For start-of-game: */
4010                 /*           [3] begins with "Creating" or "Continuing" */
4011                 /*           [4] is " *" or empty (don't care). */
4012                 int gamenum = atoi(star_match[0]);
4013                 char *whitename, *blackname, *why, *endtoken;
4014                 ChessMove endtype = EndOfFile;
4015
4016                 if (tkind == 0) {
4017                   whitename = star_match[1];
4018                   blackname = star_match[2];
4019                   why = star_match[3];
4020                   endtoken = star_match[4];
4021                 } else {
4022                   whitename = star_match[1];
4023                   blackname = star_match[3];
4024                   why = star_match[5];
4025                   endtoken = star_match[6];
4026                 }
4027
4028                 /* Game start messages */
4029                 if (strncmp(why, "Creating ", 9) == 0 ||
4030                     strncmp(why, "Continuing ", 11) == 0) {
4031                     gs_gamenum = gamenum;
4032                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4033                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4034                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4035 #if ZIPPY
4036                     if (appData.zippyPlay) {
4037                         ZippyGameStart(whitename, blackname);
4038                     }
4039 #endif /*ZIPPY*/
4040                     partnerBoardValid = FALSE; // [HGM] bughouse
4041                     continue;
4042                 }
4043
4044                 /* Game end messages */
4045                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4046                     ics_gamenum != gamenum) {
4047                     continue;
4048                 }
4049                 while (endtoken[0] == ' ') endtoken++;
4050                 switch (endtoken[0]) {
4051                   case '*':
4052                   default:
4053                     endtype = GameUnfinished;
4054                     break;
4055                   case '0':
4056                     endtype = BlackWins;
4057                     break;
4058                   case '1':
4059                     if (endtoken[1] == '/')
4060                       endtype = GameIsDrawn;
4061                     else
4062                       endtype = WhiteWins;
4063                     break;
4064                 }
4065                 GameEnds(endtype, why, GE_ICS);
4066 #if ZIPPY
4067                 if (appData.zippyPlay && first.initDone) {
4068                     ZippyGameEnd(endtype, why);
4069                     if (first.pr == NoProc) {
4070                       /* Start the next process early so that we'll
4071                          be ready for the next challenge */
4072                       StartChessProgram(&first);
4073                     }
4074                     /* Send "new" early, in case this command takes
4075                        a long time to finish, so that we'll be ready
4076                        for the next challenge. */
4077                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4078                     Reset(TRUE, TRUE);
4079                 }
4080 #endif /*ZIPPY*/
4081                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4082                 continue;
4083             }
4084
4085             if (looking_at(buf, &i, "Removing game * from observation") ||
4086                 looking_at(buf, &i, "no longer observing game *") ||
4087                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4088                 if (gameMode == IcsObserving &&
4089                     atoi(star_match[0]) == ics_gamenum)
4090                   {
4091                       /* icsEngineAnalyze */
4092                       if (appData.icsEngineAnalyze) {
4093                             ExitAnalyzeMode();
4094                             ModeHighlight();
4095                       }
4096                       StopClocks();
4097                       gameMode = IcsIdle;
4098                       ics_gamenum = -1;
4099                       ics_user_moved = FALSE;
4100                   }
4101                 continue;
4102             }
4103
4104             if (looking_at(buf, &i, "no longer examining game *")) {
4105                 if (gameMode == IcsExamining &&
4106                     atoi(star_match[0]) == ics_gamenum)
4107                   {
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             /* Advance leftover_start past any newlines we find,
4116                so only partial lines can get reparsed */
4117             if (looking_at(buf, &i, "\n")) {
4118                 prevColor = curColor;
4119                 if (curColor != ColorNormal) {
4120                     if (oldi > next_out) {
4121                         SendToPlayer(&buf[next_out], oldi - next_out);
4122                         next_out = oldi;
4123                     }
4124                     Colorize(ColorNormal, FALSE);
4125                     curColor = ColorNormal;
4126                 }
4127                 if (started == STARTED_BOARD) {
4128                     started = STARTED_NONE;
4129                     parse[parse_pos] = NULLCHAR;
4130                     ParseBoard12(parse);
4131                     ics_user_moved = 0;
4132
4133                     /* Send premove here */
4134                     if (appData.premove) {
4135                       char str[MSG_SIZ];
4136                       if (currentMove == 0 &&
4137                           gameMode == IcsPlayingWhite &&
4138                           appData.premoveWhite) {
4139                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4140                         if (appData.debugMode)
4141                           fprintf(debugFP, "Sending premove:\n");
4142                         SendToICS(str);
4143                       } else if (currentMove == 1 &&
4144                                  gameMode == IcsPlayingBlack &&
4145                                  appData.premoveBlack) {
4146                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4147                         if (appData.debugMode)
4148                           fprintf(debugFP, "Sending premove:\n");
4149                         SendToICS(str);
4150                       } else if (gotPremove) {
4151                         gotPremove = 0;
4152                         ClearPremoveHighlights();
4153                         if (appData.debugMode)
4154                           fprintf(debugFP, "Sending premove:\n");
4155                           UserMoveEvent(premoveFromX, premoveFromY,
4156                                         premoveToX, premoveToY,
4157                                         premovePromoChar);
4158                       }
4159                     }
4160
4161                     /* Usually suppress following prompt */
4162                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4163                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4164                         if (looking_at(buf, &i, "*% ")) {
4165                             savingComment = FALSE;
4166                             suppressKibitz = 0;
4167                         }
4168                     }
4169                     next_out = i;
4170                 } else if (started == STARTED_HOLDINGS) {
4171                     int gamenum;
4172                     char new_piece[MSG_SIZ];
4173                     started = STARTED_NONE;
4174                     parse[parse_pos] = NULLCHAR;
4175                     if (appData.debugMode)
4176                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4177                                                         parse, currentMove);
4178                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4179                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4180                         if (gameInfo.variant == VariantNormal) {
4181                           /* [HGM] We seem to switch variant during a game!
4182                            * Presumably no holdings were displayed, so we have
4183                            * to move the position two files to the right to
4184                            * create room for them!
4185                            */
4186                           VariantClass newVariant;
4187                           switch(gameInfo.boardWidth) { // base guess on board width
4188                                 case 9:  newVariant = VariantShogi; break;
4189                                 case 10: newVariant = VariantGreat; break;
4190                                 default: newVariant = VariantCrazyhouse; break;
4191                           }
4192                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4193                           /* Get a move list just to see the header, which
4194                              will tell us whether this is really bug or zh */
4195                           if (ics_getting_history == H_FALSE) {
4196                             ics_getting_history = H_REQUESTED;
4197                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4198                             SendToICS(str);
4199                           }
4200                         }
4201                         new_piece[0] = NULLCHAR;
4202                         sscanf(parse, "game %d white [%s black [%s <- %s",
4203                                &gamenum, white_holding, black_holding,
4204                                new_piece);
4205                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4206                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4207                         /* [HGM] copy holdings to board holdings area */
4208                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4209                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4210                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4211 #if ZIPPY
4212                         if (appData.zippyPlay && first.initDone) {
4213                             ZippyHoldings(white_holding, black_holding,
4214                                           new_piece);
4215                         }
4216 #endif /*ZIPPY*/
4217                         if (tinyLayout || smallLayout) {
4218                             char wh[16], bh[16];
4219                             PackHolding(wh, white_holding);
4220                             PackHolding(bh, black_holding);
4221                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4222                                     gameInfo.white, gameInfo.black);
4223                         } else {
4224                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4225                                     gameInfo.white, white_holding, _("vs."),
4226                                     gameInfo.black, black_holding);
4227                         }
4228                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4229                         DrawPosition(FALSE, boards[currentMove]);
4230                         DisplayTitle(str);
4231                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4232                         sscanf(parse, "game %d white [%s black [%s <- %s",
4233                                &gamenum, white_holding, black_holding,
4234                                new_piece);
4235                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4236                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4237                         /* [HGM] copy holdings to partner-board holdings area */
4238                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4239                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4240                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4241                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4242                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4243                       }
4244                     }
4245                     /* Suppress following prompt */
4246                     if (looking_at(buf, &i, "*% ")) {
4247                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4248                         savingComment = FALSE;
4249                         suppressKibitz = 0;
4250                     }
4251                     next_out = i;
4252                 }
4253                 continue;
4254             }
4255
4256             i++;                /* skip unparsed character and loop back */
4257         }
4258
4259         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4260 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4261 //          SendToPlayer(&buf[next_out], i - next_out);
4262             started != STARTED_HOLDINGS && leftover_start > next_out) {
4263             SendToPlayer(&buf[next_out], leftover_start - next_out);
4264             next_out = i;
4265         }
4266
4267         leftover_len = buf_len - leftover_start;
4268         /* if buffer ends with something we couldn't parse,
4269            reparse it after appending the next read */
4270
4271     } else if (count == 0) {
4272         RemoveInputSource(isr);
4273         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4274     } else {
4275         DisplayFatalError(_("Error reading from ICS"), error, 1);
4276     }
4277 }
4278
4279
4280 /* Board style 12 looks like this:
4281
4282    <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
4283
4284  * The "<12> " is stripped before it gets to this routine.  The two
4285  * trailing 0's (flip state and clock ticking) are later addition, and
4286  * some chess servers may not have them, or may have only the first.
4287  * Additional trailing fields may be added in the future.
4288  */
4289
4290 #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"
4291
4292 #define RELATION_OBSERVING_PLAYED    0
4293 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4294 #define RELATION_PLAYING_MYMOVE      1
4295 #define RELATION_PLAYING_NOTMYMOVE  -1
4296 #define RELATION_EXAMINING           2
4297 #define RELATION_ISOLATED_BOARD     -3
4298 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4299
4300 void
4301 ParseBoard12 (char *string)
4302 {
4303 #if ZIPPY
4304     int i, takeback;
4305     char *bookHit = NULL; // [HGM] book
4306 #endif
4307     GameMode newGameMode;
4308     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4309     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4310     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4311     char to_play, board_chars[200];
4312     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4313     char black[32], white[32];
4314     Board board;
4315     int prevMove = currentMove;
4316     int ticking = 2;
4317     ChessMove moveType;
4318     int fromX, fromY, toX, toY;
4319     char promoChar;
4320     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4321     Boolean weird = FALSE, reqFlag = FALSE;
4322
4323     fromX = fromY = toX = toY = -1;
4324
4325     newGame = FALSE;
4326
4327     if (appData.debugMode)
4328       fprintf(debugFP, "Parsing board: %s\n", string);
4329
4330     move_str[0] = NULLCHAR;
4331     elapsed_time[0] = NULLCHAR;
4332     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4333         int  i = 0, j;
4334         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4335             if(string[i] == ' ') { ranks++; files = 0; }
4336             else files++;
4337             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4338             i++;
4339         }
4340         for(j = 0; j <i; j++) board_chars[j] = string[j];
4341         board_chars[i] = '\0';
4342         string += i + 1;
4343     }
4344     n = sscanf(string, PATTERN, &to_play, &double_push,
4345                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4346                &gamenum, white, black, &relation, &basetime, &increment,
4347                &white_stren, &black_stren, &white_time, &black_time,
4348                &moveNum, str, elapsed_time, move_str, &ics_flip,
4349                &ticking);
4350
4351     if (n < 21) {
4352         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4353         DisplayError(str, 0);
4354         return;
4355     }
4356
4357     /* Convert the move number to internal form */
4358     moveNum = (moveNum - 1) * 2;
4359     if (to_play == 'B') moveNum++;
4360     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4361       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4362                         0, 1);
4363       return;
4364     }
4365
4366     switch (relation) {
4367       case RELATION_OBSERVING_PLAYED:
4368       case RELATION_OBSERVING_STATIC:
4369         if (gamenum == -1) {
4370             /* Old ICC buglet */
4371             relation = RELATION_OBSERVING_STATIC;
4372         }
4373         newGameMode = IcsObserving;
4374         break;
4375       case RELATION_PLAYING_MYMOVE:
4376       case RELATION_PLAYING_NOTMYMOVE:
4377         newGameMode =
4378           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4379             IcsPlayingWhite : IcsPlayingBlack;
4380         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4381         break;
4382       case RELATION_EXAMINING:
4383         newGameMode = IcsExamining;
4384         break;
4385       case RELATION_ISOLATED_BOARD:
4386       default:
4387         /* Just display this board.  If user was doing something else,
4388            we will forget about it until the next board comes. */
4389         newGameMode = IcsIdle;
4390         break;
4391       case RELATION_STARTING_POSITION:
4392         newGameMode = gameMode;
4393         break;
4394     }
4395
4396     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4397         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4398          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4399       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4400       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4401       static int lastBgGame = -1;
4402       char *toSqr;
4403       for (k = 0; k < ranks; k++) {
4404         for (j = 0; j < files; j++)
4405           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4406         if(gameInfo.holdingsWidth > 1) {
4407              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4408              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4409         }
4410       }
4411       CopyBoard(partnerBoard, board);
4412       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4413         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4414         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4415       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4416       if(toSqr = strchr(str, '-')) {
4417         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4418         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4419       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4420       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4421       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4422       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4423       if(twoBoards) {
4424           DisplayWhiteClock(white_time*fac, to_play == 'W');
4425           DisplayBlackClock(black_time*fac, to_play != 'W');
4426           activePartner = to_play;
4427           if(gamenum != lastBgGame) {
4428               char buf[MSG_SIZ];
4429               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4430               DisplayTitle(buf);
4431           }
4432           lastBgGame = gamenum;
4433           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4434                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4435       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4436                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4437       if(!twoBoards) DisplayMessage(partnerStatus, "");
4438         partnerBoardValid = TRUE;
4439       return;
4440     }
4441
4442     if(appData.dualBoard && appData.bgObserve) {
4443         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4444             SendToICS(ics_prefix), SendToICS("pobserve\n");
4445         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4446             char buf[MSG_SIZ];
4447             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4448             SendToICS(buf);
4449         }
4450     }
4451
4452     /* Modify behavior for initial board display on move listing
4453        of wild games.
4454        */
4455     switch (ics_getting_history) {
4456       case H_FALSE:
4457       case H_REQUESTED:
4458         break;
4459       case H_GOT_REQ_HEADER:
4460       case H_GOT_UNREQ_HEADER:
4461         /* This is the initial position of the current game */
4462         gamenum = ics_gamenum;
4463         moveNum = 0;            /* old ICS bug workaround */
4464         if (to_play == 'B') {
4465           startedFromSetupPosition = TRUE;
4466           blackPlaysFirst = TRUE;
4467           moveNum = 1;
4468           if (forwardMostMove == 0) forwardMostMove = 1;
4469           if (backwardMostMove == 0) backwardMostMove = 1;
4470           if (currentMove == 0) currentMove = 1;
4471         }
4472         newGameMode = gameMode;
4473         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4474         break;
4475       case H_GOT_UNWANTED_HEADER:
4476         /* This is an initial board that we don't want */
4477         return;
4478       case H_GETTING_MOVES:
4479         /* Should not happen */
4480         DisplayError(_("Error gathering move list: extra board"), 0);
4481         ics_getting_history = H_FALSE;
4482         return;
4483     }
4484
4485    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4486                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4487                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4488      /* [HGM] We seem to have switched variant unexpectedly
4489       * Try to guess new variant from board size
4490       */
4491           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4492           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4493           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4494           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4495           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4496           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4497           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4498           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4499           /* Get a move list just to see the header, which
4500              will tell us whether this is really bug or zh */
4501           if (ics_getting_history == H_FALSE) {
4502             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4503             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4504             SendToICS(str);
4505           }
4506     }
4507
4508     /* Take action if this is the first board of a new game, or of a
4509        different game than is currently being displayed.  */
4510     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4511         relation == RELATION_ISOLATED_BOARD) {
4512
4513         /* Forget the old game and get the history (if any) of the new one */
4514         if (gameMode != BeginningOfGame) {
4515           Reset(TRUE, TRUE);
4516         }
4517         newGame = TRUE;
4518         if (appData.autoRaiseBoard) BoardToTop();
4519         prevMove = -3;
4520         if (gamenum == -1) {
4521             newGameMode = IcsIdle;
4522         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4523                    appData.getMoveList && !reqFlag) {
4524             /* Need to get game history */
4525             ics_getting_history = H_REQUESTED;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528         }
4529
4530         /* Initially flip the board to have black on the bottom if playing
4531            black or if the ICS flip flag is set, but let the user change
4532            it with the Flip View button. */
4533         flipView = appData.autoFlipView ?
4534           (newGameMode == IcsPlayingBlack) || ics_flip :
4535           appData.flipView;
4536
4537         /* Done with values from previous mode; copy in new ones */
4538         gameMode = newGameMode;
4539         ModeHighlight();
4540         ics_gamenum = gamenum;
4541         if (gamenum == gs_gamenum) {
4542             int klen = strlen(gs_kind);
4543             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4544             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4545             gameInfo.event = StrSave(str);
4546         } else {
4547             gameInfo.event = StrSave("ICS game");
4548         }
4549         gameInfo.site = StrSave(appData.icsHost);
4550         gameInfo.date = PGNDate();
4551         gameInfo.round = StrSave("-");
4552         gameInfo.white = StrSave(white);
4553         gameInfo.black = StrSave(black);
4554         timeControl = basetime * 60 * 1000;
4555         timeControl_2 = 0;
4556         timeIncrement = increment * 1000;
4557         movesPerSession = 0;
4558         gameInfo.timeControl = TimeControlTagValue();
4559         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4560   if (appData.debugMode) {
4561     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4562     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4563     setbuf(debugFP, NULL);
4564   }
4565
4566         gameInfo.outOfBook = NULL;
4567
4568         /* Do we have the ratings? */
4569         if (strcmp(player1Name, white) == 0 &&
4570             strcmp(player2Name, black) == 0) {
4571             if (appData.debugMode)
4572               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4573                       player1Rating, player2Rating);
4574             gameInfo.whiteRating = player1Rating;
4575             gameInfo.blackRating = player2Rating;
4576         } else if (strcmp(player2Name, white) == 0 &&
4577                    strcmp(player1Name, black) == 0) {
4578             if (appData.debugMode)
4579               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4580                       player2Rating, player1Rating);
4581             gameInfo.whiteRating = player2Rating;
4582             gameInfo.blackRating = player1Rating;
4583         }
4584         player1Name[0] = player2Name[0] = NULLCHAR;
4585
4586         /* Silence shouts if requested */
4587         if (appData.quietPlay &&
4588             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4589             SendToICS(ics_prefix);
4590             SendToICS("set shout 0\n");
4591         }
4592     }
4593
4594     /* Deal with midgame name changes */
4595     if (!newGame) {
4596         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4597             if (gameInfo.white) free(gameInfo.white);
4598             gameInfo.white = StrSave(white);
4599         }
4600         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4601             if (gameInfo.black) free(gameInfo.black);
4602             gameInfo.black = StrSave(black);
4603         }
4604     }
4605
4606     /* Throw away game result if anything actually changes in examine mode */
4607     if (gameMode == IcsExamining && !newGame) {
4608         gameInfo.result = GameUnfinished;
4609         if (gameInfo.resultDetails != NULL) {
4610             free(gameInfo.resultDetails);
4611             gameInfo.resultDetails = NULL;
4612         }
4613     }
4614
4615     /* In pausing && IcsExamining mode, we ignore boards coming
4616        in if they are in a different variation than we are. */
4617     if (pauseExamInvalid) return;
4618     if (pausing && gameMode == IcsExamining) {
4619         if (moveNum <= pauseExamForwardMostMove) {
4620             pauseExamInvalid = TRUE;
4621             forwardMostMove = pauseExamForwardMostMove;
4622             return;
4623         }
4624     }
4625
4626   if (appData.debugMode) {
4627     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4628   }
4629     /* Parse the board */
4630     for (k = 0; k < ranks; k++) {
4631       for (j = 0; j < files; j++)
4632         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4633       if(gameInfo.holdingsWidth > 1) {
4634            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4635            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4636       }
4637     }
4638     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4639       board[5][BOARD_RGHT+1] = WhiteAngel;
4640       board[6][BOARD_RGHT+1] = WhiteMarshall;
4641       board[1][0] = BlackMarshall;
4642       board[2][0] = BlackAngel;
4643       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4644     }
4645     CopyBoard(boards[moveNum], board);
4646     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4647     if (moveNum == 0) {
4648         startedFromSetupPosition =
4649           !CompareBoards(board, initialPosition);
4650         if(startedFromSetupPosition)
4651             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4652     }
4653
4654     /* [HGM] Set castling rights. Take the outermost Rooks,
4655        to make it also work for FRC opening positions. Note that board12
4656        is really defective for later FRC positions, as it has no way to
4657        indicate which Rook can castle if they are on the same side of King.
4658        For the initial position we grant rights to the outermost Rooks,
4659        and remember thos rights, and we then copy them on positions
4660        later in an FRC game. This means WB might not recognize castlings with
4661        Rooks that have moved back to their original position as illegal,
4662        but in ICS mode that is not its job anyway.
4663     */
4664     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4665     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4666
4667         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4668             if(board[0][i] == WhiteRook) j = i;
4669         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4670         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4671             if(board[0][i] == WhiteRook) j = i;
4672         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4673         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4674             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4675         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4676         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4677             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4678         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4679
4680         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4681         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4682         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4683             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4684         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4685             if(board[BOARD_HEIGHT-1][k] == bKing)
4686                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4687         if(gameInfo.variant == VariantTwoKings) {
4688             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4689             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4690             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4691         }
4692     } else { int r;
4693         r = boards[moveNum][CASTLING][0] = initialRights[0];
4694         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4695         r = boards[moveNum][CASTLING][1] = initialRights[1];
4696         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4697         r = boards[moveNum][CASTLING][3] = initialRights[3];
4698         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4699         r = boards[moveNum][CASTLING][4] = initialRights[4];
4700         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4701         /* wildcastle kludge: always assume King has rights */
4702         r = boards[moveNum][CASTLING][2] = initialRights[2];
4703         r = boards[moveNum][CASTLING][5] = initialRights[5];
4704     }
4705     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4706     boards[moveNum][EP_STATUS] = EP_NONE;
4707     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4708     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4709     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4710
4711
4712     if (ics_getting_history == H_GOT_REQ_HEADER ||
4713         ics_getting_history == H_GOT_UNREQ_HEADER) {
4714         /* This was an initial position from a move list, not
4715            the current position */
4716         return;
4717     }
4718
4719     /* Update currentMove and known move number limits */
4720     newMove = newGame || moveNum > forwardMostMove;
4721
4722     if (newGame) {
4723         forwardMostMove = backwardMostMove = currentMove = moveNum;
4724         if (gameMode == IcsExamining && moveNum == 0) {
4725           /* Workaround for ICS limitation: we are not told the wild
4726              type when starting to examine a game.  But if we ask for
4727              the move list, the move list header will tell us */
4728             ics_getting_history = H_REQUESTED;
4729             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4730             SendToICS(str);
4731         }
4732     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4733                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4734 #if ZIPPY
4735         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4736         /* [HGM] applied this also to an engine that is silently watching        */
4737         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4738             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4739             gameInfo.variant == currentlyInitializedVariant) {
4740           takeback = forwardMostMove - moveNum;
4741           for (i = 0; i < takeback; i++) {
4742             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4743             SendToProgram("undo\n", &first);
4744           }
4745         }
4746 #endif
4747
4748         forwardMostMove = moveNum;
4749         if (!pausing || currentMove > forwardMostMove)
4750           currentMove = forwardMostMove;
4751     } else {
4752         /* New part of history that is not contiguous with old part */
4753         if (pausing && gameMode == IcsExamining) {
4754             pauseExamInvalid = TRUE;
4755             forwardMostMove = pauseExamForwardMostMove;
4756             return;
4757         }
4758         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4759 #if ZIPPY
4760             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4761                 // [HGM] when we will receive the move list we now request, it will be
4762                 // fed to the engine from the first move on. So if the engine is not
4763                 // in the initial position now, bring it there.
4764                 InitChessProgram(&first, 0);
4765             }
4766 #endif
4767             ics_getting_history = H_REQUESTED;
4768             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4769             SendToICS(str);
4770         }
4771         forwardMostMove = backwardMostMove = currentMove = moveNum;
4772     }
4773
4774     /* Update the clocks */
4775     if (strchr(elapsed_time, '.')) {
4776       /* Time is in ms */
4777       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4778       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4779     } else {
4780       /* Time is in seconds */
4781       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4782       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4783     }
4784
4785
4786 #if ZIPPY
4787     if (appData.zippyPlay && newGame &&
4788         gameMode != IcsObserving && gameMode != IcsIdle &&
4789         gameMode != IcsExamining)
4790       ZippyFirstBoard(moveNum, basetime, increment);
4791 #endif
4792
4793     /* Put the move on the move list, first converting
4794        to canonical algebraic form. */
4795     if (moveNum > 0) {
4796   if (appData.debugMode) {
4797     int f = forwardMostMove;
4798     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4799             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4800             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4801     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4802     fprintf(debugFP, "moveNum = %d\n", moveNum);
4803     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4804     setbuf(debugFP, NULL);
4805   }
4806         if (moveNum <= backwardMostMove) {
4807             /* We don't know what the board looked like before
4808                this move.  Punt. */
4809           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4810             strcat(parseList[moveNum - 1], " ");
4811             strcat(parseList[moveNum - 1], elapsed_time);
4812             moveList[moveNum - 1][0] = NULLCHAR;
4813         } else if (strcmp(move_str, "none") == 0) {
4814             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4815             /* Again, we don't know what the board looked like;
4816                this is really the start of the game. */
4817             parseList[moveNum - 1][0] = NULLCHAR;
4818             moveList[moveNum - 1][0] = NULLCHAR;
4819             backwardMostMove = moveNum;
4820             startedFromSetupPosition = TRUE;
4821             fromX = fromY = toX = toY = -1;
4822         } else {
4823           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4824           //                 So we parse the long-algebraic move string in stead of the SAN move
4825           int valid; char buf[MSG_SIZ], *prom;
4826
4827           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4828                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4829           // str looks something like "Q/a1-a2"; kill the slash
4830           if(str[1] == '/')
4831             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4832           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4833           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4834                 strcat(buf, prom); // long move lacks promo specification!
4835           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4836                 if(appData.debugMode)
4837                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4838                 safeStrCpy(move_str, buf, MSG_SIZ);
4839           }
4840           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4841                                 &fromX, &fromY, &toX, &toY, &promoChar)
4842                || ParseOneMove(buf, moveNum - 1, &moveType,
4843                                 &fromX, &fromY, &toX, &toY, &promoChar);
4844           // end of long SAN patch
4845           if (valid) {
4846             (void) CoordsToAlgebraic(boards[moveNum - 1],
4847                                      PosFlags(moveNum - 1),
4848                                      fromY, fromX, toY, toX, promoChar,
4849                                      parseList[moveNum-1]);
4850             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4851               case MT_NONE:
4852               case MT_STALEMATE:
4853               default:
4854                 break;
4855               case MT_CHECK:
4856                 if(!IS_SHOGI(gameInfo.variant))
4857                     strcat(parseList[moveNum - 1], "+");
4858                 break;
4859               case MT_CHECKMATE:
4860               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4861                 strcat(parseList[moveNum - 1], "#");
4862                 break;
4863             }
4864             strcat(parseList[moveNum - 1], " ");
4865             strcat(parseList[moveNum - 1], elapsed_time);
4866             /* currentMoveString is set as a side-effect of ParseOneMove */
4867             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4868             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4869             strcat(moveList[moveNum - 1], "\n");
4870
4871             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4872                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4873               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4874                 ChessSquare old, new = boards[moveNum][k][j];
4875                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4876                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4877                   if(old == new) continue;
4878                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4879                   else if(new == WhiteWazir || new == BlackWazir) {
4880                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4881                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4882                       else boards[moveNum][k][j] = old; // preserve type of Gold
4883                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4884                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4885               }
4886           } else {
4887             /* Move from ICS was illegal!?  Punt. */
4888             if (appData.debugMode) {
4889               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4890               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4891             }
4892             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4893             strcat(parseList[moveNum - 1], " ");
4894             strcat(parseList[moveNum - 1], elapsed_time);
4895             moveList[moveNum - 1][0] = NULLCHAR;
4896             fromX = fromY = toX = toY = -1;
4897           }
4898         }
4899   if (appData.debugMode) {
4900     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4901     setbuf(debugFP, NULL);
4902   }
4903
4904 #if ZIPPY
4905         /* Send move to chess program (BEFORE animating it). */
4906         if (appData.zippyPlay && !newGame && newMove &&
4907            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4908
4909             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4910                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4911                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4912                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4913                             move_str);
4914                     DisplayError(str, 0);
4915                 } else {
4916                     if (first.sendTime) {
4917                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4918                     }
4919                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4920                     if (firstMove && !bookHit) {
4921                         firstMove = FALSE;
4922                         if (first.useColors) {
4923                           SendToProgram(gameMode == IcsPlayingWhite ?
4924                                         "white\ngo\n" :
4925                                         "black\ngo\n", &first);
4926                         } else {
4927                           SendToProgram("go\n", &first);
4928                         }
4929                         first.maybeThinking = TRUE;
4930                     }
4931                 }
4932             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4933               if (moveList[moveNum - 1][0] == NULLCHAR) {
4934                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4935                 DisplayError(str, 0);
4936               } else {
4937                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4938                 SendMoveToProgram(moveNum - 1, &first);
4939               }
4940             }
4941         }
4942 #endif
4943     }
4944
4945     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4946         /* If move comes from a remote source, animate it.  If it
4947            isn't remote, it will have already been animated. */
4948         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4949             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4950         }
4951         if (!pausing && appData.highlightLastMove) {
4952             SetHighlights(fromX, fromY, toX, toY);
4953         }
4954     }
4955
4956     /* Start the clocks */
4957     whiteFlag = blackFlag = FALSE;
4958     appData.clockMode = !(basetime == 0 && increment == 0);
4959     if (ticking == 0) {
4960       ics_clock_paused = TRUE;
4961       StopClocks();
4962     } else if (ticking == 1) {
4963       ics_clock_paused = FALSE;
4964     }
4965     if (gameMode == IcsIdle ||
4966         relation == RELATION_OBSERVING_STATIC ||
4967         relation == RELATION_EXAMINING ||
4968         ics_clock_paused)
4969       DisplayBothClocks();
4970     else
4971       StartClocks();
4972
4973     /* Display opponents and material strengths */
4974     if (gameInfo.variant != VariantBughouse &&
4975         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4976         if (tinyLayout || smallLayout) {
4977             if(gameInfo.variant == VariantNormal)
4978               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4979                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4980                     basetime, increment);
4981             else
4982               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4983                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4984                     basetime, increment, (int) gameInfo.variant);
4985         } else {
4986             if(gameInfo.variant == VariantNormal)
4987               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4988                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4989                     basetime, increment);
4990             else
4991               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4992                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4993                     basetime, increment, VariantName(gameInfo.variant));
4994         }
4995         DisplayTitle(str);
4996   if (appData.debugMode) {
4997     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4998   }
4999     }
5000
5001
5002     /* Display the board */
5003     if (!pausing && !appData.noGUI) {
5004
5005       if (appData.premove)
5006           if (!gotPremove ||
5007              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5008              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5009               ClearPremoveHighlights();
5010
5011       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5012         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5013       DrawPosition(j, boards[currentMove]);
5014
5015       DisplayMove(moveNum - 1);
5016       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5017             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5018               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5019         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5020       }
5021     }
5022
5023     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5024 #if ZIPPY
5025     if(bookHit) { // [HGM] book: simulate book reply
5026         static char bookMove[MSG_SIZ]; // a bit generous?
5027
5028         programStats.nodes = programStats.depth = programStats.time =
5029         programStats.score = programStats.got_only_move = 0;
5030         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5031
5032         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5033         strcat(bookMove, bookHit);
5034         HandleMachineMove(bookMove, &first);
5035     }
5036 #endif
5037 }
5038
5039 void
5040 GetMoveListEvent ()
5041 {
5042     char buf[MSG_SIZ];
5043     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5044         ics_getting_history = H_REQUESTED;
5045         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5046         SendToICS(buf);
5047     }
5048 }
5049
5050 void
5051 SendToBoth (char *msg)
5052 {   // to make it easy to keep two engines in step in dual analysis
5053     SendToProgram(msg, &first);
5054     if(second.analyzing) SendToProgram(msg, &second);
5055 }
5056
5057 void
5058 AnalysisPeriodicEvent (int force)
5059 {
5060     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5061          && !force) || !appData.periodicUpdates)
5062       return;
5063
5064     /* Send . command to Crafty to collect stats */
5065     SendToBoth(".\n");
5066
5067     /* Don't send another until we get a response (this makes
5068        us stop sending to old Crafty's which don't understand
5069        the "." command (sending illegal cmds resets node count & time,
5070        which looks bad)) */
5071     programStats.ok_to_send = 0;
5072 }
5073
5074 void
5075 ics_update_width (int new_width)
5076 {
5077         ics_printf("set width %d\n", new_width);
5078 }
5079
5080 void
5081 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5082 {
5083     char buf[MSG_SIZ];
5084
5085     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5086         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5087             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5088             SendToProgram(buf, cps);
5089             return;
5090         }
5091         // null move in variant where engine does not understand it (for analysis purposes)
5092         SendBoard(cps, moveNum + 1); // send position after move in stead.
5093         return;
5094     }
5095     if (cps->useUsermove) {
5096       SendToProgram("usermove ", cps);
5097     }
5098     if (cps->useSAN) {
5099       char *space;
5100       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5101         int len = space - parseList[moveNum];
5102         memcpy(buf, parseList[moveNum], len);
5103         buf[len++] = '\n';
5104         buf[len] = NULLCHAR;
5105       } else {
5106         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5107       }
5108       SendToProgram(buf, cps);
5109     } else {
5110       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5111         AlphaRank(moveList[moveNum], 4);
5112         SendToProgram(moveList[moveNum], cps);
5113         AlphaRank(moveList[moveNum], 4); // and back
5114       } else
5115       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5116        * the engine. It would be nice to have a better way to identify castle
5117        * moves here. */
5118       if(appData.fischerCastling && cps->useOOCastle) {
5119         int fromX = moveList[moveNum][0] - AAA;
5120         int fromY = moveList[moveNum][1] - ONE;
5121         int toX = moveList[moveNum][2] - AAA;
5122         int toY = moveList[moveNum][3] - ONE;
5123         if((boards[moveNum][fromY][fromX] == WhiteKing
5124             && boards[moveNum][toY][toX] == WhiteRook)
5125            || (boards[moveNum][fromY][fromX] == BlackKing
5126                && boards[moveNum][toY][toX] == BlackRook)) {
5127           if(toX > fromX) SendToProgram("O-O\n", cps);
5128           else SendToProgram("O-O-O\n", cps);
5129         }
5130         else SendToProgram(moveList[moveNum], cps);
5131       } else
5132       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5133           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5134                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5135                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5136                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5137           SendToProgram(buf, cps);
5138       } else
5139       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5140         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5141           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5142           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5143                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5144         } else
5145           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5146                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5147         SendToProgram(buf, cps);
5148       }
5149       else SendToProgram(moveList[moveNum], cps);
5150       /* End of additions by Tord */
5151     }
5152
5153     /* [HGM] setting up the opening has brought engine in force mode! */
5154     /*       Send 'go' if we are in a mode where machine should play. */
5155     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5156         (gameMode == TwoMachinesPlay   ||
5157 #if ZIPPY
5158          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5159 #endif
5160          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5161         SendToProgram("go\n", cps);
5162   if (appData.debugMode) {
5163     fprintf(debugFP, "(extra)\n");
5164   }
5165     }
5166     setboardSpoiledMachineBlack = 0;
5167 }
5168
5169 void
5170 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5171 {
5172     char user_move[MSG_SIZ];
5173     char suffix[4];
5174
5175     if(gameInfo.variant == VariantSChess && promoChar) {
5176         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5177         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5178     } else suffix[0] = NULLCHAR;
5179
5180     switch (moveType) {
5181       default:
5182         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5183                 (int)moveType, fromX, fromY, toX, toY);
5184         DisplayError(user_move + strlen("say "), 0);
5185         break;
5186       case WhiteKingSideCastle:
5187       case BlackKingSideCastle:
5188       case WhiteQueenSideCastleWild:
5189       case BlackQueenSideCastleWild:
5190       /* PUSH Fabien */
5191       case WhiteHSideCastleFR:
5192       case BlackHSideCastleFR:
5193       /* POP Fabien */
5194         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5195         break;
5196       case WhiteQueenSideCastle:
5197       case BlackQueenSideCastle:
5198       case WhiteKingSideCastleWild:
5199       case BlackKingSideCastleWild:
5200       /* PUSH Fabien */
5201       case WhiteASideCastleFR:
5202       case BlackASideCastleFR:
5203       /* POP Fabien */
5204         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5205         break;
5206       case WhiteNonPromotion:
5207       case BlackNonPromotion:
5208         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5209         break;
5210       case WhitePromotion:
5211       case BlackPromotion:
5212         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5213            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5214           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5215                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5216                 PieceToChar(WhiteFerz));
5217         else if(gameInfo.variant == VariantGreat)
5218           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5219                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5220                 PieceToChar(WhiteMan));
5221         else
5222           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5223                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5224                 promoChar);
5225         break;
5226       case WhiteDrop:
5227       case BlackDrop:
5228       drop:
5229         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5230                  ToUpper(PieceToChar((ChessSquare) fromX)),
5231                  AAA + toX, ONE + toY);
5232         break;
5233       case IllegalMove:  /* could be a variant we don't quite understand */
5234         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5235       case NormalMove:
5236       case WhiteCapturesEnPassant:
5237       case BlackCapturesEnPassant:
5238         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5239                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5240         break;
5241     }
5242     SendToICS(user_move);
5243     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5244         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5245 }
5246
5247 void
5248 UploadGameEvent ()
5249 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5250     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5251     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5252     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5253       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5254       return;
5255     }
5256     if(gameMode != IcsExamining) { // is this ever not the case?
5257         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5258
5259         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5260           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5261         } else { // on FICS we must first go to general examine mode
5262           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5263         }
5264         if(gameInfo.variant != VariantNormal) {
5265             // try figure out wild number, as xboard names are not always valid on ICS
5266             for(i=1; i<=36; i++) {
5267               snprintf(buf, MSG_SIZ, "wild/%d", i);
5268                 if(StringToVariant(buf) == gameInfo.variant) break;
5269             }
5270             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5271             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5272             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5273         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5274         SendToICS(ics_prefix);
5275         SendToICS(buf);
5276         if(startedFromSetupPosition || backwardMostMove != 0) {
5277           fen = PositionToFEN(backwardMostMove, NULL, 1);
5278           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5279             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5280             SendToICS(buf);
5281           } else { // FICS: everything has to set by separate bsetup commands
5282             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5283             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5284             SendToICS(buf);
5285             if(!WhiteOnMove(backwardMostMove)) {
5286                 SendToICS("bsetup tomove black\n");
5287             }
5288             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5289             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5290             SendToICS(buf);
5291             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5292             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5293             SendToICS(buf);
5294             i = boards[backwardMostMove][EP_STATUS];
5295             if(i >= 0) { // set e.p.
5296               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5297                 SendToICS(buf);
5298             }
5299             bsetup++;
5300           }
5301         }
5302       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5303             SendToICS("bsetup done\n"); // switch to normal examining.
5304     }
5305     for(i = backwardMostMove; i<last; i++) {
5306         char buf[20];
5307         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5308         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5309             int len = strlen(moveList[i]);
5310             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5311             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5312         }
5313         SendToICS(buf);
5314     }
5315     SendToICS(ics_prefix);
5316     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5317 }
5318
5319 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5320
5321 void
5322 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5323 {
5324     if (rf == DROP_RANK) {
5325       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5326       sprintf(move, "%c@%c%c\n",
5327                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5328     } else {
5329         if (promoChar == 'x' || promoChar == NULLCHAR) {
5330           sprintf(move, "%c%c%c%c\n",
5331                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5332           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5333         } else {
5334             sprintf(move, "%c%c%c%c%c\n",
5335                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5336         }
5337     }
5338 }
5339
5340 void
5341 ProcessICSInitScript (FILE *f)
5342 {
5343     char buf[MSG_SIZ];
5344
5345     while (fgets(buf, MSG_SIZ, f)) {
5346         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5347     }
5348
5349     fclose(f);
5350 }
5351
5352
5353 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5354 int dragging;
5355 static ClickType lastClickType;
5356
5357 int
5358 Partner (ChessSquare *p)
5359 { // change piece into promotion partner if one shogi-promotes to the other
5360   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5361   ChessSquare partner;
5362   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5363   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5364   *p = partner;
5365   return 1;
5366 }
5367
5368 void
5369 Sweep (int step)
5370 {
5371     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5372     static int toggleFlag;
5373     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5374     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5375     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5376     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5377     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5378     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5379     do {
5380         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5381         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5382         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5383         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5384         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5385         if(!step) step = -1;
5386     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5387             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5388             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5389             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5390     if(toX >= 0) {
5391         int victim = boards[currentMove][toY][toX];
5392         boards[currentMove][toY][toX] = promoSweep;
5393         DrawPosition(FALSE, boards[currentMove]);
5394         boards[currentMove][toY][toX] = victim;
5395     } else
5396     ChangeDragPiece(promoSweep);
5397 }
5398
5399 int
5400 PromoScroll (int x, int y)
5401 {
5402   int step = 0;
5403
5404   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5405   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5406   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5407   if(!step) return FALSE;
5408   lastX = x; lastY = y;
5409   if((promoSweep < BlackPawn) == flipView) step = -step;
5410   if(step > 0) selectFlag = 1;
5411   if(!selectFlag) Sweep(step);
5412   return FALSE;
5413 }
5414
5415 void
5416 NextPiece (int step)
5417 {
5418     ChessSquare piece = boards[currentMove][toY][toX];
5419     do {
5420         pieceSweep -= step;
5421         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5422         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5423         if(!step) step = -1;
5424     } while(PieceToChar(pieceSweep) == '.');
5425     boards[currentMove][toY][toX] = pieceSweep;
5426     DrawPosition(FALSE, boards[currentMove]);
5427     boards[currentMove][toY][toX] = piece;
5428 }
5429 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5430 void
5431 AlphaRank (char *move, int n)
5432 {
5433 //    char *p = move, c; int x, y;
5434
5435     if (appData.debugMode) {
5436         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5437     }
5438
5439     if(move[1]=='*' &&
5440        move[2]>='0' && move[2]<='9' &&
5441        move[3]>='a' && move[3]<='x'    ) {
5442         move[1] = '@';
5443         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5444         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5445     } else
5446     if(move[0]>='0' && move[0]<='9' &&
5447        move[1]>='a' && move[1]<='x' &&
5448        move[2]>='0' && move[2]<='9' &&
5449        move[3]>='a' && move[3]<='x'    ) {
5450         /* input move, Shogi -> normal */
5451         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5452         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5453         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5454         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5455     } else
5456     if(move[1]=='@' &&
5457        move[3]>='0' && move[3]<='9' &&
5458        move[2]>='a' && move[2]<='x'    ) {
5459         move[1] = '*';
5460         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5461         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5462     } else
5463     if(
5464        move[0]>='a' && move[0]<='x' &&
5465        move[3]>='0' && move[3]<='9' &&
5466        move[2]>='a' && move[2]<='x'    ) {
5467          /* output move, normal -> Shogi */
5468         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5469         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5470         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5471         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5472         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5473     }
5474     if (appData.debugMode) {
5475         fprintf(debugFP, "   out = '%s'\n", move);
5476     }
5477 }
5478
5479 char yy_textstr[8000];
5480
5481 /* Parser for moves from gnuchess, ICS, or user typein box */
5482 Boolean
5483 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5484 {
5485     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5486
5487     switch (*moveType) {
5488       case WhitePromotion:
5489       case BlackPromotion:
5490       case WhiteNonPromotion:
5491       case BlackNonPromotion:
5492       case NormalMove:
5493       case FirstLeg:
5494       case WhiteCapturesEnPassant:
5495       case BlackCapturesEnPassant:
5496       case WhiteKingSideCastle:
5497       case WhiteQueenSideCastle:
5498       case BlackKingSideCastle:
5499       case BlackQueenSideCastle:
5500       case WhiteKingSideCastleWild:
5501       case WhiteQueenSideCastleWild:
5502       case BlackKingSideCastleWild:
5503       case BlackQueenSideCastleWild:
5504       /* Code added by Tord: */
5505       case WhiteHSideCastleFR:
5506       case WhiteASideCastleFR:
5507       case BlackHSideCastleFR:
5508       case BlackASideCastleFR:
5509       /* End of code added by Tord */
5510       case IllegalMove:         /* bug or odd chess variant */
5511         *fromX = currentMoveString[0] - AAA;
5512         *fromY = currentMoveString[1] - ONE;
5513         *toX = currentMoveString[2] - AAA;
5514         *toY = currentMoveString[3] - ONE;
5515         *promoChar = currentMoveString[4];
5516         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5517             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5518     if (appData.debugMode) {
5519         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5520     }
5521             *fromX = *fromY = *toX = *toY = 0;
5522             return FALSE;
5523         }
5524         if (appData.testLegality) {
5525           return (*moveType != IllegalMove);
5526         } else {
5527           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5528                          // [HGM] lion: if this is a double move we are less critical
5529                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5530         }
5531
5532       case WhiteDrop:
5533       case BlackDrop:
5534         *fromX = *moveType == WhiteDrop ?
5535           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5536           (int) CharToPiece(ToLower(currentMoveString[0]));
5537         *fromY = DROP_RANK;
5538         *toX = currentMoveString[2] - AAA;
5539         *toY = currentMoveString[3] - ONE;
5540         *promoChar = NULLCHAR;
5541         return TRUE;
5542
5543       case AmbiguousMove:
5544       case ImpossibleMove:
5545       case EndOfFile:
5546       case ElapsedTime:
5547       case Comment:
5548       case PGNTag:
5549       case NAG:
5550       case WhiteWins:
5551       case BlackWins:
5552       case GameIsDrawn:
5553       default:
5554     if (appData.debugMode) {
5555         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5556     }
5557         /* bug? */
5558         *fromX = *fromY = *toX = *toY = 0;
5559         *promoChar = NULLCHAR;
5560         return FALSE;
5561     }
5562 }
5563
5564 Boolean pushed = FALSE;
5565 char *lastParseAttempt;
5566
5567 void
5568 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5569 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5570   int fromX, fromY, toX, toY; char promoChar;
5571   ChessMove moveType;
5572   Boolean valid;
5573   int nr = 0;
5574
5575   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5576   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5577     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5578     pushed = TRUE;
5579   }
5580   endPV = forwardMostMove;
5581   do {
5582     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5583     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5584     lastParseAttempt = pv;
5585     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5586     if(!valid && nr == 0 &&
5587        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5588         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5589         // Hande case where played move is different from leading PV move
5590         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5591         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5592         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5593         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5594           endPV += 2; // if position different, keep this
5595           moveList[endPV-1][0] = fromX + AAA;
5596           moveList[endPV-1][1] = fromY + ONE;
5597           moveList[endPV-1][2] = toX + AAA;
5598           moveList[endPV-1][3] = toY + ONE;
5599           parseList[endPV-1][0] = NULLCHAR;
5600           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5601         }
5602       }
5603     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5604     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5605     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5606     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5607         valid++; // allow comments in PV
5608         continue;
5609     }
5610     nr++;
5611     if(endPV+1 > framePtr) break; // no space, truncate
5612     if(!valid) break;
5613     endPV++;
5614     CopyBoard(boards[endPV], boards[endPV-1]);
5615     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5616     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5617     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5618     CoordsToAlgebraic(boards[endPV - 1],
5619                              PosFlags(endPV - 1),
5620                              fromY, fromX, toY, toX, promoChar,
5621                              parseList[endPV - 1]);
5622   } while(valid);
5623   if(atEnd == 2) return; // used hidden, for PV conversion
5624   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5625   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5626   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5627                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5628   DrawPosition(TRUE, boards[currentMove]);
5629 }
5630
5631 int
5632 MultiPV (ChessProgramState *cps)
5633 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5634         int i;
5635         for(i=0; i<cps->nrOptions; i++)
5636             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5637                 return i;
5638         return -1;
5639 }
5640
5641 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5642
5643 Boolean
5644 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5645 {
5646         int startPV, multi, lineStart, origIndex = index;
5647         char *p, buf2[MSG_SIZ];
5648         ChessProgramState *cps = (pane ? &second : &first);
5649
5650         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5651         lastX = x; lastY = y;
5652         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5653         lineStart = startPV = index;
5654         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5655         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5656         index = startPV;
5657         do{ while(buf[index] && buf[index] != '\n') index++;
5658         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5659         buf[index] = 0;
5660         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5661                 int n = cps->option[multi].value;
5662                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5663                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5664                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5665                 cps->option[multi].value = n;
5666                 *start = *end = 0;
5667                 return FALSE;
5668         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5669                 ExcludeClick(origIndex - lineStart);
5670                 return FALSE;
5671         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5672                 Collapse(origIndex - lineStart);
5673                 return FALSE;
5674         }
5675         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5676         *start = startPV; *end = index-1;
5677         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5678         return TRUE;
5679 }
5680
5681 char *
5682 PvToSAN (char *pv)
5683 {
5684         static char buf[10*MSG_SIZ];
5685         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5686         *buf = NULLCHAR;
5687         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5688         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5689         for(i = forwardMostMove; i<endPV; i++){
5690             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5691             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5692             k += strlen(buf+k);
5693         }
5694         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5695         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5696         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5697         endPV = savedEnd;
5698         return buf;
5699 }
5700
5701 Boolean
5702 LoadPV (int x, int y)
5703 { // called on right mouse click to load PV
5704   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5705   lastX = x; lastY = y;
5706   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5707   extendGame = FALSE;
5708   return TRUE;
5709 }
5710
5711 void
5712 UnLoadPV ()
5713 {
5714   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5715   if(endPV < 0) return;
5716   if(appData.autoCopyPV) CopyFENToClipboard();
5717   endPV = -1;
5718   if(extendGame && currentMove > forwardMostMove) {
5719         Boolean saveAnimate = appData.animate;
5720         if(pushed) {
5721             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5722                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5723             } else storedGames--; // abandon shelved tail of original game
5724         }
5725         pushed = FALSE;
5726         forwardMostMove = currentMove;
5727         currentMove = oldFMM;
5728         appData.animate = FALSE;
5729         ToNrEvent(forwardMostMove);
5730         appData.animate = saveAnimate;
5731   }
5732   currentMove = forwardMostMove;
5733   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5734   ClearPremoveHighlights();
5735   DrawPosition(TRUE, boards[currentMove]);
5736 }
5737
5738 void
5739 MovePV (int x, int y, int h)
5740 { // step through PV based on mouse coordinates (called on mouse move)
5741   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5742
5743   // we must somehow check if right button is still down (might be released off board!)
5744   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5745   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5746   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5747   if(!step) return;
5748   lastX = x; lastY = y;
5749
5750   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5751   if(endPV < 0) return;
5752   if(y < margin) step = 1; else
5753   if(y > h - margin) step = -1;
5754   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5755   currentMove += step;
5756   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5757   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5758                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5759   DrawPosition(FALSE, boards[currentMove]);
5760 }
5761
5762
5763 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5764 // All positions will have equal probability, but the current method will not provide a unique
5765 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5766 #define DARK 1
5767 #define LITE 2
5768 #define ANY 3
5769
5770 int squaresLeft[4];
5771 int piecesLeft[(int)BlackPawn];
5772 int seed, nrOfShuffles;
5773
5774 void
5775 GetPositionNumber ()
5776 {       // sets global variable seed
5777         int i;
5778
5779         seed = appData.defaultFrcPosition;
5780         if(seed < 0) { // randomize based on time for negative FRC position numbers
5781                 for(i=0; i<50; i++) seed += random();
5782                 seed = random() ^ random() >> 8 ^ random() << 8;
5783                 if(seed<0) seed = -seed;
5784         }
5785 }
5786
5787 int
5788 put (Board board, int pieceType, int rank, int n, int shade)
5789 // put the piece on the (n-1)-th empty squares of the given shade
5790 {
5791         int i;
5792
5793         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5794                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5795                         board[rank][i] = (ChessSquare) pieceType;
5796                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5797                         squaresLeft[ANY]--;
5798                         piecesLeft[pieceType]--;
5799                         return i;
5800                 }
5801         }
5802         return -1;
5803 }
5804
5805
5806 void
5807 AddOnePiece (Board board, int pieceType, int rank, int shade)
5808 // calculate where the next piece goes, (any empty square), and put it there
5809 {
5810         int i;
5811
5812         i = seed % squaresLeft[shade];
5813         nrOfShuffles *= squaresLeft[shade];
5814         seed /= squaresLeft[shade];
5815         put(board, pieceType, rank, i, shade);
5816 }
5817
5818 void
5819 AddTwoPieces (Board board, int pieceType, int rank)
5820 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5821 {
5822         int i, n=squaresLeft[ANY], j=n-1, k;
5823
5824         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5825         i = seed % k;  // pick one
5826         nrOfShuffles *= k;
5827         seed /= k;
5828         while(i >= j) i -= j--;
5829         j = n - 1 - j; i += j;
5830         put(board, pieceType, rank, j, ANY);
5831         put(board, pieceType, rank, i, ANY);
5832 }
5833
5834 void
5835 SetUpShuffle (Board board, int number)
5836 {
5837         int i, p, first=1;
5838
5839         GetPositionNumber(); nrOfShuffles = 1;
5840
5841         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5842         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5843         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5844
5845         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5846
5847         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5848             p = (int) board[0][i];
5849             if(p < (int) BlackPawn) piecesLeft[p] ++;
5850             board[0][i] = EmptySquare;
5851         }
5852
5853         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5854             // shuffles restricted to allow normal castling put KRR first
5855             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5856                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5857             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5858                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5859             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5860                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5861             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5862                 put(board, WhiteRook, 0, 0, ANY);
5863             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5864         }
5865
5866         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5867             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5868             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5869                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5870                 while(piecesLeft[p] >= 2) {
5871                     AddOnePiece(board, p, 0, LITE);
5872                     AddOnePiece(board, p, 0, DARK);
5873                 }
5874                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5875             }
5876
5877         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5878             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5879             // but we leave King and Rooks for last, to possibly obey FRC restriction
5880             if(p == (int)WhiteRook) continue;
5881             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5882             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5883         }
5884
5885         // now everything is placed, except perhaps King (Unicorn) and Rooks
5886
5887         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5888             // Last King gets castling rights
5889             while(piecesLeft[(int)WhiteUnicorn]) {
5890                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5891                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5892             }
5893
5894             while(piecesLeft[(int)WhiteKing]) {
5895                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5896                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5897             }
5898
5899
5900         } else {
5901             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5902             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5903         }
5904
5905         // Only Rooks can be left; simply place them all
5906         while(piecesLeft[(int)WhiteRook]) {
5907                 i = put(board, WhiteRook, 0, 0, ANY);
5908                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5909                         if(first) {
5910                                 first=0;
5911                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5912                         }
5913                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5914                 }
5915         }
5916         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5917             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5918         }
5919
5920         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5921 }
5922
5923 int
5924 SetCharTable (char *table, const char * map)
5925 /* [HGM] moved here from winboard.c because of its general usefulness */
5926 /*       Basically a safe strcpy that uses the last character as King */
5927 {
5928     int result = FALSE; int NrPieces;
5929
5930     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5931                     && NrPieces >= 12 && !(NrPieces&1)) {
5932         int i; /* [HGM] Accept even length from 12 to 34 */
5933
5934         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5935         for( i=0; i<NrPieces/2-1; i++ ) {
5936             table[i] = map[i];
5937             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5938         }
5939         table[(int) WhiteKing]  = map[NrPieces/2-1];
5940         table[(int) BlackKing]  = map[NrPieces-1];
5941
5942         result = TRUE;
5943     }
5944
5945     return result;
5946 }
5947
5948 void
5949 Prelude (Board board)
5950 {       // [HGM] superchess: random selection of exo-pieces
5951         int i, j, k; ChessSquare p;
5952         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5953
5954         GetPositionNumber(); // use FRC position number
5955
5956         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5957             SetCharTable(pieceToChar, appData.pieceToCharTable);
5958             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5959                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5960         }
5961
5962         j = seed%4;                 seed /= 4;
5963         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5964         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5965         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5966         j = seed%3 + (seed%3 >= j); seed /= 3;
5967         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5968         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5969         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5970         j = seed%3;                 seed /= 3;
5971         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5972         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5973         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5974         j = seed%2 + (seed%2 >= j); seed /= 2;
5975         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5976         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5977         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5978         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5979         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5980         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5981         put(board, exoPieces[0],    0, 0, ANY);
5982         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5983 }
5984
5985 void
5986 InitPosition (int redraw)
5987 {
5988     ChessSquare (* pieces)[BOARD_FILES];
5989     int i, j, pawnRow=1, pieceRows=1, overrule,
5990     oldx = gameInfo.boardWidth,
5991     oldy = gameInfo.boardHeight,
5992     oldh = gameInfo.holdingsWidth;
5993     static int oldv;
5994
5995     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5996
5997     /* [AS] Initialize pv info list [HGM] and game status */
5998     {
5999         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6000             pvInfoList[i].depth = 0;
6001             boards[i][EP_STATUS] = EP_NONE;
6002             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6003         }
6004
6005         initialRulePlies = 0; /* 50-move counter start */
6006
6007         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6008         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6009     }
6010
6011
6012     /* [HGM] logic here is completely changed. In stead of full positions */
6013     /* the initialized data only consist of the two backranks. The switch */
6014     /* selects which one we will use, which is than copied to the Board   */
6015     /* initialPosition, which for the rest is initialized by Pawns and    */
6016     /* empty squares. This initial position is then copied to boards[0],  */
6017     /* possibly after shuffling, so that it remains available.            */
6018
6019     gameInfo.holdingsWidth = 0; /* default board sizes */
6020     gameInfo.boardWidth    = 8;
6021     gameInfo.boardHeight   = 8;
6022     gameInfo.holdingsSize  = 0;
6023     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6024     for(i=0; i<BOARD_FILES-2; i++)
6025       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6026     initialPosition[EP_STATUS] = EP_NONE;
6027     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6028     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6029          SetCharTable(pieceNickName, appData.pieceNickNames);
6030     else SetCharTable(pieceNickName, "............");
6031     pieces = FIDEArray;
6032
6033     switch (gameInfo.variant) {
6034     case VariantFischeRandom:
6035       shuffleOpenings = TRUE;
6036       appData.fischerCastling = TRUE;
6037     default:
6038       break;
6039     case VariantShatranj:
6040       pieces = ShatranjArray;
6041       nrCastlingRights = 0;
6042       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6043       break;
6044     case VariantMakruk:
6045       pieces = makrukArray;
6046       nrCastlingRights = 0;
6047       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6048       break;
6049     case VariantASEAN:
6050       pieces = aseanArray;
6051       nrCastlingRights = 0;
6052       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6053       break;
6054     case VariantTwoKings:
6055       pieces = twoKingsArray;
6056       break;
6057     case VariantGrand:
6058       pieces = GrandArray;
6059       nrCastlingRights = 0;
6060       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6061       gameInfo.boardWidth = 10;
6062       gameInfo.boardHeight = 10;
6063       gameInfo.holdingsSize = 7;
6064       break;
6065     case VariantCapaRandom:
6066       shuffleOpenings = TRUE;
6067       appData.fischerCastling = TRUE;
6068     case VariantCapablanca:
6069       pieces = CapablancaArray;
6070       gameInfo.boardWidth = 10;
6071       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6072       break;
6073     case VariantGothic:
6074       pieces = GothicArray;
6075       gameInfo.boardWidth = 10;
6076       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6077       break;
6078     case VariantSChess:
6079       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6080       gameInfo.holdingsSize = 7;
6081       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6082       break;
6083     case VariantJanus:
6084       pieces = JanusArray;
6085       gameInfo.boardWidth = 10;
6086       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6087       nrCastlingRights = 6;
6088         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6089         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6090         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6091         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6092         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6093         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6094       break;
6095     case VariantFalcon:
6096       pieces = FalconArray;
6097       gameInfo.boardWidth = 10;
6098       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6099       break;
6100     case VariantXiangqi:
6101       pieces = XiangqiArray;
6102       gameInfo.boardWidth  = 9;
6103       gameInfo.boardHeight = 10;
6104       nrCastlingRights = 0;
6105       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6106       break;
6107     case VariantShogi:
6108       pieces = ShogiArray;
6109       gameInfo.boardWidth  = 9;
6110       gameInfo.boardHeight = 9;
6111       gameInfo.holdingsSize = 7;
6112       nrCastlingRights = 0;
6113       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6114       break;
6115     case VariantChu:
6116       pieces = ChuArray; pieceRows = 3;
6117       gameInfo.boardWidth  = 12;
6118       gameInfo.boardHeight = 12;
6119       nrCastlingRights = 0;
6120       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6121                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6122       break;
6123     case VariantCourier:
6124       pieces = CourierArray;
6125       gameInfo.boardWidth  = 12;
6126       nrCastlingRights = 0;
6127       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6128       break;
6129     case VariantKnightmate:
6130       pieces = KnightmateArray;
6131       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6132       break;
6133     case VariantSpartan:
6134       pieces = SpartanArray;
6135       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6136       break;
6137     case VariantLion:
6138       pieces = lionArray;
6139       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6140       break;
6141     case VariantChuChess:
6142       pieces = ChuChessArray;
6143       gameInfo.boardWidth = 10;
6144       gameInfo.boardHeight = 10;
6145       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6146       break;
6147     case VariantFairy:
6148       pieces = fairyArray;
6149       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6150       break;
6151     case VariantGreat:
6152       pieces = GreatArray;
6153       gameInfo.boardWidth = 10;
6154       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6155       gameInfo.holdingsSize = 8;
6156       break;
6157     case VariantSuper:
6158       pieces = FIDEArray;
6159       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6160       gameInfo.holdingsSize = 8;
6161       startedFromSetupPosition = TRUE;
6162       break;
6163     case VariantCrazyhouse:
6164     case VariantBughouse:
6165       pieces = FIDEArray;
6166       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6167       gameInfo.holdingsSize = 5;
6168       break;
6169     case VariantWildCastle:
6170       pieces = FIDEArray;
6171       /* !!?shuffle with kings guaranteed to be on d or e file */
6172       shuffleOpenings = 1;
6173       break;
6174     case VariantNoCastle:
6175       pieces = FIDEArray;
6176       nrCastlingRights = 0;
6177       /* !!?unconstrained back-rank shuffle */
6178       shuffleOpenings = 1;
6179       break;
6180     }
6181
6182     overrule = 0;
6183     if(appData.NrFiles >= 0) {
6184         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6185         gameInfo.boardWidth = appData.NrFiles;
6186     }
6187     if(appData.NrRanks >= 0) {
6188         gameInfo.boardHeight = appData.NrRanks;
6189     }
6190     if(appData.holdingsSize >= 0) {
6191         i = appData.holdingsSize;
6192         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6193         gameInfo.holdingsSize = i;
6194     }
6195     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6196     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6197         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6198
6199     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6200     if(pawnRow < 1) pawnRow = 1;
6201     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6202        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6203     if(gameInfo.variant == VariantChu) pawnRow = 3;
6204
6205     /* User pieceToChar list overrules defaults */
6206     if(appData.pieceToCharTable != NULL)
6207         SetCharTable(pieceToChar, appData.pieceToCharTable);
6208
6209     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6210
6211         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6212             s = (ChessSquare) 0; /* account holding counts in guard band */
6213         for( i=0; i<BOARD_HEIGHT; i++ )
6214             initialPosition[i][j] = s;
6215
6216         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6217         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6218         initialPosition[pawnRow][j] = WhitePawn;
6219         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6220         if(gameInfo.variant == VariantXiangqi) {
6221             if(j&1) {
6222                 initialPosition[pawnRow][j] =
6223                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6224                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6225                    initialPosition[2][j] = WhiteCannon;
6226                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6227                 }
6228             }
6229         }
6230         if(gameInfo.variant == VariantChu) {
6231              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6232                initialPosition[pawnRow+1][j] = WhiteCobra,
6233                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6234              for(i=1; i<pieceRows; i++) {
6235                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6236                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6237              }
6238         }
6239         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6240             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6241                initialPosition[0][j] = WhiteRook;
6242                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6243             }
6244         }
6245         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6246     }
6247     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6248     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6249
6250             j=BOARD_LEFT+1;
6251             initialPosition[1][j] = WhiteBishop;
6252             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6253             j=BOARD_RGHT-2;
6254             initialPosition[1][j] = WhiteRook;
6255             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6256     }
6257
6258     if( nrCastlingRights == -1) {
6259         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6260         /*       This sets default castling rights from none to normal corners   */
6261         /* Variants with other castling rights must set them themselves above    */
6262         nrCastlingRights = 6;
6263
6264         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6265         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6266         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6267         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6268         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6269         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6270      }
6271
6272      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6273      if(gameInfo.variant == VariantGreat) { // promotion commoners
6274         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6275         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6276         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6277         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6278      }
6279      if( gameInfo.variant == VariantSChess ) {
6280       initialPosition[1][0] = BlackMarshall;
6281       initialPosition[2][0] = BlackAngel;
6282       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6283       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6284       initialPosition[1][1] = initialPosition[2][1] =
6285       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6286      }
6287   if (appData.debugMode) {
6288     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6289   }
6290     if(shuffleOpenings) {
6291         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6292         startedFromSetupPosition = TRUE;
6293     }
6294     if(startedFromPositionFile) {
6295       /* [HGM] loadPos: use PositionFile for every new game */
6296       CopyBoard(initialPosition, filePosition);
6297       for(i=0; i<nrCastlingRights; i++)
6298           initialRights[i] = filePosition[CASTLING][i];
6299       startedFromSetupPosition = TRUE;
6300     }
6301
6302     CopyBoard(boards[0], initialPosition);
6303
6304     if(oldx != gameInfo.boardWidth ||
6305        oldy != gameInfo.boardHeight ||
6306        oldv != gameInfo.variant ||
6307        oldh != gameInfo.holdingsWidth
6308                                          )
6309             InitDrawingSizes(-2 ,0);
6310
6311     oldv = gameInfo.variant;
6312     if (redraw)
6313       DrawPosition(TRUE, boards[currentMove]);
6314 }
6315
6316 void
6317 SendBoard (ChessProgramState *cps, int moveNum)
6318 {
6319     char message[MSG_SIZ];
6320
6321     if (cps->useSetboard) {
6322       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6323       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6324       SendToProgram(message, cps);
6325       free(fen);
6326
6327     } else {
6328       ChessSquare *bp;
6329       int i, j, left=0, right=BOARD_WIDTH;
6330       /* Kludge to set black to move, avoiding the troublesome and now
6331        * deprecated "black" command.
6332        */
6333       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6334         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6335
6336       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6337
6338       SendToProgram("edit\n", cps);
6339       SendToProgram("#\n", cps);
6340       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6341         bp = &boards[moveNum][i][left];
6342         for (j = left; j < right; j++, bp++) {
6343           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6344           if ((int) *bp < (int) BlackPawn) {
6345             if(j == BOARD_RGHT+1)
6346                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6347             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6348             if(message[0] == '+' || message[0] == '~') {
6349               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6350                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6351                         AAA + j, ONE + i);
6352             }
6353             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6354                 message[1] = BOARD_RGHT   - 1 - j + '1';
6355                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6356             }
6357             SendToProgram(message, cps);
6358           }
6359         }
6360       }
6361
6362       SendToProgram("c\n", cps);
6363       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6364         bp = &boards[moveNum][i][left];
6365         for (j = left; j < right; j++, bp++) {
6366           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6367           if (((int) *bp != (int) EmptySquare)
6368               && ((int) *bp >= (int) BlackPawn)) {
6369             if(j == BOARD_LEFT-2)
6370                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6371             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6372                     AAA + j, ONE + i);
6373             if(message[0] == '+' || message[0] == '~') {
6374               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6375                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6376                         AAA + j, ONE + i);
6377             }
6378             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6379                 message[1] = BOARD_RGHT   - 1 - j + '1';
6380                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6381             }
6382             SendToProgram(message, cps);
6383           }
6384         }
6385       }
6386
6387       SendToProgram(".\n", cps);
6388     }
6389     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6390 }
6391
6392 char exclusionHeader[MSG_SIZ];
6393 int exCnt, excludePtr;
6394 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6395 static Exclusion excluTab[200];
6396 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6397
6398 static void
6399 WriteMap (int s)
6400 {
6401     int j;
6402     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6403     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6404 }
6405
6406 static void
6407 ClearMap ()
6408 {
6409     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6410     excludePtr = 24; exCnt = 0;
6411     WriteMap(0);
6412 }
6413
6414 static void
6415 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6416 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6417     char buf[2*MOVE_LEN], *p;
6418     Exclusion *e = excluTab;
6419     int i;
6420     for(i=0; i<exCnt; i++)
6421         if(e[i].ff == fromX && e[i].fr == fromY &&
6422            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6423     if(i == exCnt) { // was not in exclude list; add it
6424         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6425         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6426             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6427             return; // abort
6428         }
6429         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6430         excludePtr++; e[i].mark = excludePtr++;
6431         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6432         exCnt++;
6433     }
6434     exclusionHeader[e[i].mark] = state;
6435 }
6436
6437 static int
6438 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6439 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6440     char buf[MSG_SIZ];
6441     int j, k;
6442     ChessMove moveType;
6443     if((signed char)promoChar == -1) { // kludge to indicate best move
6444         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6445             return 1; // if unparsable, abort
6446     }
6447     // update exclusion map (resolving toggle by consulting existing state)
6448     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6449     j = k%8; k >>= 3;
6450     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6451     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6452          excludeMap[k] |=   1<<j;
6453     else excludeMap[k] &= ~(1<<j);
6454     // update header
6455     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6456     // inform engine
6457     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6458     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6459     SendToBoth(buf);
6460     return (state == '+');
6461 }
6462
6463 static void
6464 ExcludeClick (int index)
6465 {
6466     int i, j;
6467     Exclusion *e = excluTab;
6468     if(index < 25) { // none, best or tail clicked
6469         if(index < 13) { // none: include all
6470             WriteMap(0); // clear map
6471             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6472             SendToBoth("include all\n"); // and inform engine
6473         } else if(index > 18) { // tail
6474             if(exclusionHeader[19] == '-') { // tail was excluded
6475                 SendToBoth("include all\n");
6476                 WriteMap(0); // clear map completely
6477                 // now re-exclude selected moves
6478                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6479                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6480             } else { // tail was included or in mixed state
6481                 SendToBoth("exclude all\n");
6482                 WriteMap(0xFF); // fill map completely
6483                 // now re-include selected moves
6484                 j = 0; // count them
6485                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6486                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6487                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6488             }
6489         } else { // best
6490             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6491         }
6492     } else {
6493         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6494             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6495             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6496             break;
6497         }
6498     }
6499 }
6500
6501 ChessSquare
6502 DefaultPromoChoice (int white)
6503 {
6504     ChessSquare result;
6505     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6506        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6507         result = WhiteFerz; // no choice
6508     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6509         result= WhiteKing; // in Suicide Q is the last thing we want
6510     else if(gameInfo.variant == VariantSpartan)
6511         result = white ? WhiteQueen : WhiteAngel;
6512     else result = WhiteQueen;
6513     if(!white) result = WHITE_TO_BLACK result;
6514     return result;
6515 }
6516
6517 static int autoQueen; // [HGM] oneclick
6518
6519 int
6520 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6521 {
6522     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6523     /* [HGM] add Shogi promotions */
6524     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6525     ChessSquare piece, partner;
6526     ChessMove moveType;
6527     Boolean premove;
6528
6529     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6530     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6531
6532     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6533       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6534         return FALSE;
6535
6536     piece = boards[currentMove][fromY][fromX];
6537     if(gameInfo.variant == VariantChu) {
6538         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6539         promotionZoneSize = BOARD_HEIGHT/3;
6540         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6541     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6542         promotionZoneSize = BOARD_HEIGHT/3;
6543         highestPromotingPiece = (int)WhiteAlfil;
6544     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6545         promotionZoneSize = 3;
6546     }
6547
6548     // Treat Lance as Pawn when it is not representing Amazon or Lance
6549     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6550         if(piece == WhiteLance) piece = WhitePawn; else
6551         if(piece == BlackLance) piece = BlackPawn;
6552     }
6553
6554     // next weed out all moves that do not touch the promotion zone at all
6555     if((int)piece >= BlackPawn) {
6556         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6557              return FALSE;
6558         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6559         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6560     } else {
6561         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6562            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6563         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6564              return FALSE;
6565     }
6566
6567     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6568
6569     // weed out mandatory Shogi promotions
6570     if(gameInfo.variant == VariantShogi) {
6571         if(piece >= BlackPawn) {
6572             if(toY == 0 && piece == BlackPawn ||
6573                toY == 0 && piece == BlackQueen ||
6574                toY <= 1 && piece == BlackKnight) {
6575                 *promoChoice = '+';
6576                 return FALSE;
6577             }
6578         } else {
6579             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6580                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6581                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6582                 *promoChoice = '+';
6583                 return FALSE;
6584             }
6585         }
6586     }
6587
6588     // weed out obviously illegal Pawn moves
6589     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6590         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6591         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6592         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6593         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6594         // note we are not allowed to test for valid (non-)capture, due to premove
6595     }
6596
6597     // we either have a choice what to promote to, or (in Shogi) whether to promote
6598     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6599        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6600         ChessSquare p=BlackFerz;  // no choice
6601         while(p < EmptySquare) {  //but make sure we use piece that exists
6602             *promoChoice = PieceToChar(p++);
6603             if(*promoChoice != '.') break;
6604         }
6605         return FALSE;
6606     }
6607     // no sense asking what we must promote to if it is going to explode...
6608     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6609         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6610         return FALSE;
6611     }
6612     // give caller the default choice even if we will not make it
6613     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6614     partner = piece; // pieces can promote if the pieceToCharTable says so
6615     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6616     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6617     if(        sweepSelect && gameInfo.variant != VariantGreat
6618                            && gameInfo.variant != VariantGrand
6619                            && gameInfo.variant != VariantSuper) return FALSE;
6620     if(autoQueen) return FALSE; // predetermined
6621
6622     // suppress promotion popup on illegal moves that are not premoves
6623     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6624               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6625     if(appData.testLegality && !premove) {
6626         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6627                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6628         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6629         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6630             return FALSE;
6631     }
6632
6633     return TRUE;
6634 }
6635
6636 int
6637 InPalace (int row, int column)
6638 {   /* [HGM] for Xiangqi */
6639     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6640          column < (BOARD_WIDTH + 4)/2 &&
6641          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6642     return FALSE;
6643 }
6644
6645 int
6646 PieceForSquare (int x, int y)
6647 {
6648   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6649      return -1;
6650   else
6651      return boards[currentMove][y][x];
6652 }
6653
6654 int
6655 OKToStartUserMove (int x, int y)
6656 {
6657     ChessSquare from_piece;
6658     int white_piece;
6659
6660     if (matchMode) return FALSE;
6661     if (gameMode == EditPosition) return TRUE;
6662
6663     if (x >= 0 && y >= 0)
6664       from_piece = boards[currentMove][y][x];
6665     else
6666       from_piece = EmptySquare;
6667
6668     if (from_piece == EmptySquare) return FALSE;
6669
6670     white_piece = (int)from_piece >= (int)WhitePawn &&
6671       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6672
6673     switch (gameMode) {
6674       case AnalyzeFile:
6675       case TwoMachinesPlay:
6676       case EndOfGame:
6677         return FALSE;
6678
6679       case IcsObserving:
6680       case IcsIdle:
6681         return FALSE;
6682
6683       case MachinePlaysWhite:
6684       case IcsPlayingBlack:
6685         if (appData.zippyPlay) return FALSE;
6686         if (white_piece) {
6687             DisplayMoveError(_("You are playing Black"));
6688             return FALSE;
6689         }
6690         break;
6691
6692       case MachinePlaysBlack:
6693       case IcsPlayingWhite:
6694         if (appData.zippyPlay) return FALSE;
6695         if (!white_piece) {
6696             DisplayMoveError(_("You are playing White"));
6697             return FALSE;
6698         }
6699         break;
6700
6701       case PlayFromGameFile:
6702             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6703       case EditGame:
6704         if (!white_piece && WhiteOnMove(currentMove)) {
6705             DisplayMoveError(_("It is White's turn"));
6706             return FALSE;
6707         }
6708         if (white_piece && !WhiteOnMove(currentMove)) {
6709             DisplayMoveError(_("It is Black's turn"));
6710             return FALSE;
6711         }
6712         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6713             /* Editing correspondence game history */
6714             /* Could disallow this or prompt for confirmation */
6715             cmailOldMove = -1;
6716         }
6717         break;
6718
6719       case BeginningOfGame:
6720         if (appData.icsActive) return FALSE;
6721         if (!appData.noChessProgram) {
6722             if (!white_piece) {
6723                 DisplayMoveError(_("You are playing White"));
6724                 return FALSE;
6725             }
6726         }
6727         break;
6728
6729       case Training:
6730         if (!white_piece && WhiteOnMove(currentMove)) {
6731             DisplayMoveError(_("It is White's turn"));
6732             return FALSE;
6733         }
6734         if (white_piece && !WhiteOnMove(currentMove)) {
6735             DisplayMoveError(_("It is Black's turn"));
6736             return FALSE;
6737         }
6738         break;
6739
6740       default:
6741       case IcsExamining:
6742         break;
6743     }
6744     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6745         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6746         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6747         && gameMode != AnalyzeFile && gameMode != Training) {
6748         DisplayMoveError(_("Displayed position is not current"));
6749         return FALSE;
6750     }
6751     return TRUE;
6752 }
6753
6754 Boolean
6755 OnlyMove (int *x, int *y, Boolean captures)
6756 {
6757     DisambiguateClosure cl;
6758     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6759     switch(gameMode) {
6760       case MachinePlaysBlack:
6761       case IcsPlayingWhite:
6762       case BeginningOfGame:
6763         if(!WhiteOnMove(currentMove)) return FALSE;
6764         break;
6765       case MachinePlaysWhite:
6766       case IcsPlayingBlack:
6767         if(WhiteOnMove(currentMove)) return FALSE;
6768         break;
6769       case EditGame:
6770         break;
6771       default:
6772         return FALSE;
6773     }
6774     cl.pieceIn = EmptySquare;
6775     cl.rfIn = *y;
6776     cl.ffIn = *x;
6777     cl.rtIn = -1;
6778     cl.ftIn = -1;
6779     cl.promoCharIn = NULLCHAR;
6780     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6781     if( cl.kind == NormalMove ||
6782         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6783         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6784         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6785       fromX = cl.ff;
6786       fromY = cl.rf;
6787       *x = cl.ft;
6788       *y = cl.rt;
6789       return TRUE;
6790     }
6791     if(cl.kind != ImpossibleMove) return FALSE;
6792     cl.pieceIn = EmptySquare;
6793     cl.rfIn = -1;
6794     cl.ffIn = -1;
6795     cl.rtIn = *y;
6796     cl.ftIn = *x;
6797     cl.promoCharIn = NULLCHAR;
6798     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6799     if( cl.kind == NormalMove ||
6800         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6801         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6802         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6803       fromX = cl.ff;
6804       fromY = cl.rf;
6805       *x = cl.ft;
6806       *y = cl.rt;
6807       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6808       return TRUE;
6809     }
6810     return FALSE;
6811 }
6812
6813 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6814 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6815 int lastLoadGameUseList = FALSE;
6816 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6817 ChessMove lastLoadGameStart = EndOfFile;
6818 int doubleClick;
6819 Boolean addToBookFlag;
6820
6821 void
6822 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6823 {
6824     ChessMove moveType;
6825     ChessSquare pup;
6826     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6827
6828     /* Check if the user is playing in turn.  This is complicated because we
6829        let the user "pick up" a piece before it is his turn.  So the piece he
6830        tried to pick up may have been captured by the time he puts it down!
6831        Therefore we use the color the user is supposed to be playing in this
6832        test, not the color of the piece that is currently on the starting
6833        square---except in EditGame mode, where the user is playing both
6834        sides; fortunately there the capture race can't happen.  (It can
6835        now happen in IcsExamining mode, but that's just too bad.  The user
6836        will get a somewhat confusing message in that case.)
6837        */
6838
6839     switch (gameMode) {
6840       case AnalyzeFile:
6841       case TwoMachinesPlay:
6842       case EndOfGame:
6843       case IcsObserving:
6844       case IcsIdle:
6845         /* We switched into a game mode where moves are not accepted,
6846            perhaps while the mouse button was down. */
6847         return;
6848
6849       case MachinePlaysWhite:
6850         /* User is moving for Black */
6851         if (WhiteOnMove(currentMove)) {
6852             DisplayMoveError(_("It is White's turn"));
6853             return;
6854         }
6855         break;
6856
6857       case MachinePlaysBlack:
6858         /* User is moving for White */
6859         if (!WhiteOnMove(currentMove)) {
6860             DisplayMoveError(_("It is Black's turn"));
6861             return;
6862         }
6863         break;
6864
6865       case PlayFromGameFile:
6866             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6867       case EditGame:
6868       case IcsExamining:
6869       case BeginningOfGame:
6870       case AnalyzeMode:
6871       case Training:
6872         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6873         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6874             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6875             /* User is moving for Black */
6876             if (WhiteOnMove(currentMove)) {
6877                 DisplayMoveError(_("It is White's turn"));
6878                 return;
6879             }
6880         } else {
6881             /* User is moving for White */
6882             if (!WhiteOnMove(currentMove)) {
6883                 DisplayMoveError(_("It is Black's turn"));
6884                 return;
6885             }
6886         }
6887         break;
6888
6889       case IcsPlayingBlack:
6890         /* User is moving for Black */
6891         if (WhiteOnMove(currentMove)) {
6892             if (!appData.premove) {
6893                 DisplayMoveError(_("It is White's turn"));
6894             } else if (toX >= 0 && toY >= 0) {
6895                 premoveToX = toX;
6896                 premoveToY = toY;
6897                 premoveFromX = fromX;
6898                 premoveFromY = fromY;
6899                 premovePromoChar = promoChar;
6900                 gotPremove = 1;
6901                 if (appData.debugMode)
6902                     fprintf(debugFP, "Got premove: fromX %d,"
6903                             "fromY %d, toX %d, toY %d\n",
6904                             fromX, fromY, toX, toY);
6905             }
6906             return;
6907         }
6908         break;
6909
6910       case IcsPlayingWhite:
6911         /* User is moving for White */
6912         if (!WhiteOnMove(currentMove)) {
6913             if (!appData.premove) {
6914                 DisplayMoveError(_("It is Black's turn"));
6915             } else if (toX >= 0 && toY >= 0) {
6916                 premoveToX = toX;
6917                 premoveToY = toY;
6918                 premoveFromX = fromX;
6919                 premoveFromY = fromY;
6920                 premovePromoChar = promoChar;
6921                 gotPremove = 1;
6922                 if (appData.debugMode)
6923                     fprintf(debugFP, "Got premove: fromX %d,"
6924                             "fromY %d, toX %d, toY %d\n",
6925                             fromX, fromY, toX, toY);
6926             }
6927             return;
6928         }
6929         break;
6930
6931       default:
6932         break;
6933
6934       case EditPosition:
6935         /* EditPosition, empty square, or different color piece;
6936            click-click move is possible */
6937         if (toX == -2 || toY == -2) {
6938             boards[0][fromY][fromX] = EmptySquare;
6939             DrawPosition(FALSE, boards[currentMove]);
6940             return;
6941         } else if (toX >= 0 && toY >= 0) {
6942             boards[0][toY][toX] = boards[0][fromY][fromX];
6943             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6944                 if(boards[0][fromY][0] != EmptySquare) {
6945                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6946                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6947                 }
6948             } else
6949             if(fromX == BOARD_RGHT+1) {
6950                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6951                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6952                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6953                 }
6954             } else
6955             boards[0][fromY][fromX] = gatingPiece;
6956             DrawPosition(FALSE, boards[currentMove]);
6957             return;
6958         }
6959         return;
6960     }
6961
6962     if(toX < 0 || toY < 0) return;
6963     pup = boards[currentMove][toY][toX];
6964
6965     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6966     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6967          if( pup != EmptySquare ) return;
6968          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6969            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6970                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6971            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6972            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6973            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6974            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6975          fromY = DROP_RANK;
6976     }
6977
6978     /* [HGM] always test for legality, to get promotion info */
6979     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6980                                          fromY, fromX, toY, toX, promoChar);
6981
6982     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6983
6984     /* [HGM] but possibly ignore an IllegalMove result */
6985     if (appData.testLegality) {
6986         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6987             DisplayMoveError(_("Illegal move"));
6988             return;
6989         }
6990     }
6991
6992     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6993         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6994              ClearPremoveHighlights(); // was included
6995         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6996         return;
6997     }
6998
6999     if(addToBookFlag) { // adding moves to book
7000         char buf[MSG_SIZ], move[MSG_SIZ];
7001         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7002         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7003         AddBookMove(buf);
7004         addToBookFlag = FALSE;
7005         ClearHighlights();
7006         return;
7007     }
7008
7009     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7010 }
7011
7012 /* Common tail of UserMoveEvent and DropMenuEvent */
7013 int
7014 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7015 {
7016     char *bookHit = 0;
7017
7018     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7019         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7020         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7021         if(WhiteOnMove(currentMove)) {
7022             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7023         } else {
7024             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7025         }
7026     }
7027
7028     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7029        move type in caller when we know the move is a legal promotion */
7030     if(moveType == NormalMove && promoChar)
7031         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7032
7033     /* [HGM] <popupFix> The following if has been moved here from
7034        UserMoveEvent(). Because it seemed to belong here (why not allow
7035        piece drops in training games?), and because it can only be
7036        performed after it is known to what we promote. */
7037     if (gameMode == Training) {
7038       /* compare the move played on the board to the next move in the
7039        * game. If they match, display the move and the opponent's response.
7040        * If they don't match, display an error message.
7041        */
7042       int saveAnimate;
7043       Board testBoard;
7044       CopyBoard(testBoard, boards[currentMove]);
7045       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7046
7047       if (CompareBoards(testBoard, boards[currentMove+1])) {
7048         ForwardInner(currentMove+1);
7049
7050         /* Autoplay the opponent's response.
7051          * if appData.animate was TRUE when Training mode was entered,
7052          * the response will be animated.
7053          */
7054         saveAnimate = appData.animate;
7055         appData.animate = animateTraining;
7056         ForwardInner(currentMove+1);
7057         appData.animate = saveAnimate;
7058
7059         /* check for the end of the game */
7060         if (currentMove >= forwardMostMove) {
7061           gameMode = PlayFromGameFile;
7062           ModeHighlight();
7063           SetTrainingModeOff();
7064           DisplayInformation(_("End of game"));
7065         }
7066       } else {
7067         DisplayError(_("Incorrect move"), 0);
7068       }
7069       return 1;
7070     }
7071
7072   /* Ok, now we know that the move is good, so we can kill
7073      the previous line in Analysis Mode */
7074   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7075                                 && currentMove < forwardMostMove) {
7076     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7077     else forwardMostMove = currentMove;
7078   }
7079
7080   ClearMap();
7081
7082   /* If we need the chess program but it's dead, restart it */
7083   ResurrectChessProgram();
7084
7085   /* A user move restarts a paused game*/
7086   if (pausing)
7087     PauseEvent();
7088
7089   thinkOutput[0] = NULLCHAR;
7090
7091   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7092
7093   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7094     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7095     return 1;
7096   }
7097
7098   if (gameMode == BeginningOfGame) {
7099     if (appData.noChessProgram) {
7100       gameMode = EditGame;
7101       SetGameInfo();
7102     } else {
7103       char buf[MSG_SIZ];
7104       gameMode = MachinePlaysBlack;
7105       StartClocks();
7106       SetGameInfo();
7107       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7108       DisplayTitle(buf);
7109       if (first.sendName) {
7110         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7111         SendToProgram(buf, &first);
7112       }
7113       StartClocks();
7114     }
7115     ModeHighlight();
7116   }
7117
7118   /* Relay move to ICS or chess engine */
7119   if (appData.icsActive) {
7120     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7121         gameMode == IcsExamining) {
7122       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7123         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7124         SendToICS("draw ");
7125         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7126       }
7127       // also send plain move, in case ICS does not understand atomic claims
7128       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7129       ics_user_moved = 1;
7130     }
7131   } else {
7132     if (first.sendTime && (gameMode == BeginningOfGame ||
7133                            gameMode == MachinePlaysWhite ||
7134                            gameMode == MachinePlaysBlack)) {
7135       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7136     }
7137     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7138          // [HGM] book: if program might be playing, let it use book
7139         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7140         first.maybeThinking = TRUE;
7141     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7142         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7143         SendBoard(&first, currentMove+1);
7144         if(second.analyzing) {
7145             if(!second.useSetboard) SendToProgram("undo\n", &second);
7146             SendBoard(&second, currentMove+1);
7147         }
7148     } else {
7149         SendMoveToProgram(forwardMostMove-1, &first);
7150         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7151     }
7152     if (currentMove == cmailOldMove + 1) {
7153       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7154     }
7155   }
7156
7157   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7158
7159   switch (gameMode) {
7160   case EditGame:
7161     if(appData.testLegality)
7162     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7163     case MT_NONE:
7164     case MT_CHECK:
7165       break;
7166     case MT_CHECKMATE:
7167     case MT_STAINMATE:
7168       if (WhiteOnMove(currentMove)) {
7169         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7170       } else {
7171         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7172       }
7173       break;
7174     case MT_STALEMATE:
7175       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7176       break;
7177     }
7178     break;
7179
7180   case MachinePlaysBlack:
7181   case MachinePlaysWhite:
7182     /* disable certain menu options while machine is thinking */
7183     SetMachineThinkingEnables();
7184     break;
7185
7186   default:
7187     break;
7188   }
7189
7190   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7191   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7192
7193   if(bookHit) { // [HGM] book: simulate book reply
7194         static char bookMove[MSG_SIZ]; // a bit generous?
7195
7196         programStats.nodes = programStats.depth = programStats.time =
7197         programStats.score = programStats.got_only_move = 0;
7198         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7199
7200         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7201         strcat(bookMove, bookHit);
7202         HandleMachineMove(bookMove, &first);
7203   }
7204   return 1;
7205 }
7206
7207 void
7208 MarkByFEN(char *fen)
7209 {
7210         int r, f;
7211         if(!appData.markers || !appData.highlightDragging) return;
7212         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7213         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7214         while(*fen) {
7215             int s = 0;
7216             marker[r][f] = 0;
7217             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7218             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7219             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7220             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7221             if(*fen == 'T') marker[r][f++] = 0; else
7222             if(*fen == 'Y') marker[r][f++] = 1; else
7223             if(*fen == 'G') marker[r][f++] = 3; else
7224             if(*fen == 'B') marker[r][f++] = 4; else
7225             if(*fen == 'C') marker[r][f++] = 5; else
7226             if(*fen == 'M') marker[r][f++] = 6; else
7227             if(*fen == 'W') marker[r][f++] = 7; else
7228             if(*fen == 'D') marker[r][f++] = 8; else
7229             if(*fen == 'R') marker[r][f++] = 2; else {
7230                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7231               f += s; fen -= s>0;
7232             }
7233             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7234             if(r < 0) break;
7235             fen++;
7236         }
7237         DrawPosition(TRUE, NULL);
7238 }
7239
7240 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7241
7242 void
7243 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7244 {
7245     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7246     Markers *m = (Markers *) closure;
7247     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7248         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7249                          || kind == WhiteCapturesEnPassant
7250                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7251     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7252 }
7253
7254 static int hoverSavedValid;
7255
7256 void
7257 MarkTargetSquares (int clear)
7258 {
7259   int x, y, sum=0;
7260   if(clear) { // no reason to ever suppress clearing
7261     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7262     hoverSavedValid = 0;
7263     if(!sum) return; // nothing was cleared,no redraw needed
7264   } else {
7265     int capt = 0;
7266     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7267        !appData.testLegality || gameMode == EditPosition) return;
7268     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7269     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7270       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7271       if(capt)
7272       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7273     }
7274   }
7275   DrawPosition(FALSE, NULL);
7276 }
7277
7278 int
7279 Explode (Board board, int fromX, int fromY, int toX, int toY)
7280 {
7281     if(gameInfo.variant == VariantAtomic &&
7282        (board[toY][toX] != EmptySquare ||                     // capture?
7283         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7284                          board[fromY][fromX] == BlackPawn   )
7285       )) {
7286         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7287         return TRUE;
7288     }
7289     return FALSE;
7290 }
7291
7292 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7293
7294 int
7295 CanPromote (ChessSquare piece, int y)
7296 {
7297         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7298         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7299         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7300         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7301            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7302            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7303          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7304         return (piece == BlackPawn && y <= zone ||
7305                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7306                 piece == BlackLance && y == 1 ||
7307                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7308 }
7309
7310 void
7311 HoverEvent (int xPix, int yPix, int x, int y)
7312 {
7313         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7314         int r, f;
7315         if(!first.highlight) return;
7316         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7317         if(x == oldX && y == oldY) return; // only do something if we enter new square
7318         oldFromX = fromX; oldFromY = fromY;
7319         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7320           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7321             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7322           hoverSavedValid = 1;
7323         } else if(oldX != x || oldY != y) {
7324           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7325           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7326           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7327             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7328           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7329             char buf[MSG_SIZ];
7330             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7331             SendToProgram(buf, &first);
7332           }
7333           oldX = x; oldY = y;
7334 //        SetHighlights(fromX, fromY, x, y);
7335         }
7336 }
7337
7338 void ReportClick(char *action, int x, int y)
7339 {
7340         char buf[MSG_SIZ]; // Inform engine of what user does
7341         int r, f;
7342         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7343           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7344         if(!first.highlight || gameMode == EditPosition) return;
7345         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7346         SendToProgram(buf, &first);
7347 }
7348
7349 void
7350 LeftClick (ClickType clickType, int xPix, int yPix)
7351 {
7352     int x, y;
7353     Boolean saveAnimate;
7354     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7355     char promoChoice = NULLCHAR;
7356     ChessSquare piece;
7357     static TimeMark lastClickTime, prevClickTime;
7358
7359     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7360
7361     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7362
7363     if (clickType == Press) ErrorPopDown();
7364     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7365
7366     x = EventToSquare(xPix, BOARD_WIDTH);
7367     y = EventToSquare(yPix, BOARD_HEIGHT);
7368     if (!flipView && y >= 0) {
7369         y = BOARD_HEIGHT - 1 - y;
7370     }
7371     if (flipView && x >= 0) {
7372         x = BOARD_WIDTH - 1 - x;
7373     }
7374
7375     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7376         defaultPromoChoice = promoSweep;
7377         promoSweep = EmptySquare;   // terminate sweep
7378         promoDefaultAltered = TRUE;
7379         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7380     }
7381
7382     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7383         if(clickType == Release) return; // ignore upclick of click-click destination
7384         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7385         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7386         if(gameInfo.holdingsWidth &&
7387                 (WhiteOnMove(currentMove)
7388                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7389                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7390             // click in right holdings, for determining promotion piece
7391             ChessSquare p = boards[currentMove][y][x];
7392             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7393             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7394             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7395                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7396                 fromX = fromY = -1;
7397                 return;
7398             }
7399         }
7400         DrawPosition(FALSE, boards[currentMove]);
7401         return;
7402     }
7403
7404     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7405     if(clickType == Press
7406             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7407               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7408               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7409         return;
7410
7411     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7412         // could be static click on premove from-square: abort premove
7413         gotPremove = 0;
7414         ClearPremoveHighlights();
7415     }
7416
7417     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7418         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7419
7420     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7421         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7422                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7423         defaultPromoChoice = DefaultPromoChoice(side);
7424     }
7425
7426     autoQueen = appData.alwaysPromoteToQueen;
7427
7428     if (fromX == -1) {
7429       int originalY = y;
7430       gatingPiece = EmptySquare;
7431       if (clickType != Press) {
7432         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7433             DragPieceEnd(xPix, yPix); dragging = 0;
7434             DrawPosition(FALSE, NULL);
7435         }
7436         return;
7437       }
7438       doubleClick = FALSE;
7439       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7440         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7441       }
7442       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7443       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7444          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7445          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7446             /* First square */
7447             if (OKToStartUserMove(fromX, fromY)) {
7448                 second = 0;
7449                 ReportClick("lift", x, y);
7450                 MarkTargetSquares(0);
7451                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7452                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7453                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7454                     promoSweep = defaultPromoChoice;
7455                     selectFlag = 0; lastX = xPix; lastY = yPix;
7456                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7457                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7458                 }
7459                 if (appData.highlightDragging) {
7460                     SetHighlights(fromX, fromY, -1, -1);
7461                 } else {
7462                     ClearHighlights();
7463                 }
7464             } else fromX = fromY = -1;
7465             return;
7466         }
7467     }
7468
7469     /* fromX != -1 */
7470     if (clickType == Press && gameMode != EditPosition) {
7471         ChessSquare fromP;
7472         ChessSquare toP;
7473         int frc;
7474
7475         // ignore off-board to clicks
7476         if(y < 0 || x < 0) return;
7477
7478         /* Check if clicking again on the same color piece */
7479         fromP = boards[currentMove][fromY][fromX];
7480         toP = boards[currentMove][y][x];
7481         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7482         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7483            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7484              WhitePawn <= toP && toP <= WhiteKing &&
7485              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7486              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7487             (BlackPawn <= fromP && fromP <= BlackKing &&
7488              BlackPawn <= toP && toP <= BlackKing &&
7489              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7490              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7491             /* Clicked again on same color piece -- changed his mind */
7492             second = (x == fromX && y == fromY);
7493             killX = killY = -1;
7494             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7495                 second = FALSE; // first double-click rather than scond click
7496                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7497             }
7498             promoDefaultAltered = FALSE;
7499             MarkTargetSquares(1);
7500            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7501             if (appData.highlightDragging) {
7502                 SetHighlights(x, y, -1, -1);
7503             } else {
7504                 ClearHighlights();
7505             }
7506             if (OKToStartUserMove(x, y)) {
7507                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7508                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7509                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7510                  gatingPiece = boards[currentMove][fromY][fromX];
7511                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7512                 fromX = x;
7513                 fromY = y; dragging = 1;
7514                 ReportClick("lift", x, y);
7515                 MarkTargetSquares(0);
7516                 DragPieceBegin(xPix, yPix, FALSE);
7517                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7518                     promoSweep = defaultPromoChoice;
7519                     selectFlag = 0; lastX = xPix; lastY = yPix;
7520                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7521                 }
7522             }
7523            }
7524            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7525            second = FALSE;
7526         }
7527         // ignore clicks on holdings
7528         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7529     }
7530
7531     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7532         DragPieceEnd(xPix, yPix); dragging = 0;
7533         if(clearFlag) {
7534             // a deferred attempt to click-click move an empty square on top of a piece
7535             boards[currentMove][y][x] = EmptySquare;
7536             ClearHighlights();
7537             DrawPosition(FALSE, boards[currentMove]);
7538             fromX = fromY = -1; clearFlag = 0;
7539             return;
7540         }
7541         if (appData.animateDragging) {
7542             /* Undo animation damage if any */
7543             DrawPosition(FALSE, NULL);
7544         }
7545         if (second || sweepSelecting) {
7546             /* Second up/down in same square; just abort move */
7547             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7548             second = sweepSelecting = 0;
7549             fromX = fromY = -1;
7550             gatingPiece = EmptySquare;
7551             MarkTargetSquares(1);
7552             ClearHighlights();
7553             gotPremove = 0;
7554             ClearPremoveHighlights();
7555         } else {
7556             /* First upclick in same square; start click-click mode */
7557             SetHighlights(x, y, -1, -1);
7558         }
7559         return;
7560     }
7561
7562     clearFlag = 0;
7563
7564     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7565         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7566         DisplayMessage(_("only marked squares are legal"),"");
7567         DrawPosition(TRUE, NULL);
7568         return; // ignore to-click
7569     }
7570
7571     /* we now have a different from- and (possibly off-board) to-square */
7572     /* Completed move */
7573     if(!sweepSelecting) {
7574         toX = x;
7575         toY = y;
7576     }
7577
7578     piece = boards[currentMove][fromY][fromX];
7579
7580     saveAnimate = appData.animate;
7581     if (clickType == Press) {
7582         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7583         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7584             // must be Edit Position mode with empty-square selected
7585             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7586             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7587             return;
7588         }
7589         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7590             return;
7591         }
7592         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7593             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7594         } else
7595         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7596         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7597           if(appData.sweepSelect) {
7598             promoSweep = defaultPromoChoice;
7599             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7600             selectFlag = 0; lastX = xPix; lastY = yPix;
7601             Sweep(0); // Pawn that is going to promote: preview promotion piece
7602             sweepSelecting = 1;
7603             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7604             MarkTargetSquares(1);
7605           }
7606           return; // promo popup appears on up-click
7607         }
7608         /* Finish clickclick move */
7609         if (appData.animate || appData.highlightLastMove) {
7610             SetHighlights(fromX, fromY, toX, toY);
7611         } else {
7612             ClearHighlights();
7613         }
7614     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7615         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7616         if (appData.animate || appData.highlightLastMove) {
7617             SetHighlights(fromX, fromY, toX, toY);
7618         } else {
7619             ClearHighlights();
7620         }
7621     } else {
7622 #if 0
7623 // [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
7624         /* Finish drag move */
7625         if (appData.highlightLastMove) {
7626             SetHighlights(fromX, fromY, toX, toY);
7627         } else {
7628             ClearHighlights();
7629         }
7630 #endif
7631         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7632         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7633           dragging *= 2;            // flag button-less dragging if we are dragging
7634           MarkTargetSquares(1);
7635           if(x == killX && y == killY) killX = killY = -1; else {
7636             killX = x; killY = y;     //remeber this square as intermediate
7637             ReportClick("put", x, y); // and inform engine
7638             ReportClick("lift", x, y);
7639             MarkTargetSquares(0);
7640             return;
7641           }
7642         }
7643         DragPieceEnd(xPix, yPix); dragging = 0;
7644         /* Don't animate move and drag both */
7645         appData.animate = FALSE;
7646     }
7647
7648     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7649     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7650         ChessSquare piece = boards[currentMove][fromY][fromX];
7651         if(gameMode == EditPosition && piece != EmptySquare &&
7652            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7653             int n;
7654
7655             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7656                 n = PieceToNumber(piece - (int)BlackPawn);
7657                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7658                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7659                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7660             } else
7661             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7662                 n = PieceToNumber(piece);
7663                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7664                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7665                 boards[currentMove][n][BOARD_WIDTH-2]++;
7666             }
7667             boards[currentMove][fromY][fromX] = EmptySquare;
7668         }
7669         ClearHighlights();
7670         fromX = fromY = -1;
7671         MarkTargetSquares(1);
7672         DrawPosition(TRUE, boards[currentMove]);
7673         return;
7674     }
7675
7676     // off-board moves should not be highlighted
7677     if(x < 0 || y < 0) ClearHighlights();
7678     else ReportClick("put", x, y);
7679
7680     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7681
7682     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7683         SetHighlights(fromX, fromY, toX, toY);
7684         MarkTargetSquares(1);
7685         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7686             // [HGM] super: promotion to captured piece selected from holdings
7687             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7688             promotionChoice = TRUE;
7689             // kludge follows to temporarily execute move on display, without promoting yet
7690             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7691             boards[currentMove][toY][toX] = p;
7692             DrawPosition(FALSE, boards[currentMove]);
7693             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7694             boards[currentMove][toY][toX] = q;
7695             DisplayMessage("Click in holdings to choose piece", "");
7696             return;
7697         }
7698         PromotionPopUp(promoChoice);
7699     } else {
7700         int oldMove = currentMove;
7701         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7702         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7703         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7704         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7705            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7706             DrawPosition(TRUE, boards[currentMove]);
7707         MarkTargetSquares(1);
7708         fromX = fromY = -1;
7709     }
7710     appData.animate = saveAnimate;
7711     if (appData.animate || appData.animateDragging) {
7712         /* Undo animation damage if needed */
7713         DrawPosition(FALSE, NULL);
7714     }
7715 }
7716
7717 int
7718 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7719 {   // front-end-free part taken out of PieceMenuPopup
7720     int whichMenu; int xSqr, ySqr;
7721
7722     if(seekGraphUp) { // [HGM] seekgraph
7723         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7724         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7725         return -2;
7726     }
7727
7728     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7729          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7730         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7731         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7732         if(action == Press)   {
7733             originalFlip = flipView;
7734             flipView = !flipView; // temporarily flip board to see game from partners perspective
7735             DrawPosition(TRUE, partnerBoard);
7736             DisplayMessage(partnerStatus, "");
7737             partnerUp = TRUE;
7738         } else if(action == Release) {
7739             flipView = originalFlip;
7740             DrawPosition(TRUE, boards[currentMove]);
7741             partnerUp = FALSE;
7742         }
7743         return -2;
7744     }
7745
7746     xSqr = EventToSquare(x, BOARD_WIDTH);
7747     ySqr = EventToSquare(y, BOARD_HEIGHT);
7748     if (action == Release) {
7749         if(pieceSweep != EmptySquare) {
7750             EditPositionMenuEvent(pieceSweep, toX, toY);
7751             pieceSweep = EmptySquare;
7752         } else UnLoadPV(); // [HGM] pv
7753     }
7754     if (action != Press) return -2; // return code to be ignored
7755     switch (gameMode) {
7756       case IcsExamining:
7757         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7758       case EditPosition:
7759         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7760         if (xSqr < 0 || ySqr < 0) return -1;
7761         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7762         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7763         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7764         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7765         NextPiece(0);
7766         return 2; // grab
7767       case IcsObserving:
7768         if(!appData.icsEngineAnalyze) return -1;
7769       case IcsPlayingWhite:
7770       case IcsPlayingBlack:
7771         if(!appData.zippyPlay) goto noZip;
7772       case AnalyzeMode:
7773       case AnalyzeFile:
7774       case MachinePlaysWhite:
7775       case MachinePlaysBlack:
7776       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7777         if (!appData.dropMenu) {
7778           LoadPV(x, y);
7779           return 2; // flag front-end to grab mouse events
7780         }
7781         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7782            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7783       case EditGame:
7784       noZip:
7785         if (xSqr < 0 || ySqr < 0) return -1;
7786         if (!appData.dropMenu || appData.testLegality &&
7787             gameInfo.variant != VariantBughouse &&
7788             gameInfo.variant != VariantCrazyhouse) return -1;
7789         whichMenu = 1; // drop menu
7790         break;
7791       default:
7792         return -1;
7793     }
7794
7795     if (((*fromX = xSqr) < 0) ||
7796         ((*fromY = ySqr) < 0)) {
7797         *fromX = *fromY = -1;
7798         return -1;
7799     }
7800     if (flipView)
7801       *fromX = BOARD_WIDTH - 1 - *fromX;
7802     else
7803       *fromY = BOARD_HEIGHT - 1 - *fromY;
7804
7805     return whichMenu;
7806 }
7807
7808 void
7809 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7810 {
7811 //    char * hint = lastHint;
7812     FrontEndProgramStats stats;
7813
7814     stats.which = cps == &first ? 0 : 1;
7815     stats.depth = cpstats->depth;
7816     stats.nodes = cpstats->nodes;
7817     stats.score = cpstats->score;
7818     stats.time = cpstats->time;
7819     stats.pv = cpstats->movelist;
7820     stats.hint = lastHint;
7821     stats.an_move_index = 0;
7822     stats.an_move_count = 0;
7823
7824     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7825         stats.hint = cpstats->move_name;
7826         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7827         stats.an_move_count = cpstats->nr_moves;
7828     }
7829
7830     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
7831
7832     SetProgramStats( &stats );
7833 }
7834
7835 void
7836 ClearEngineOutputPane (int which)
7837 {
7838     static FrontEndProgramStats dummyStats;
7839     dummyStats.which = which;
7840     dummyStats.pv = "#";
7841     SetProgramStats( &dummyStats );
7842 }
7843
7844 #define MAXPLAYERS 500
7845
7846 char *
7847 TourneyStandings (int display)
7848 {
7849     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7850     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7851     char result, *p, *names[MAXPLAYERS];
7852
7853     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7854         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7855     names[0] = p = strdup(appData.participants);
7856     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7857
7858     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7859
7860     while(result = appData.results[nr]) {
7861         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7862         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7863         wScore = bScore = 0;
7864         switch(result) {
7865           case '+': wScore = 2; break;
7866           case '-': bScore = 2; break;
7867           case '=': wScore = bScore = 1; break;
7868           case ' ':
7869           case '*': return strdup("busy"); // tourney not finished
7870         }
7871         score[w] += wScore;
7872         score[b] += bScore;
7873         games[w]++;
7874         games[b]++;
7875         nr++;
7876     }
7877     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7878     for(w=0; w<nPlayers; w++) {
7879         bScore = -1;
7880         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7881         ranking[w] = b; points[w] = bScore; score[b] = -2;
7882     }
7883     p = malloc(nPlayers*34+1);
7884     for(w=0; w<nPlayers && w<display; w++)
7885         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7886     free(names[0]);
7887     return p;
7888 }
7889
7890 void
7891 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7892 {       // count all piece types
7893         int p, f, r;
7894         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7895         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7896         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7897                 p = board[r][f];
7898                 pCnt[p]++;
7899                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7900                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7901                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7902                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7903                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7904                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7905         }
7906 }
7907
7908 int
7909 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7910 {
7911         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7912         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7913
7914         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7915         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7916         if(myPawns == 2 && nMine == 3) // KPP
7917             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7918         if(myPawns == 1 && nMine == 2) // KP
7919             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7920         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7921             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7922         if(myPawns) return FALSE;
7923         if(pCnt[WhiteRook+side])
7924             return pCnt[BlackRook-side] ||
7925                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7926                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7927                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7928         if(pCnt[WhiteCannon+side]) {
7929             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7930             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7931         }
7932         if(pCnt[WhiteKnight+side])
7933             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7934         return FALSE;
7935 }
7936
7937 int
7938 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7939 {
7940         VariantClass v = gameInfo.variant;
7941
7942         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7943         if(v == VariantShatranj) return TRUE; // always winnable through baring
7944         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7945         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7946
7947         if(v == VariantXiangqi) {
7948                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7949
7950                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7951                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7952                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7953                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7954                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7955                 if(stale) // we have at least one last-rank P plus perhaps C
7956                     return majors // KPKX
7957                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7958                 else // KCA*E*
7959                     return pCnt[WhiteFerz+side] // KCAK
7960                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7961                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7962                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7963
7964         } else if(v == VariantKnightmate) {
7965                 if(nMine == 1) return FALSE;
7966                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7967         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7968                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7969
7970                 if(nMine == 1) return FALSE; // bare King
7971                 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
7972                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7973                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7974                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7975                 if(pCnt[WhiteKnight+side])
7976                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7977                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7978                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7979                 if(nBishops)
7980                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7981                 if(pCnt[WhiteAlfil+side])
7982                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7983                 if(pCnt[WhiteWazir+side])
7984                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7985         }
7986
7987         return TRUE;
7988 }
7989
7990 int
7991 CompareWithRights (Board b1, Board b2)
7992 {
7993     int rights = 0;
7994     if(!CompareBoards(b1, b2)) return FALSE;
7995     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7996     /* compare castling rights */
7997     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7998            rights++; /* King lost rights, while rook still had them */
7999     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8000         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8001            rights++; /* but at least one rook lost them */
8002     }
8003     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8004            rights++;
8005     if( b1[CASTLING][5] != NoRights ) {
8006         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8007            rights++;
8008     }
8009     return rights == 0;
8010 }
8011
8012 int
8013 Adjudicate (ChessProgramState *cps)
8014 {       // [HGM] some adjudications useful with buggy engines
8015         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8016         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8017         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8018         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8019         int k, drop, count = 0; static int bare = 1;
8020         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8021         Boolean canAdjudicate = !appData.icsActive;
8022
8023         // most tests only when we understand the game, i.e. legality-checking on
8024             if( appData.testLegality )
8025             {   /* [HGM] Some more adjudications for obstinate engines */
8026                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8027                 static int moveCount = 6;
8028                 ChessMove result;
8029                 char *reason = NULL;
8030
8031                 /* Count what is on board. */
8032                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8033
8034                 /* Some material-based adjudications that have to be made before stalemate test */
8035                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8036                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8037                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8038                      if(canAdjudicate && appData.checkMates) {
8039                          if(engineOpponent)
8040                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8041                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8042                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8043                          return 1;
8044                      }
8045                 }
8046
8047                 /* Bare King in Shatranj (loses) or Losers (wins) */
8048                 if( nrW == 1 || nrB == 1) {
8049                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8050                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8051                      if(canAdjudicate && appData.checkMates) {
8052                          if(engineOpponent)
8053                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8054                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8055                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8056                          return 1;
8057                      }
8058                   } else
8059                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8060                   {    /* bare King */
8061                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8062                         if(canAdjudicate && appData.checkMates) {
8063                             /* but only adjudicate if adjudication enabled */
8064                             if(engineOpponent)
8065                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8066                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8067                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8068                             return 1;
8069                         }
8070                   }
8071                 } else bare = 1;
8072
8073
8074             // don't wait for engine to announce game end if we can judge ourselves
8075             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8076               case MT_CHECK:
8077                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8078                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8079                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8080                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8081                             checkCnt++;
8082                         if(checkCnt >= 2) {
8083                             reason = "Xboard adjudication: 3rd check";
8084                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8085                             break;
8086                         }
8087                     }
8088                 }
8089               case MT_NONE:
8090               default:
8091                 break;
8092               case MT_STEALMATE:
8093               case MT_STALEMATE:
8094               case MT_STAINMATE:
8095                 reason = "Xboard adjudication: Stalemate";
8096                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8097                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8098                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8099                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8100                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8101                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8102                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8103                                                                         EP_CHECKMATE : EP_WINS);
8104                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8105                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8106                 }
8107                 break;
8108               case MT_CHECKMATE:
8109                 reason = "Xboard adjudication: Checkmate";
8110                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8111                 if(gameInfo.variant == VariantShogi) {
8112                     if(forwardMostMove > backwardMostMove
8113                        && moveList[forwardMostMove-1][1] == '@'
8114                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8115                         reason = "XBoard adjudication: pawn-drop mate";
8116                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8117                     }
8118                 }
8119                 break;
8120             }
8121
8122                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8123                     case EP_STALEMATE:
8124                         result = GameIsDrawn; break;
8125                     case EP_CHECKMATE:
8126                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8127                     case EP_WINS:
8128                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8129                     default:
8130                         result = EndOfFile;
8131                 }
8132                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8133                     if(engineOpponent)
8134                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8135                     GameEnds( result, reason, GE_XBOARD );
8136                     return 1;
8137                 }
8138
8139                 /* Next absolutely insufficient mating material. */
8140                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8141                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8142                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8143
8144                      /* always flag draws, for judging claims */
8145                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8146
8147                      if(canAdjudicate && appData.materialDraws) {
8148                          /* but only adjudicate them if adjudication enabled */
8149                          if(engineOpponent) {
8150                            SendToProgram("force\n", engineOpponent); // suppress reply
8151                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8152                          }
8153                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8154                          return 1;
8155                      }
8156                 }
8157
8158                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8159                 if(gameInfo.variant == VariantXiangqi ?
8160                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8161                  : nrW + nrB == 4 &&
8162                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8163                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8164                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8165                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8166                    ) ) {
8167                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8168                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8169                           if(engineOpponent) {
8170                             SendToProgram("force\n", engineOpponent); // suppress reply
8171                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8172                           }
8173                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8174                           return 1;
8175                      }
8176                 } else moveCount = 6;
8177             }
8178
8179         // Repetition draws and 50-move rule can be applied independently of legality testing
8180
8181                 /* Check for rep-draws */
8182                 count = 0;
8183                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8184                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8185                 for(k = forwardMostMove-2;
8186                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8187                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8188                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8189                     k-=2)
8190                 {   int rights=0;
8191                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8192                         /* compare castling rights */
8193                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8194                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8195                                 rights++; /* King lost rights, while rook still had them */
8196                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8197                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8198                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8199                                    rights++; /* but at least one rook lost them */
8200                         }
8201                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8202                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8203                                 rights++;
8204                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8205                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8206                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8207                                    rights++;
8208                         }
8209                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8210                             && appData.drawRepeats > 1) {
8211                              /* adjudicate after user-specified nr of repeats */
8212                              int result = GameIsDrawn;
8213                              char *details = "XBoard adjudication: repetition draw";
8214                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8215                                 // [HGM] xiangqi: check for forbidden perpetuals
8216                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8217                                 for(m=forwardMostMove; m>k; m-=2) {
8218                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8219                                         ourPerpetual = 0; // the current mover did not always check
8220                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8221                                         hisPerpetual = 0; // the opponent did not always check
8222                                 }
8223                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8224                                                                         ourPerpetual, hisPerpetual);
8225                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8226                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8227                                     details = "Xboard adjudication: perpetual checking";
8228                                 } else
8229                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8230                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8231                                 } else
8232                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8233                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8234                                         result = BlackWins;
8235                                         details = "Xboard adjudication: repetition";
8236                                     }
8237                                 } else // it must be XQ
8238                                 // Now check for perpetual chases
8239                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8240                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8241                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8242                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8243                                         static char resdet[MSG_SIZ];
8244                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8245                                         details = resdet;
8246                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8247                                     } else
8248                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8249                                         break; // Abort repetition-checking loop.
8250                                 }
8251                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8252                              }
8253                              if(engineOpponent) {
8254                                SendToProgram("force\n", engineOpponent); // suppress reply
8255                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8256                              }
8257                              GameEnds( result, details, GE_XBOARD );
8258                              return 1;
8259                         }
8260                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8261                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8262                     }
8263                 }
8264
8265                 /* Now we test for 50-move draws. Determine ply count */
8266                 count = forwardMostMove;
8267                 /* look for last irreversble move */
8268                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8269                     count--;
8270                 /* if we hit starting position, add initial plies */
8271                 if( count == backwardMostMove )
8272                     count -= initialRulePlies;
8273                 count = forwardMostMove - count;
8274                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8275                         // adjust reversible move counter for checks in Xiangqi
8276                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8277                         if(i < backwardMostMove) i = backwardMostMove;
8278                         while(i <= forwardMostMove) {
8279                                 lastCheck = inCheck; // check evasion does not count
8280                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8281                                 if(inCheck || lastCheck) count--; // check does not count
8282                                 i++;
8283                         }
8284                 }
8285                 if( count >= 100)
8286                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8287                          /* this is used to judge if draw claims are legal */
8288                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8289                          if(engineOpponent) {
8290                            SendToProgram("force\n", engineOpponent); // suppress reply
8291                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8292                          }
8293                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8294                          return 1;
8295                 }
8296
8297                 /* if draw offer is pending, treat it as a draw claim
8298                  * when draw condition present, to allow engines a way to
8299                  * claim draws before making their move to avoid a race
8300                  * condition occurring after their move
8301                  */
8302                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8303                          char *p = NULL;
8304                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8305                              p = "Draw claim: 50-move rule";
8306                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8307                              p = "Draw claim: 3-fold repetition";
8308                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8309                              p = "Draw claim: insufficient mating material";
8310                          if( p != NULL && canAdjudicate) {
8311                              if(engineOpponent) {
8312                                SendToProgram("force\n", engineOpponent); // suppress reply
8313                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8314                              }
8315                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8316                              return 1;
8317                          }
8318                 }
8319
8320                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8321                     if(engineOpponent) {
8322                       SendToProgram("force\n", engineOpponent); // suppress reply
8323                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8324                     }
8325                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8326                     return 1;
8327                 }
8328         return 0;
8329 }
8330
8331 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8332 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8333 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8334
8335 static int
8336 BitbaseProbe ()
8337 {
8338     int pieces[10], squares[10], cnt=0, r, f, res;
8339     static int loaded;
8340     static PPROBE_EGBB probeBB;
8341     if(!appData.testLegality) return 10;
8342     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8343     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8344     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8345     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8346         ChessSquare piece = boards[forwardMostMove][r][f];
8347         int black = (piece >= BlackPawn);
8348         int type = piece - black*BlackPawn;
8349         if(piece == EmptySquare) continue;
8350         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8351         if(type == WhiteKing) type = WhiteQueen + 1;
8352         type = egbbCode[type];
8353         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8354         pieces[cnt] = type + black*6;
8355         if(++cnt > 5) return 11;
8356     }
8357     pieces[cnt] = squares[cnt] = 0;
8358     // probe EGBB
8359     if(loaded == 2) return 13; // loading failed before
8360     if(loaded == 0) {
8361         loaded = 2; // prepare for failure
8362         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8363         HMODULE lib;
8364         PLOAD_EGBB loadBB;
8365         if(!path) return 13; // no egbb installed
8366         strncpy(buf, path + 8, MSG_SIZ);
8367         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8368         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8369         lib = LoadLibrary(buf);
8370         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8371         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8372         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8373         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8374         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8375         loaded = 1; // success!
8376     }
8377     res = probeBB(forwardMostMove & 1, pieces, squares);
8378     return res > 0 ? 1 : res < 0 ? -1 : 0;
8379 }
8380
8381 char *
8382 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8383 {   // [HGM] book: this routine intercepts moves to simulate book replies
8384     char *bookHit = NULL;
8385
8386     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8387         char buf[MSG_SIZ];
8388         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8389         SendToProgram(buf, cps);
8390     }
8391     //first determine if the incoming move brings opponent into his book
8392     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8393         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8394     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8395     if(bookHit != NULL && !cps->bookSuspend) {
8396         // make sure opponent is not going to reply after receiving move to book position
8397         SendToProgram("force\n", cps);
8398         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8399     }
8400     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8401     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8402     // now arrange restart after book miss
8403     if(bookHit) {
8404         // after a book hit we never send 'go', and the code after the call to this routine
8405         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8406         char buf[MSG_SIZ], *move = bookHit;
8407         if(cps->useSAN) {
8408             int fromX, fromY, toX, toY;
8409             char promoChar;
8410             ChessMove moveType;
8411             move = buf + 30;
8412             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8413                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8414                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8415                                     PosFlags(forwardMostMove),
8416                                     fromY, fromX, toY, toX, promoChar, move);
8417             } else {
8418                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8419                 bookHit = NULL;
8420             }
8421         }
8422         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8423         SendToProgram(buf, cps);
8424         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8425     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8426         SendToProgram("go\n", cps);
8427         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8428     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8429         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8430             SendToProgram("go\n", cps);
8431         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8432     }
8433     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8434 }
8435
8436 int
8437 LoadError (char *errmess, ChessProgramState *cps)
8438 {   // unloads engine and switches back to -ncp mode if it was first
8439     if(cps->initDone) return FALSE;
8440     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8441     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8442     cps->pr = NoProc;
8443     if(cps == &first) {
8444         appData.noChessProgram = TRUE;
8445         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8446         gameMode = BeginningOfGame; ModeHighlight();
8447         SetNCPMode();
8448     }
8449     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8450     DisplayMessage("", ""); // erase waiting message
8451     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8452     return TRUE;
8453 }
8454
8455 char *savedMessage;
8456 ChessProgramState *savedState;
8457 void
8458 DeferredBookMove (void)
8459 {
8460         if(savedState->lastPing != savedState->lastPong)
8461                     ScheduleDelayedEvent(DeferredBookMove, 10);
8462         else
8463         HandleMachineMove(savedMessage, savedState);
8464 }
8465
8466 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8467 static ChessProgramState *stalledEngine;
8468 static char stashedInputMove[MSG_SIZ];
8469
8470 void
8471 HandleMachineMove (char *message, ChessProgramState *cps)
8472 {
8473     static char firstLeg[20];
8474     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8475     char realname[MSG_SIZ];
8476     int fromX, fromY, toX, toY;
8477     ChessMove moveType;
8478     char promoChar, roar;
8479     char *p, *pv=buf1;
8480     int machineWhite, oldError;
8481     char *bookHit;
8482
8483     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8484         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8485         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8486             DisplayError(_("Invalid pairing from pairing engine"), 0);
8487             return;
8488         }
8489         pairingReceived = 1;
8490         NextMatchGame();
8491         return; // Skim the pairing messages here.
8492     }
8493
8494     oldError = cps->userError; cps->userError = 0;
8495
8496 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8497     /*
8498      * Kludge to ignore BEL characters
8499      */
8500     while (*message == '\007') message++;
8501
8502     /*
8503      * [HGM] engine debug message: ignore lines starting with '#' character
8504      */
8505     if(cps->debug && *message == '#') return;
8506
8507     /*
8508      * Look for book output
8509      */
8510     if (cps == &first && bookRequested) {
8511         if (message[0] == '\t' || message[0] == ' ') {
8512             /* Part of the book output is here; append it */
8513             strcat(bookOutput, message);
8514             strcat(bookOutput, "  \n");
8515             return;
8516         } else if (bookOutput[0] != NULLCHAR) {
8517             /* All of book output has arrived; display it */
8518             char *p = bookOutput;
8519             while (*p != NULLCHAR) {
8520                 if (*p == '\t') *p = ' ';
8521                 p++;
8522             }
8523             DisplayInformation(bookOutput);
8524             bookRequested = FALSE;
8525             /* Fall through to parse the current output */
8526         }
8527     }
8528
8529     /*
8530      * Look for machine move.
8531      */
8532     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8533         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8534     {
8535         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8536             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8537             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8538             stalledEngine = cps;
8539             if(appData.ponderNextMove) { // bring opponent out of ponder
8540                 if(gameMode == TwoMachinesPlay) {
8541                     if(cps->other->pause)
8542                         PauseEngine(cps->other);
8543                     else
8544                         SendToProgram("easy\n", cps->other);
8545                 }
8546             }
8547             StopClocks();
8548             return;
8549         }
8550
8551         /* This method is only useful on engines that support ping */
8552         if (cps->lastPing != cps->lastPong) {
8553           if (gameMode == BeginningOfGame) {
8554             /* Extra move from before last new; ignore */
8555             if (appData.debugMode) {
8556                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8557             }
8558           } else {
8559             if (appData.debugMode) {
8560                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8561                         cps->which, gameMode);
8562             }
8563
8564             SendToProgram("undo\n", cps);
8565           }
8566           return;
8567         }
8568
8569         switch (gameMode) {
8570           case BeginningOfGame:
8571             /* Extra move from before last reset; ignore */
8572             if (appData.debugMode) {
8573                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8574             }
8575             return;
8576
8577           case EndOfGame:
8578           case IcsIdle:
8579           default:
8580             /* Extra move after we tried to stop.  The mode test is
8581                not a reliable way of detecting this problem, but it's
8582                the best we can do on engines that don't support ping.
8583             */
8584             if (appData.debugMode) {
8585                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8586                         cps->which, gameMode);
8587             }
8588             SendToProgram("undo\n", cps);
8589             return;
8590
8591           case MachinePlaysWhite:
8592           case IcsPlayingWhite:
8593             machineWhite = TRUE;
8594             break;
8595
8596           case MachinePlaysBlack:
8597           case IcsPlayingBlack:
8598             machineWhite = FALSE;
8599             break;
8600
8601           case TwoMachinesPlay:
8602             machineWhite = (cps->twoMachinesColor[0] == 'w');
8603             break;
8604         }
8605         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8606             if (appData.debugMode) {
8607                 fprintf(debugFP,
8608                         "Ignoring move out of turn by %s, gameMode %d"
8609                         ", forwardMost %d\n",
8610                         cps->which, gameMode, forwardMostMove);
8611             }
8612             return;
8613         }
8614
8615         if(cps->alphaRank) AlphaRank(machineMove, 4);
8616
8617         // [HGM] lion: (some very limited) support for Alien protocol
8618         killX = killY = -1;
8619         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8620             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8621             return;
8622         } else if(firstLeg[0]) { // there was a previous leg;
8623             // only support case where same piece makes two step (and don't even test that!)
8624             char buf[20], *p = machineMove+1, *q = buf+1, f;
8625             safeStrCpy(buf, machineMove, 20);
8626             while(isdigit(*q)) q++; // find start of to-square
8627             safeStrCpy(machineMove, firstLeg, 20);
8628             while(isdigit(*p)) p++;
8629             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8630             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8631             firstLeg[0] = NULLCHAR;
8632         }
8633
8634         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8635                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8636             /* Machine move could not be parsed; ignore it. */
8637           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8638                     machineMove, _(cps->which));
8639             DisplayMoveError(buf1);
8640             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8641                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8642             if (gameMode == TwoMachinesPlay) {
8643               GameEnds(machineWhite ? BlackWins : WhiteWins,
8644                        buf1, GE_XBOARD);
8645             }
8646             return;
8647         }
8648
8649         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8650         /* So we have to redo legality test with true e.p. status here,  */
8651         /* to make sure an illegal e.p. capture does not slip through,   */
8652         /* to cause a forfeit on a justified illegal-move complaint      */
8653         /* of the opponent.                                              */
8654         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8655            ChessMove moveType;
8656            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8657                              fromY, fromX, toY, toX, promoChar);
8658             if(moveType == IllegalMove) {
8659               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8660                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8661                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8662                            buf1, GE_XBOARD);
8663                 return;
8664            } else if(!appData.fischerCastling)
8665            /* [HGM] Kludge to handle engines that send FRC-style castling
8666               when they shouldn't (like TSCP-Gothic) */
8667            switch(moveType) {
8668              case WhiteASideCastleFR:
8669              case BlackASideCastleFR:
8670                toX+=2;
8671                currentMoveString[2]++;
8672                break;
8673              case WhiteHSideCastleFR:
8674              case BlackHSideCastleFR:
8675                toX--;
8676                currentMoveString[2]--;
8677                break;
8678              default: ; // nothing to do, but suppresses warning of pedantic compilers
8679            }
8680         }
8681         hintRequested = FALSE;
8682         lastHint[0] = NULLCHAR;
8683         bookRequested = FALSE;
8684         /* Program may be pondering now */
8685         cps->maybeThinking = TRUE;
8686         if (cps->sendTime == 2) cps->sendTime = 1;
8687         if (cps->offeredDraw) cps->offeredDraw--;
8688
8689         /* [AS] Save move info*/
8690         pvInfoList[ forwardMostMove ].score = programStats.score;
8691         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8692         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8693
8694         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8695
8696         /* Test suites abort the 'game' after one move */
8697         if(*appData.finger) {
8698            static FILE *f;
8699            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8700            if(!f) f = fopen(appData.finger, "w");
8701            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8702            else { DisplayFatalError("Bad output file", errno, 0); return; }
8703            free(fen);
8704            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8705         }
8706
8707         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8708         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8709             int count = 0;
8710
8711             while( count < adjudicateLossPlies ) {
8712                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8713
8714                 if( count & 1 ) {
8715                     score = -score; /* Flip score for winning side */
8716                 }
8717 printf("score=%d count=%d\n",score,count);
8718                 if( score > appData.adjudicateLossThreshold ) {
8719                     break;
8720                 }
8721
8722                 count++;
8723             }
8724
8725             if( count >= adjudicateLossPlies ) {
8726                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8727
8728                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8729                     "Xboard adjudication",
8730                     GE_XBOARD );
8731
8732                 return;
8733             }
8734         }
8735
8736         if(Adjudicate(cps)) {
8737             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8738             return; // [HGM] adjudicate: for all automatic game ends
8739         }
8740
8741 #if ZIPPY
8742         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8743             first.initDone) {
8744           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8745                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8746                 SendToICS("draw ");
8747                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8748           }
8749           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8750           ics_user_moved = 1;
8751           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8752                 char buf[3*MSG_SIZ];
8753
8754                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8755                         programStats.score / 100.,
8756                         programStats.depth,
8757                         programStats.time / 100.,
8758                         (unsigned int)programStats.nodes,
8759                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8760                         programStats.movelist);
8761                 SendToICS(buf);
8762           }
8763         }
8764 #endif
8765
8766         /* [AS] Clear stats for next move */
8767         ClearProgramStats();
8768         thinkOutput[0] = NULLCHAR;
8769         hiddenThinkOutputState = 0;
8770
8771         bookHit = NULL;
8772         if (gameMode == TwoMachinesPlay) {
8773             /* [HGM] relaying draw offers moved to after reception of move */
8774             /* and interpreting offer as claim if it brings draw condition */
8775             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8776                 SendToProgram("draw\n", cps->other);
8777             }
8778             if (cps->other->sendTime) {
8779                 SendTimeRemaining(cps->other,
8780                                   cps->other->twoMachinesColor[0] == 'w');
8781             }
8782             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8783             if (firstMove && !bookHit) {
8784                 firstMove = FALSE;
8785                 if (cps->other->useColors) {
8786                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8787                 }
8788                 SendToProgram("go\n", cps->other);
8789             }
8790             cps->other->maybeThinking = TRUE;
8791         }
8792
8793         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8794
8795         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8796
8797         if (!pausing && appData.ringBellAfterMoves) {
8798             if(!roar) RingBell();
8799         }
8800
8801         /*
8802          * Reenable menu items that were disabled while
8803          * machine was thinking
8804          */
8805         if (gameMode != TwoMachinesPlay)
8806             SetUserThinkingEnables();
8807
8808         // [HGM] book: after book hit opponent has received move and is now in force mode
8809         // force the book reply into it, and then fake that it outputted this move by jumping
8810         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8811         if(bookHit) {
8812                 static char bookMove[MSG_SIZ]; // a bit generous?
8813
8814                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8815                 strcat(bookMove, bookHit);
8816                 message = bookMove;
8817                 cps = cps->other;
8818                 programStats.nodes = programStats.depth = programStats.time =
8819                 programStats.score = programStats.got_only_move = 0;
8820                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8821
8822                 if(cps->lastPing != cps->lastPong) {
8823                     savedMessage = message; // args for deferred call
8824                     savedState = cps;
8825                     ScheduleDelayedEvent(DeferredBookMove, 10);
8826                     return;
8827                 }
8828                 goto FakeBookMove;
8829         }
8830
8831         return;
8832     }
8833
8834     /* Set special modes for chess engines.  Later something general
8835      *  could be added here; for now there is just one kludge feature,
8836      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8837      *  when "xboard" is given as an interactive command.
8838      */
8839     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8840         cps->useSigint = FALSE;
8841         cps->useSigterm = FALSE;
8842     }
8843     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8844       ParseFeatures(message+8, cps);
8845       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8846     }
8847
8848     if (!strncmp(message, "setup ", 6) && 
8849         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8850           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8851                                         ) { // [HGM] allow first engine to define opening position
8852       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8853       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8854       *buf = NULLCHAR;
8855       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8856       if(startedFromSetupPosition) return;
8857       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8858       if(dummy >= 3) {
8859         while(message[s] && message[s++] != ' ');
8860         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8861            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8862             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8863             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8864           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8865           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8866         }
8867       }
8868       ParseFEN(boards[0], &dummy, message+s, FALSE);
8869       DrawPosition(TRUE, boards[0]);
8870       startedFromSetupPosition = TRUE;
8871       return;
8872     }
8873     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8874      * want this, I was asked to put it in, and obliged.
8875      */
8876     if (!strncmp(message, "setboard ", 9)) {
8877         Board initial_position;
8878
8879         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8880
8881         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8882             DisplayError(_("Bad FEN received from engine"), 0);
8883             return ;
8884         } else {
8885            Reset(TRUE, FALSE);
8886            CopyBoard(boards[0], initial_position);
8887            initialRulePlies = FENrulePlies;
8888            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8889            else gameMode = MachinePlaysBlack;
8890            DrawPosition(FALSE, boards[currentMove]);
8891         }
8892         return;
8893     }
8894
8895     /*
8896      * Look for communication commands
8897      */
8898     if (!strncmp(message, "telluser ", 9)) {
8899         if(message[9] == '\\' && message[10] == '\\')
8900             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8901         PlayTellSound();
8902         DisplayNote(message + 9);
8903         return;
8904     }
8905     if (!strncmp(message, "tellusererror ", 14)) {
8906         cps->userError = 1;
8907         if(message[14] == '\\' && message[15] == '\\')
8908             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8909         PlayTellSound();
8910         DisplayError(message + 14, 0);
8911         return;
8912     }
8913     if (!strncmp(message, "tellopponent ", 13)) {
8914       if (appData.icsActive) {
8915         if (loggedOn) {
8916           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8917           SendToICS(buf1);
8918         }
8919       } else {
8920         DisplayNote(message + 13);
8921       }
8922       return;
8923     }
8924     if (!strncmp(message, "tellothers ", 11)) {
8925       if (appData.icsActive) {
8926         if (loggedOn) {
8927           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8928           SendToICS(buf1);
8929         }
8930       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8931       return;
8932     }
8933     if (!strncmp(message, "tellall ", 8)) {
8934       if (appData.icsActive) {
8935         if (loggedOn) {
8936           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8937           SendToICS(buf1);
8938         }
8939       } else {
8940         DisplayNote(message + 8);
8941       }
8942       return;
8943     }
8944     if (strncmp(message, "warning", 7) == 0) {
8945         /* Undocumented feature, use tellusererror in new code */
8946         DisplayError(message, 0);
8947         return;
8948     }
8949     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8950         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8951         strcat(realname, " query");
8952         AskQuestion(realname, buf2, buf1, cps->pr);
8953         return;
8954     }
8955     /* Commands from the engine directly to ICS.  We don't allow these to be
8956      *  sent until we are logged on. Crafty kibitzes have been known to
8957      *  interfere with the login process.
8958      */
8959     if (loggedOn) {
8960         if (!strncmp(message, "tellics ", 8)) {
8961             SendToICS(message + 8);
8962             SendToICS("\n");
8963             return;
8964         }
8965         if (!strncmp(message, "tellicsnoalias ", 15)) {
8966             SendToICS(ics_prefix);
8967             SendToICS(message + 15);
8968             SendToICS("\n");
8969             return;
8970         }
8971         /* The following are for backward compatibility only */
8972         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8973             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8974             SendToICS(ics_prefix);
8975             SendToICS(message);
8976             SendToICS("\n");
8977             return;
8978         }
8979     }
8980     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8981         if(initPing == cps->lastPong) {
8982             if(gameInfo.variant == VariantUnknown) {
8983                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8984                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8985                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8986             }
8987             initPing = -1;
8988         }
8989         return;
8990     }
8991     if(!strncmp(message, "highlight ", 10)) {
8992         if(appData.testLegality && appData.markers) return;
8993         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8994         return;
8995     }
8996     if(!strncmp(message, "click ", 6)) {
8997         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8998         if(appData.testLegality || !appData.oneClick) return;
8999         sscanf(message+6, "%c%d%c", &f, &y, &c);
9000         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9001         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9002         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9003         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9004         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9005         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9006             LeftClick(Release, lastLeftX, lastLeftY);
9007         controlKey  = (c == ',');
9008         LeftClick(Press, x, y);
9009         LeftClick(Release, x, y);
9010         first.highlight = f;
9011         return;
9012     }
9013     /*
9014      * If the move is illegal, cancel it and redraw the board.
9015      * Also deal with other error cases.  Matching is rather loose
9016      * here to accommodate engines written before the spec.
9017      */
9018     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9019         strncmp(message, "Error", 5) == 0) {
9020         if (StrStr(message, "name") ||
9021             StrStr(message, "rating") || StrStr(message, "?") ||
9022             StrStr(message, "result") || StrStr(message, "board") ||
9023             StrStr(message, "bk") || StrStr(message, "computer") ||
9024             StrStr(message, "variant") || StrStr(message, "hint") ||
9025             StrStr(message, "random") || StrStr(message, "depth") ||
9026             StrStr(message, "accepted")) {
9027             return;
9028         }
9029         if (StrStr(message, "protover")) {
9030           /* Program is responding to input, so it's apparently done
9031              initializing, and this error message indicates it is
9032              protocol version 1.  So we don't need to wait any longer
9033              for it to initialize and send feature commands. */
9034           FeatureDone(cps, 1);
9035           cps->protocolVersion = 1;
9036           return;
9037         }
9038         cps->maybeThinking = FALSE;
9039
9040         if (StrStr(message, "draw")) {
9041             /* Program doesn't have "draw" command */
9042             cps->sendDrawOffers = 0;
9043             return;
9044         }
9045         if (cps->sendTime != 1 &&
9046             (StrStr(message, "time") || StrStr(message, "otim"))) {
9047           /* Program apparently doesn't have "time" or "otim" command */
9048           cps->sendTime = 0;
9049           return;
9050         }
9051         if (StrStr(message, "analyze")) {
9052             cps->analysisSupport = FALSE;
9053             cps->analyzing = FALSE;
9054 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9055             EditGameEvent(); // [HGM] try to preserve loaded game
9056             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9057             DisplayError(buf2, 0);
9058             return;
9059         }
9060         if (StrStr(message, "(no matching move)st")) {
9061           /* Special kludge for GNU Chess 4 only */
9062           cps->stKludge = TRUE;
9063           SendTimeControl(cps, movesPerSession, timeControl,
9064                           timeIncrement, appData.searchDepth,
9065                           searchTime);
9066           return;
9067         }
9068         if (StrStr(message, "(no matching move)sd")) {
9069           /* Special kludge for GNU Chess 4 only */
9070           cps->sdKludge = TRUE;
9071           SendTimeControl(cps, movesPerSession, timeControl,
9072                           timeIncrement, appData.searchDepth,
9073                           searchTime);
9074           return;
9075         }
9076         if (!StrStr(message, "llegal")) {
9077             return;
9078         }
9079         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9080             gameMode == IcsIdle) return;
9081         if (forwardMostMove <= backwardMostMove) return;
9082         if (pausing) PauseEvent();
9083       if(appData.forceIllegal) {
9084             // [HGM] illegal: machine refused move; force position after move into it
9085           SendToProgram("force\n", cps);
9086           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9087                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9088                 // when black is to move, while there might be nothing on a2 or black
9089                 // might already have the move. So send the board as if white has the move.
9090                 // But first we must change the stm of the engine, as it refused the last move
9091                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9092                 if(WhiteOnMove(forwardMostMove)) {
9093                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9094                     SendBoard(cps, forwardMostMove); // kludgeless board
9095                 } else {
9096                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9097                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9098                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9099                 }
9100           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9101             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9102                  gameMode == TwoMachinesPlay)
9103               SendToProgram("go\n", cps);
9104             return;
9105       } else
9106         if (gameMode == PlayFromGameFile) {
9107             /* Stop reading this game file */
9108             gameMode = EditGame;
9109             ModeHighlight();
9110         }
9111         /* [HGM] illegal-move claim should forfeit game when Xboard */
9112         /* only passes fully legal moves                            */
9113         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9114             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9115                                 "False illegal-move claim", GE_XBOARD );
9116             return; // do not take back move we tested as valid
9117         }
9118         currentMove = forwardMostMove-1;
9119         DisplayMove(currentMove-1); /* before DisplayMoveError */
9120         SwitchClocks(forwardMostMove-1); // [HGM] race
9121         DisplayBothClocks();
9122         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9123                 parseList[currentMove], _(cps->which));
9124         DisplayMoveError(buf1);
9125         DrawPosition(FALSE, boards[currentMove]);
9126
9127         SetUserThinkingEnables();
9128         return;
9129     }
9130     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9131         /* Program has a broken "time" command that
9132            outputs a string not ending in newline.
9133            Don't use it. */
9134         cps->sendTime = 0;
9135     }
9136
9137     /*
9138      * If chess program startup fails, exit with an error message.
9139      * Attempts to recover here are futile. [HGM] Well, we try anyway
9140      */
9141     if ((StrStr(message, "unknown host") != NULL)
9142         || (StrStr(message, "No remote directory") != NULL)
9143         || (StrStr(message, "not found") != NULL)
9144         || (StrStr(message, "No such file") != NULL)
9145         || (StrStr(message, "can't alloc") != NULL)
9146         || (StrStr(message, "Permission denied") != NULL)) {
9147
9148         cps->maybeThinking = FALSE;
9149         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9150                 _(cps->which), cps->program, cps->host, message);
9151         RemoveInputSource(cps->isr);
9152         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9153             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9154             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9155         }
9156         return;
9157     }
9158
9159     /*
9160      * Look for hint output
9161      */
9162     if (sscanf(message, "Hint: %s", buf1) == 1) {
9163         if (cps == &first && hintRequested) {
9164             hintRequested = FALSE;
9165             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9166                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9167                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9168                                     PosFlags(forwardMostMove),
9169                                     fromY, fromX, toY, toX, promoChar, buf1);
9170                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9171                 DisplayInformation(buf2);
9172             } else {
9173                 /* Hint move could not be parsed!? */
9174               snprintf(buf2, sizeof(buf2),
9175                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9176                         buf1, _(cps->which));
9177                 DisplayError(buf2, 0);
9178             }
9179         } else {
9180           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9181         }
9182         return;
9183     }
9184
9185     /*
9186      * Ignore other messages if game is not in progress
9187      */
9188     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9189         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9190
9191     /*
9192      * look for win, lose, draw, or draw offer
9193      */
9194     if (strncmp(message, "1-0", 3) == 0) {
9195         char *p, *q, *r = "";
9196         p = strchr(message, '{');
9197         if (p) {
9198             q = strchr(p, '}');
9199             if (q) {
9200                 *q = NULLCHAR;
9201                 r = p + 1;
9202             }
9203         }
9204         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9205         return;
9206     } else if (strncmp(message, "0-1", 3) == 0) {
9207         char *p, *q, *r = "";
9208         p = strchr(message, '{');
9209         if (p) {
9210             q = strchr(p, '}');
9211             if (q) {
9212                 *q = NULLCHAR;
9213                 r = p + 1;
9214             }
9215         }
9216         /* Kludge for Arasan 4.1 bug */
9217         if (strcmp(r, "Black resigns") == 0) {
9218             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9219             return;
9220         }
9221         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9222         return;
9223     } else if (strncmp(message, "1/2", 3) == 0) {
9224         char *p, *q, *r = "";
9225         p = strchr(message, '{');
9226         if (p) {
9227             q = strchr(p, '}');
9228             if (q) {
9229                 *q = NULLCHAR;
9230                 r = p + 1;
9231             }
9232         }
9233
9234         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9235         return;
9236
9237     } else if (strncmp(message, "White resign", 12) == 0) {
9238         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9239         return;
9240     } else if (strncmp(message, "Black resign", 12) == 0) {
9241         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9242         return;
9243     } else if (strncmp(message, "White matches", 13) == 0 ||
9244                strncmp(message, "Black matches", 13) == 0   ) {
9245         /* [HGM] ignore GNUShogi noises */
9246         return;
9247     } else if (strncmp(message, "White", 5) == 0 &&
9248                message[5] != '(' &&
9249                StrStr(message, "Black") == NULL) {
9250         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9251         return;
9252     } else if (strncmp(message, "Black", 5) == 0 &&
9253                message[5] != '(') {
9254         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9255         return;
9256     } else if (strcmp(message, "resign") == 0 ||
9257                strcmp(message, "computer resigns") == 0) {
9258         switch (gameMode) {
9259           case MachinePlaysBlack:
9260           case IcsPlayingBlack:
9261             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9262             break;
9263           case MachinePlaysWhite:
9264           case IcsPlayingWhite:
9265             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9266             break;
9267           case TwoMachinesPlay:
9268             if (cps->twoMachinesColor[0] == 'w')
9269               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9270             else
9271               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9272             break;
9273           default:
9274             /* can't happen */
9275             break;
9276         }
9277         return;
9278     } else if (strncmp(message, "opponent mates", 14) == 0) {
9279         switch (gameMode) {
9280           case MachinePlaysBlack:
9281           case IcsPlayingBlack:
9282             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9283             break;
9284           case MachinePlaysWhite:
9285           case IcsPlayingWhite:
9286             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9287             break;
9288           case TwoMachinesPlay:
9289             if (cps->twoMachinesColor[0] == 'w')
9290               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9291             else
9292               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9293             break;
9294           default:
9295             /* can't happen */
9296             break;
9297         }
9298         return;
9299     } else if (strncmp(message, "computer mates", 14) == 0) {
9300         switch (gameMode) {
9301           case MachinePlaysBlack:
9302           case IcsPlayingBlack:
9303             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9304             break;
9305           case MachinePlaysWhite:
9306           case IcsPlayingWhite:
9307             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9308             break;
9309           case TwoMachinesPlay:
9310             if (cps->twoMachinesColor[0] == 'w')
9311               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9312             else
9313               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9314             break;
9315           default:
9316             /* can't happen */
9317             break;
9318         }
9319         return;
9320     } else if (strncmp(message, "checkmate", 9) == 0) {
9321         if (WhiteOnMove(forwardMostMove)) {
9322             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9323         } else {
9324             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9325         }
9326         return;
9327     } else if (strstr(message, "Draw") != NULL ||
9328                strstr(message, "game is a draw") != NULL) {
9329         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9330         return;
9331     } else if (strstr(message, "offer") != NULL &&
9332                strstr(message, "draw") != NULL) {
9333 #if ZIPPY
9334         if (appData.zippyPlay && first.initDone) {
9335             /* Relay offer to ICS */
9336             SendToICS(ics_prefix);
9337             SendToICS("draw\n");
9338         }
9339 #endif
9340         cps->offeredDraw = 2; /* valid until this engine moves twice */
9341         if (gameMode == TwoMachinesPlay) {
9342             if (cps->other->offeredDraw) {
9343                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9344             /* [HGM] in two-machine mode we delay relaying draw offer      */
9345             /* until after we also have move, to see if it is really claim */
9346             }
9347         } else if (gameMode == MachinePlaysWhite ||
9348                    gameMode == MachinePlaysBlack) {
9349           if (userOfferedDraw) {
9350             DisplayInformation(_("Machine accepts your draw offer"));
9351             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9352           } else {
9353             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9354           }
9355         }
9356     }
9357
9358
9359     /*
9360      * Look for thinking output
9361      */
9362     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9363           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9364                                 ) {
9365         int plylev, mvleft, mvtot, curscore, time;
9366         char mvname[MOVE_LEN];
9367         u64 nodes; // [DM]
9368         char plyext;
9369         int ignore = FALSE;
9370         int prefixHint = FALSE;
9371         mvname[0] = NULLCHAR;
9372
9373         switch (gameMode) {
9374           case MachinePlaysBlack:
9375           case IcsPlayingBlack:
9376             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9377             break;
9378           case MachinePlaysWhite:
9379           case IcsPlayingWhite:
9380             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9381             break;
9382           case AnalyzeMode:
9383           case AnalyzeFile:
9384             break;
9385           case IcsObserving: /* [DM] icsEngineAnalyze */
9386             if (!appData.icsEngineAnalyze) ignore = TRUE;
9387             break;
9388           case TwoMachinesPlay:
9389             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9390                 ignore = TRUE;
9391             }
9392             break;
9393           default:
9394             ignore = TRUE;
9395             break;
9396         }
9397
9398         if (!ignore) {
9399             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9400             buf1[0] = NULLCHAR;
9401             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9402                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9403
9404                 if (plyext != ' ' && plyext != '\t') {
9405                     time *= 100;
9406                 }
9407
9408                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9409                 if( cps->scoreIsAbsolute &&
9410                     ( gameMode == MachinePlaysBlack ||
9411                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9412                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9413                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9414                      !WhiteOnMove(currentMove)
9415                     ) )
9416                 {
9417                     curscore = -curscore;
9418                 }
9419
9420                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9421
9422                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9423                         char buf[MSG_SIZ];
9424                         FILE *f;
9425                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9426                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9427                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9428                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9429                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9430                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9431                                 fclose(f);
9432                         }
9433                         else
9434                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9435                           DisplayError(_("failed writing PV"), 0);
9436                 }
9437
9438                 tempStats.depth = plylev;
9439                 tempStats.nodes = nodes;
9440                 tempStats.time = time;
9441                 tempStats.score = curscore;
9442                 tempStats.got_only_move = 0;
9443
9444                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9445                         int ticklen;
9446
9447                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9448                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9449                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9450                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9451                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9452                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9453                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9454                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9455                 }
9456
9457                 /* Buffer overflow protection */
9458                 if (pv[0] != NULLCHAR) {
9459                     if (strlen(pv) >= sizeof(tempStats.movelist)
9460                         && appData.debugMode) {
9461                         fprintf(debugFP,
9462                                 "PV is too long; using the first %u bytes.\n",
9463                                 (unsigned) sizeof(tempStats.movelist) - 1);
9464                     }
9465
9466                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9467                 } else {
9468                     sprintf(tempStats.movelist, " no PV\n");
9469                 }
9470
9471                 if (tempStats.seen_stat) {
9472                     tempStats.ok_to_send = 1;
9473                 }
9474
9475                 if (strchr(tempStats.movelist, '(') != NULL) {
9476                     tempStats.line_is_book = 1;
9477                     tempStats.nr_moves = 0;
9478                     tempStats.moves_left = 0;
9479                 } else {
9480                     tempStats.line_is_book = 0;
9481                 }
9482
9483                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9484                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9485
9486                 SendProgramStatsToFrontend( cps, &tempStats );
9487
9488                 /*
9489                     [AS] Protect the thinkOutput buffer from overflow... this
9490                     is only useful if buf1 hasn't overflowed first!
9491                 */
9492                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9493                          plylev,
9494                          (gameMode == TwoMachinesPlay ?
9495                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9496                          ((double) curscore) / 100.0,
9497                          prefixHint ? lastHint : "",
9498                          prefixHint ? " " : "" );
9499
9500                 if( buf1[0] != NULLCHAR ) {
9501                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9502
9503                     if( strlen(pv) > max_len ) {
9504                         if( appData.debugMode) {
9505                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9506                         }
9507                         pv[max_len+1] = '\0';
9508                     }
9509
9510                     strcat( thinkOutput, pv);
9511                 }
9512
9513                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9514                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9515                     DisplayMove(currentMove - 1);
9516                 }
9517                 return;
9518
9519             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9520                 /* crafty (9.25+) says "(only move) <move>"
9521                  * if there is only 1 legal move
9522                  */
9523                 sscanf(p, "(only move) %s", buf1);
9524                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9525                 sprintf(programStats.movelist, "%s (only move)", buf1);
9526                 programStats.depth = 1;
9527                 programStats.nr_moves = 1;
9528                 programStats.moves_left = 1;
9529                 programStats.nodes = 1;
9530                 programStats.time = 1;
9531                 programStats.got_only_move = 1;
9532
9533                 /* Not really, but we also use this member to
9534                    mean "line isn't going to change" (Crafty
9535                    isn't searching, so stats won't change) */
9536                 programStats.line_is_book = 1;
9537
9538                 SendProgramStatsToFrontend( cps, &programStats );
9539
9540                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9541                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9542                     DisplayMove(currentMove - 1);
9543                 }
9544                 return;
9545             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9546                               &time, &nodes, &plylev, &mvleft,
9547                               &mvtot, mvname) >= 5) {
9548                 /* The stat01: line is from Crafty (9.29+) in response
9549                    to the "." command */
9550                 programStats.seen_stat = 1;
9551                 cps->maybeThinking = TRUE;
9552
9553                 if (programStats.got_only_move || !appData.periodicUpdates)
9554                   return;
9555
9556                 programStats.depth = plylev;
9557                 programStats.time = time;
9558                 programStats.nodes = nodes;
9559                 programStats.moves_left = mvleft;
9560                 programStats.nr_moves = mvtot;
9561                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9562                 programStats.ok_to_send = 1;
9563                 programStats.movelist[0] = '\0';
9564
9565                 SendProgramStatsToFrontend( cps, &programStats );
9566
9567                 return;
9568
9569             } else if (strncmp(message,"++",2) == 0) {
9570                 /* Crafty 9.29+ outputs this */
9571                 programStats.got_fail = 2;
9572                 return;
9573
9574             } else if (strncmp(message,"--",2) == 0) {
9575                 /* Crafty 9.29+ outputs this */
9576                 programStats.got_fail = 1;
9577                 return;
9578
9579             } else if (thinkOutput[0] != NULLCHAR &&
9580                        strncmp(message, "    ", 4) == 0) {
9581                 unsigned message_len;
9582
9583                 p = message;
9584                 while (*p && *p == ' ') p++;
9585
9586                 message_len = strlen( p );
9587
9588                 /* [AS] Avoid buffer overflow */
9589                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9590                     strcat(thinkOutput, " ");
9591                     strcat(thinkOutput, p);
9592                 }
9593
9594                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9595                     strcat(programStats.movelist, " ");
9596                     strcat(programStats.movelist, p);
9597                 }
9598
9599                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9600                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9601                     DisplayMove(currentMove - 1);
9602                 }
9603                 return;
9604             }
9605         }
9606         else {
9607             buf1[0] = NULLCHAR;
9608
9609             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9610                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9611             {
9612                 ChessProgramStats cpstats;
9613
9614                 if (plyext != ' ' && plyext != '\t') {
9615                     time *= 100;
9616                 }
9617
9618                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9619                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9620                     curscore = -curscore;
9621                 }
9622
9623                 cpstats.depth = plylev;
9624                 cpstats.nodes = nodes;
9625                 cpstats.time = time;
9626                 cpstats.score = curscore;
9627                 cpstats.got_only_move = 0;
9628                 cpstats.movelist[0] = '\0';
9629
9630                 if (buf1[0] != NULLCHAR) {
9631                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9632                 }
9633
9634                 cpstats.ok_to_send = 0;
9635                 cpstats.line_is_book = 0;
9636                 cpstats.nr_moves = 0;
9637                 cpstats.moves_left = 0;
9638
9639                 SendProgramStatsToFrontend( cps, &cpstats );
9640             }
9641         }
9642     }
9643 }
9644
9645
9646 /* Parse a game score from the character string "game", and
9647    record it as the history of the current game.  The game
9648    score is NOT assumed to start from the standard position.
9649    The display is not updated in any way.
9650    */
9651 void
9652 ParseGameHistory (char *game)
9653 {
9654     ChessMove moveType;
9655     int fromX, fromY, toX, toY, boardIndex;
9656     char promoChar;
9657     char *p, *q;
9658     char buf[MSG_SIZ];
9659
9660     if (appData.debugMode)
9661       fprintf(debugFP, "Parsing game history: %s\n", game);
9662
9663     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9664     gameInfo.site = StrSave(appData.icsHost);
9665     gameInfo.date = PGNDate();
9666     gameInfo.round = StrSave("-");
9667
9668     /* Parse out names of players */
9669     while (*game == ' ') game++;
9670     p = buf;
9671     while (*game != ' ') *p++ = *game++;
9672     *p = NULLCHAR;
9673     gameInfo.white = StrSave(buf);
9674     while (*game == ' ') game++;
9675     p = buf;
9676     while (*game != ' ' && *game != '\n') *p++ = *game++;
9677     *p = NULLCHAR;
9678     gameInfo.black = StrSave(buf);
9679
9680     /* Parse moves */
9681     boardIndex = blackPlaysFirst ? 1 : 0;
9682     yynewstr(game);
9683     for (;;) {
9684         yyboardindex = boardIndex;
9685         moveType = (ChessMove) Myylex();
9686         switch (moveType) {
9687           case IllegalMove:             /* maybe suicide chess, etc. */
9688   if (appData.debugMode) {
9689     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9690     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9691     setbuf(debugFP, NULL);
9692   }
9693           case WhitePromotion:
9694           case BlackPromotion:
9695           case WhiteNonPromotion:
9696           case BlackNonPromotion:
9697           case NormalMove:
9698           case FirstLeg:
9699           case WhiteCapturesEnPassant:
9700           case BlackCapturesEnPassant:
9701           case WhiteKingSideCastle:
9702           case WhiteQueenSideCastle:
9703           case BlackKingSideCastle:
9704           case BlackQueenSideCastle:
9705           case WhiteKingSideCastleWild:
9706           case WhiteQueenSideCastleWild:
9707           case BlackKingSideCastleWild:
9708           case BlackQueenSideCastleWild:
9709           /* PUSH Fabien */
9710           case WhiteHSideCastleFR:
9711           case WhiteASideCastleFR:
9712           case BlackHSideCastleFR:
9713           case BlackASideCastleFR:
9714           /* POP Fabien */
9715             fromX = currentMoveString[0] - AAA;
9716             fromY = currentMoveString[1] - ONE;
9717             toX = currentMoveString[2] - AAA;
9718             toY = currentMoveString[3] - ONE;
9719             promoChar = currentMoveString[4];
9720             break;
9721           case WhiteDrop:
9722           case BlackDrop:
9723             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9724             fromX = moveType == WhiteDrop ?
9725               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9726             (int) CharToPiece(ToLower(currentMoveString[0]));
9727             fromY = DROP_RANK;
9728             toX = currentMoveString[2] - AAA;
9729             toY = currentMoveString[3] - ONE;
9730             promoChar = NULLCHAR;
9731             break;
9732           case AmbiguousMove:
9733             /* bug? */
9734             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9735   if (appData.debugMode) {
9736     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9737     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9738     setbuf(debugFP, NULL);
9739   }
9740             DisplayError(buf, 0);
9741             return;
9742           case ImpossibleMove:
9743             /* bug? */
9744             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9745   if (appData.debugMode) {
9746     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9747     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9748     setbuf(debugFP, NULL);
9749   }
9750             DisplayError(buf, 0);
9751             return;
9752           case EndOfFile:
9753             if (boardIndex < backwardMostMove) {
9754                 /* Oops, gap.  How did that happen? */
9755                 DisplayError(_("Gap in move list"), 0);
9756                 return;
9757             }
9758             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9759             if (boardIndex > forwardMostMove) {
9760                 forwardMostMove = boardIndex;
9761             }
9762             return;
9763           case ElapsedTime:
9764             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9765                 strcat(parseList[boardIndex-1], " ");
9766                 strcat(parseList[boardIndex-1], yy_text);
9767             }
9768             continue;
9769           case Comment:
9770           case PGNTag:
9771           case NAG:
9772           default:
9773             /* ignore */
9774             continue;
9775           case WhiteWins:
9776           case BlackWins:
9777           case GameIsDrawn:
9778           case GameUnfinished:
9779             if (gameMode == IcsExamining) {
9780                 if (boardIndex < backwardMostMove) {
9781                     /* Oops, gap.  How did that happen? */
9782                     return;
9783                 }
9784                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9785                 return;
9786             }
9787             gameInfo.result = moveType;
9788             p = strchr(yy_text, '{');
9789             if (p == NULL) p = strchr(yy_text, '(');
9790             if (p == NULL) {
9791                 p = yy_text;
9792                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9793             } else {
9794                 q = strchr(p, *p == '{' ? '}' : ')');
9795                 if (q != NULL) *q = NULLCHAR;
9796                 p++;
9797             }
9798             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9799             gameInfo.resultDetails = StrSave(p);
9800             continue;
9801         }
9802         if (boardIndex >= forwardMostMove &&
9803             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9804             backwardMostMove = blackPlaysFirst ? 1 : 0;
9805             return;
9806         }
9807         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9808                                  fromY, fromX, toY, toX, promoChar,
9809                                  parseList[boardIndex]);
9810         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9811         /* currentMoveString is set as a side-effect of yylex */
9812         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9813         strcat(moveList[boardIndex], "\n");
9814         boardIndex++;
9815         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9816         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9817           case MT_NONE:
9818           case MT_STALEMATE:
9819           default:
9820             break;
9821           case MT_CHECK:
9822             if(!IS_SHOGI(gameInfo.variant))
9823                 strcat(parseList[boardIndex - 1], "+");
9824             break;
9825           case MT_CHECKMATE:
9826           case MT_STAINMATE:
9827             strcat(parseList[boardIndex - 1], "#");
9828             break;
9829         }
9830     }
9831 }
9832
9833
9834 /* Apply a move to the given board  */
9835 void
9836 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9837 {
9838   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9839   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9840
9841     /* [HGM] compute & store e.p. status and castling rights for new position */
9842     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9843
9844       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9845       oldEP = (signed char)board[EP_STATUS];
9846       board[EP_STATUS] = EP_NONE;
9847
9848   if (fromY == DROP_RANK) {
9849         /* must be first */
9850         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9851             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9852             return;
9853         }
9854         piece = board[toY][toX] = (ChessSquare) fromX;
9855   } else {
9856 //      ChessSquare victim;
9857       int i;
9858
9859       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9860 //           victim = board[killY][killX],
9861            board[killY][killX] = EmptySquare,
9862            board[EP_STATUS] = EP_CAPTURE;
9863
9864       if( board[toY][toX] != EmptySquare ) {
9865            board[EP_STATUS] = EP_CAPTURE;
9866            if( (fromX != toX || fromY != toY) && // not igui!
9867                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9868                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9869                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9870            }
9871       }
9872
9873       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9874            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9875                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9876       } else
9877       if( board[fromY][fromX] == WhitePawn ) {
9878            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9879                board[EP_STATUS] = EP_PAWN_MOVE;
9880            if( toY-fromY==2) {
9881                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9882                         gameInfo.variant != VariantBerolina || toX < fromX)
9883                       board[EP_STATUS] = toX | berolina;
9884                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9885                         gameInfo.variant != VariantBerolina || toX > fromX)
9886                       board[EP_STATUS] = toX;
9887            }
9888       } else
9889       if( board[fromY][fromX] == BlackPawn ) {
9890            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9891                board[EP_STATUS] = EP_PAWN_MOVE;
9892            if( toY-fromY== -2) {
9893                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9894                         gameInfo.variant != VariantBerolina || toX < fromX)
9895                       board[EP_STATUS] = toX | berolina;
9896                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9897                         gameInfo.variant != VariantBerolina || toX > fromX)
9898                       board[EP_STATUS] = toX;
9899            }
9900        }
9901
9902        for(i=0; i<nrCastlingRights; i++) {
9903            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9904               board[CASTLING][i] == toX   && castlingRank[i] == toY
9905              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9906        }
9907
9908        if(gameInfo.variant == VariantSChess) { // update virginity
9909            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9910            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9911            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9912            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9913        }
9914
9915      if (fromX == toX && fromY == toY) return;
9916
9917      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9918      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9919      if(gameInfo.variant == VariantKnightmate)
9920          king += (int) WhiteUnicorn - (int) WhiteKing;
9921
9922     /* Code added by Tord: */
9923     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9924     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9925         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9926       board[fromY][fromX] = EmptySquare;
9927       board[toY][toX] = EmptySquare;
9928       if((toX > fromX) != (piece == WhiteRook)) {
9929         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9930       } else {
9931         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9932       }
9933     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9934                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9935       board[fromY][fromX] = EmptySquare;
9936       board[toY][toX] = EmptySquare;
9937       if((toX > fromX) != (piece == BlackRook)) {
9938         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9939       } else {
9940         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9941       }
9942     /* End of code added by Tord */
9943
9944     } else if (board[fromY][fromX] == king
9945         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9946         && toY == fromY && toX > fromX+1) {
9947         board[fromY][fromX] = EmptySquare;
9948         board[toY][toX] = king;
9949         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9950         board[fromY][BOARD_RGHT-1] = EmptySquare;
9951     } else if (board[fromY][fromX] == king
9952         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9953                && toY == fromY && toX < fromX-1) {
9954         board[fromY][fromX] = EmptySquare;
9955         board[toY][toX] = king;
9956         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9957         board[fromY][BOARD_LEFT] = EmptySquare;
9958     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9959                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9960                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9961                ) {
9962         /* white pawn promotion */
9963         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9964         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9965             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9966         board[fromY][fromX] = EmptySquare;
9967     } else if ((fromY >= BOARD_HEIGHT>>1)
9968                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9969                && (toX != fromX)
9970                && gameInfo.variant != VariantXiangqi
9971                && gameInfo.variant != VariantBerolina
9972                && (board[fromY][fromX] == WhitePawn)
9973                && (board[toY][toX] == EmptySquare)) {
9974         board[fromY][fromX] = EmptySquare;
9975         board[toY][toX] = WhitePawn;
9976         captured = board[toY - 1][toX];
9977         board[toY - 1][toX] = EmptySquare;
9978     } else if ((fromY == BOARD_HEIGHT-4)
9979                && (toX == fromX)
9980                && gameInfo.variant == VariantBerolina
9981                && (board[fromY][fromX] == WhitePawn)
9982                && (board[toY][toX] == EmptySquare)) {
9983         board[fromY][fromX] = EmptySquare;
9984         board[toY][toX] = WhitePawn;
9985         if(oldEP & EP_BEROLIN_A) {
9986                 captured = board[fromY][fromX-1];
9987                 board[fromY][fromX-1] = EmptySquare;
9988         }else{  captured = board[fromY][fromX+1];
9989                 board[fromY][fromX+1] = EmptySquare;
9990         }
9991     } else if (board[fromY][fromX] == king
9992         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9993                && toY == fromY && toX > fromX+1) {
9994         board[fromY][fromX] = EmptySquare;
9995         board[toY][toX] = king;
9996         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9997         board[fromY][BOARD_RGHT-1] = EmptySquare;
9998     } else if (board[fromY][fromX] == king
9999         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10000                && toY == fromY && toX < fromX-1) {
10001         board[fromY][fromX] = EmptySquare;
10002         board[toY][toX] = king;
10003         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10004         board[fromY][BOARD_LEFT] = EmptySquare;
10005     } else if (fromY == 7 && fromX == 3
10006                && board[fromY][fromX] == BlackKing
10007                && toY == 7 && toX == 5) {
10008         board[fromY][fromX] = EmptySquare;
10009         board[toY][toX] = BlackKing;
10010         board[fromY][7] = EmptySquare;
10011         board[toY][4] = BlackRook;
10012     } else if (fromY == 7 && fromX == 3
10013                && board[fromY][fromX] == BlackKing
10014                && toY == 7 && toX == 1) {
10015         board[fromY][fromX] = EmptySquare;
10016         board[toY][toX] = BlackKing;
10017         board[fromY][0] = EmptySquare;
10018         board[toY][2] = BlackRook;
10019     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10020                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10021                && toY < promoRank && promoChar
10022                ) {
10023         /* black pawn promotion */
10024         board[toY][toX] = CharToPiece(ToLower(promoChar));
10025         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10026             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10027         board[fromY][fromX] = EmptySquare;
10028     } else if ((fromY < BOARD_HEIGHT>>1)
10029                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10030                && (toX != fromX)
10031                && gameInfo.variant != VariantXiangqi
10032                && gameInfo.variant != VariantBerolina
10033                && (board[fromY][fromX] == BlackPawn)
10034                && (board[toY][toX] == EmptySquare)) {
10035         board[fromY][fromX] = EmptySquare;
10036         board[toY][toX] = BlackPawn;
10037         captured = board[toY + 1][toX];
10038         board[toY + 1][toX] = EmptySquare;
10039     } else if ((fromY == 3)
10040                && (toX == fromX)
10041                && gameInfo.variant == VariantBerolina
10042                && (board[fromY][fromX] == BlackPawn)
10043                && (board[toY][toX] == EmptySquare)) {
10044         board[fromY][fromX] = EmptySquare;
10045         board[toY][toX] = BlackPawn;
10046         if(oldEP & EP_BEROLIN_A) {
10047                 captured = board[fromY][fromX-1];
10048                 board[fromY][fromX-1] = EmptySquare;
10049         }else{  captured = board[fromY][fromX+1];
10050                 board[fromY][fromX+1] = EmptySquare;
10051         }
10052     } else {
10053         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10054         board[fromY][fromX] = EmptySquare;
10055         board[toY][toX] = piece;
10056     }
10057   }
10058
10059     if (gameInfo.holdingsWidth != 0) {
10060
10061       /* !!A lot more code needs to be written to support holdings  */
10062       /* [HGM] OK, so I have written it. Holdings are stored in the */
10063       /* penultimate board files, so they are automaticlly stored   */
10064       /* in the game history.                                       */
10065       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10066                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10067         /* Delete from holdings, by decreasing count */
10068         /* and erasing image if necessary            */
10069         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10070         if(p < (int) BlackPawn) { /* white drop */
10071              p -= (int)WhitePawn;
10072                  p = PieceToNumber((ChessSquare)p);
10073              if(p >= gameInfo.holdingsSize) p = 0;
10074              if(--board[p][BOARD_WIDTH-2] <= 0)
10075                   board[p][BOARD_WIDTH-1] = EmptySquare;
10076              if((int)board[p][BOARD_WIDTH-2] < 0)
10077                         board[p][BOARD_WIDTH-2] = 0;
10078         } else {                  /* black drop */
10079              p -= (int)BlackPawn;
10080                  p = PieceToNumber((ChessSquare)p);
10081              if(p >= gameInfo.holdingsSize) p = 0;
10082              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10083                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10084              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10085                         board[BOARD_HEIGHT-1-p][1] = 0;
10086         }
10087       }
10088       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10089           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10090         /* [HGM] holdings: Add to holdings, if holdings exist */
10091         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10092                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10093                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10094         }
10095         p = (int) captured;
10096         if (p >= (int) BlackPawn) {
10097           p -= (int)BlackPawn;
10098           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10099                   /* in Shogi restore piece to its original  first */
10100                   captured = (ChessSquare) (DEMOTED captured);
10101                   p = DEMOTED p;
10102           }
10103           p = PieceToNumber((ChessSquare)p);
10104           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10105           board[p][BOARD_WIDTH-2]++;
10106           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10107         } else {
10108           p -= (int)WhitePawn;
10109           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10110                   captured = (ChessSquare) (DEMOTED captured);
10111                   p = DEMOTED p;
10112           }
10113           p = PieceToNumber((ChessSquare)p);
10114           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10115           board[BOARD_HEIGHT-1-p][1]++;
10116           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10117         }
10118       }
10119     } else if (gameInfo.variant == VariantAtomic) {
10120       if (captured != EmptySquare) {
10121         int y, x;
10122         for (y = toY-1; y <= toY+1; y++) {
10123           for (x = toX-1; x <= toX+1; x++) {
10124             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10125                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10126               board[y][x] = EmptySquare;
10127             }
10128           }
10129         }
10130         board[toY][toX] = EmptySquare;
10131       }
10132     }
10133
10134     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10135         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10136     } else
10137     if(promoChar == '+') {
10138         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10139         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10140         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10141           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10142     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10143         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10144         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10145            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10146         board[toY][toX] = newPiece;
10147     }
10148     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10149                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10150         // [HGM] superchess: take promotion piece out of holdings
10151         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10152         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10153             if(!--board[k][BOARD_WIDTH-2])
10154                 board[k][BOARD_WIDTH-1] = EmptySquare;
10155         } else {
10156             if(!--board[BOARD_HEIGHT-1-k][1])
10157                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10158         }
10159     }
10160 }
10161
10162 /* Updates forwardMostMove */
10163 void
10164 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10165 {
10166     int x = toX, y = toY;
10167     char *s = parseList[forwardMostMove];
10168     ChessSquare p = boards[forwardMostMove][toY][toX];
10169 //    forwardMostMove++; // [HGM] bare: moved downstream
10170
10171     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10172     (void) CoordsToAlgebraic(boards[forwardMostMove],
10173                              PosFlags(forwardMostMove),
10174                              fromY, fromX, y, x, promoChar,
10175                              s);
10176     if(killX >= 0 && killY >= 0)
10177         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10178
10179     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10180         int timeLeft; static int lastLoadFlag=0; int king, piece;
10181         piece = boards[forwardMostMove][fromY][fromX];
10182         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10183         if(gameInfo.variant == VariantKnightmate)
10184             king += (int) WhiteUnicorn - (int) WhiteKing;
10185         if(forwardMostMove == 0) {
10186             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10187                 fprintf(serverMoves, "%s;", UserName());
10188             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10189                 fprintf(serverMoves, "%s;", second.tidy);
10190             fprintf(serverMoves, "%s;", first.tidy);
10191             if(gameMode == MachinePlaysWhite)
10192                 fprintf(serverMoves, "%s;", UserName());
10193             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10194                 fprintf(serverMoves, "%s;", second.tidy);
10195         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10196         lastLoadFlag = loadFlag;
10197         // print base move
10198         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10199         // print castling suffix
10200         if( toY == fromY && piece == king ) {
10201             if(toX-fromX > 1)
10202                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10203             if(fromX-toX >1)
10204                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10205         }
10206         // e.p. suffix
10207         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10208              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10209              boards[forwardMostMove][toY][toX] == EmptySquare
10210              && fromX != toX && fromY != toY)
10211                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10212         // promotion suffix
10213         if(promoChar != NULLCHAR) {
10214             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10215                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10216                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10217             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10218         }
10219         if(!loadFlag) {
10220                 char buf[MOVE_LEN*2], *p; int len;
10221             fprintf(serverMoves, "/%d/%d",
10222                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10223             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10224             else                      timeLeft = blackTimeRemaining/1000;
10225             fprintf(serverMoves, "/%d", timeLeft);
10226                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10227                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10228                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10229                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10230             fprintf(serverMoves, "/%s", buf);
10231         }
10232         fflush(serverMoves);
10233     }
10234
10235     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10236         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10237       return;
10238     }
10239     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10240     if (commentList[forwardMostMove+1] != NULL) {
10241         free(commentList[forwardMostMove+1]);
10242         commentList[forwardMostMove+1] = NULL;
10243     }
10244     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10245     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10246     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10247     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10248     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10249     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10250     adjustedClock = FALSE;
10251     gameInfo.result = GameUnfinished;
10252     if (gameInfo.resultDetails != NULL) {
10253         free(gameInfo.resultDetails);
10254         gameInfo.resultDetails = NULL;
10255     }
10256     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10257                               moveList[forwardMostMove - 1]);
10258     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10259       case MT_NONE:
10260       case MT_STALEMATE:
10261       default:
10262         break;
10263       case MT_CHECK:
10264         if(!IS_SHOGI(gameInfo.variant))
10265             strcat(parseList[forwardMostMove - 1], "+");
10266         break;
10267       case MT_CHECKMATE:
10268       case MT_STAINMATE:
10269         strcat(parseList[forwardMostMove - 1], "#");
10270         break;
10271     }
10272 }
10273
10274 /* Updates currentMove if not pausing */
10275 void
10276 ShowMove (int fromX, int fromY, int toX, int toY)
10277 {
10278     int instant = (gameMode == PlayFromGameFile) ?
10279         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10280     if(appData.noGUI) return;
10281     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10282         if (!instant) {
10283             if (forwardMostMove == currentMove + 1) {
10284                 AnimateMove(boards[forwardMostMove - 1],
10285                             fromX, fromY, toX, toY);
10286             }
10287         }
10288         currentMove = forwardMostMove;
10289     }
10290
10291     killX = killY = -1; // [HGM] lion: used up
10292
10293     if (instant) return;
10294
10295     DisplayMove(currentMove - 1);
10296     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10297             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10298                 SetHighlights(fromX, fromY, toX, toY);
10299             }
10300     }
10301     DrawPosition(FALSE, boards[currentMove]);
10302     DisplayBothClocks();
10303     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10304 }
10305
10306 void
10307 SendEgtPath (ChessProgramState *cps)
10308 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10309         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10310
10311         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10312
10313         while(*p) {
10314             char c, *q = name+1, *r, *s;
10315
10316             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10317             while(*p && *p != ',') *q++ = *p++;
10318             *q++ = ':'; *q = 0;
10319             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10320                 strcmp(name, ",nalimov:") == 0 ) {
10321                 // take nalimov path from the menu-changeable option first, if it is defined
10322               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10323                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10324             } else
10325             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10326                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10327                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10328                 s = r = StrStr(s, ":") + 1; // beginning of path info
10329                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10330                 c = *r; *r = 0;             // temporarily null-terminate path info
10331                     *--q = 0;               // strip of trailig ':' from name
10332                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10333                 *r = c;
10334                 SendToProgram(buf,cps);     // send egtbpath command for this format
10335             }
10336             if(*p == ',') p++; // read away comma to position for next format name
10337         }
10338 }
10339
10340 static int
10341 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10342 {
10343       int width = 8, height = 8, holdings = 0;             // most common sizes
10344       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10345       // correct the deviations default for each variant
10346       if( v == VariantXiangqi ) width = 9,  height = 10;
10347       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10348       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10349       if( v == VariantCapablanca || v == VariantCapaRandom ||
10350           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10351                                 width = 10;
10352       if( v == VariantCourier ) width = 12;
10353       if( v == VariantSuper )                            holdings = 8;
10354       if( v == VariantGreat )   width = 10,              holdings = 8;
10355       if( v == VariantSChess )                           holdings = 7;
10356       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10357       if( v == VariantChuChess) width = 10, height = 10;
10358       if( v == VariantChu )     width = 12, height = 12;
10359       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10360              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10361              holdingsSize >= 0 && holdingsSize != holdings;
10362 }
10363
10364 char variantError[MSG_SIZ];
10365
10366 char *
10367 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10368 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10369       char *p, *variant = VariantName(v);
10370       static char b[MSG_SIZ];
10371       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10372            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10373                                                holdingsSize, variant); // cook up sized variant name
10374            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10375            if(StrStr(list, b) == NULL) {
10376                // specific sized variant not known, check if general sizing allowed
10377                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10378                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10379                             boardWidth, boardHeight, holdingsSize, engine);
10380                    return NULL;
10381                }
10382                /* [HGM] here we really should compare with the maximum supported board size */
10383            }
10384       } else snprintf(b, MSG_SIZ,"%s", variant);
10385       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10386       p = StrStr(list, b);
10387       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10388       if(p == NULL) {
10389           // occurs not at all in list, or only as sub-string
10390           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10391           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10392               int l = strlen(variantError);
10393               char *q;
10394               while(p != list && p[-1] != ',') p--;
10395               q = strchr(p, ',');
10396               if(q) *q = NULLCHAR;
10397               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10398               if(q) *q= ',';
10399           }
10400           return NULL;
10401       }
10402       return b;
10403 }
10404
10405 void
10406 InitChessProgram (ChessProgramState *cps, int setup)
10407 /* setup needed to setup FRC opening position */
10408 {
10409     char buf[MSG_SIZ], *b;
10410     if (appData.noChessProgram) return;
10411     hintRequested = FALSE;
10412     bookRequested = FALSE;
10413
10414     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10415     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10416     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10417     if(cps->memSize) { /* [HGM] memory */
10418       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10419         SendToProgram(buf, cps);
10420     }
10421     SendEgtPath(cps); /* [HGM] EGT */
10422     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10423       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10424         SendToProgram(buf, cps);
10425     }
10426
10427     SendToProgram(cps->initString, cps);
10428     if (gameInfo.variant != VariantNormal &&
10429         gameInfo.variant != VariantLoadable
10430         /* [HGM] also send variant if board size non-standard */
10431         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10432
10433       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10434                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10435       if (b == NULL) {
10436         DisplayFatalError(variantError, 0, 1);
10437         return;
10438       }
10439
10440       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10441       SendToProgram(buf, cps);
10442     }
10443     currentlyInitializedVariant = gameInfo.variant;
10444
10445     /* [HGM] send opening position in FRC to first engine */
10446     if(setup) {
10447           SendToProgram("force\n", cps);
10448           SendBoard(cps, 0);
10449           /* engine is now in force mode! Set flag to wake it up after first move. */
10450           setboardSpoiledMachineBlack = 1;
10451     }
10452
10453     if (cps->sendICS) {
10454       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10455       SendToProgram(buf, cps);
10456     }
10457     cps->maybeThinking = FALSE;
10458     cps->offeredDraw = 0;
10459     if (!appData.icsActive) {
10460         SendTimeControl(cps, movesPerSession, timeControl,
10461                         timeIncrement, appData.searchDepth,
10462                         searchTime);
10463     }
10464     if (appData.showThinking
10465         // [HGM] thinking: four options require thinking output to be sent
10466         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10467                                 ) {
10468         SendToProgram("post\n", cps);
10469     }
10470     SendToProgram("hard\n", cps);
10471     if (!appData.ponderNextMove) {
10472         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10473            it without being sure what state we are in first.  "hard"
10474            is not a toggle, so that one is OK.
10475          */
10476         SendToProgram("easy\n", cps);
10477     }
10478     if (cps->usePing) {
10479       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10480       SendToProgram(buf, cps);
10481     }
10482     cps->initDone = TRUE;
10483     ClearEngineOutputPane(cps == &second);
10484 }
10485
10486
10487 void
10488 ResendOptions (ChessProgramState *cps)
10489 { // send the stored value of the options
10490   int i;
10491   char buf[MSG_SIZ];
10492   Option *opt = cps->option;
10493   for(i=0; i<cps->nrOptions; i++, opt++) {
10494       switch(opt->type) {
10495         case Spin:
10496         case Slider:
10497         case CheckBox:
10498             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10499           break;
10500         case ComboBox:
10501           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10502           break;
10503         default:
10504             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10505           break;
10506         case Button:
10507         case SaveButton:
10508           continue;
10509       }
10510       SendToProgram(buf, cps);
10511   }
10512 }
10513
10514 void
10515 StartChessProgram (ChessProgramState *cps)
10516 {
10517     char buf[MSG_SIZ];
10518     int err;
10519
10520     if (appData.noChessProgram) return;
10521     cps->initDone = FALSE;
10522
10523     if (strcmp(cps->host, "localhost") == 0) {
10524         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10525     } else if (*appData.remoteShell == NULLCHAR) {
10526         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10527     } else {
10528         if (*appData.remoteUser == NULLCHAR) {
10529           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10530                     cps->program);
10531         } else {
10532           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10533                     cps->host, appData.remoteUser, cps->program);
10534         }
10535         err = StartChildProcess(buf, "", &cps->pr);
10536     }
10537
10538     if (err != 0) {
10539       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10540         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10541         if(cps != &first) return;
10542         appData.noChessProgram = TRUE;
10543         ThawUI();
10544         SetNCPMode();
10545 //      DisplayFatalError(buf, err, 1);
10546 //      cps->pr = NoProc;
10547 //      cps->isr = NULL;
10548         return;
10549     }
10550
10551     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10552     if (cps->protocolVersion > 1) {
10553       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10554       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10555         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10556         cps->comboCnt = 0;  //                and values of combo boxes
10557       }
10558       SendToProgram(buf, cps);
10559       if(cps->reload) ResendOptions(cps);
10560     } else {
10561       SendToProgram("xboard\n", cps);
10562     }
10563 }
10564
10565 void
10566 TwoMachinesEventIfReady P((void))
10567 {
10568   static int curMess = 0;
10569   if (first.lastPing != first.lastPong) {
10570     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10571     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10572     return;
10573   }
10574   if (second.lastPing != second.lastPong) {
10575     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10576     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10577     return;
10578   }
10579   DisplayMessage("", ""); curMess = 0;
10580   TwoMachinesEvent();
10581 }
10582
10583 char *
10584 MakeName (char *template)
10585 {
10586     time_t clock;
10587     struct tm *tm;
10588     static char buf[MSG_SIZ];
10589     char *p = buf;
10590     int i;
10591
10592     clock = time((time_t *)NULL);
10593     tm = localtime(&clock);
10594
10595     while(*p++ = *template++) if(p[-1] == '%') {
10596         switch(*template++) {
10597           case 0:   *p = 0; return buf;
10598           case 'Y': i = tm->tm_year+1900; break;
10599           case 'y': i = tm->tm_year-100; break;
10600           case 'M': i = tm->tm_mon+1; break;
10601           case 'd': i = tm->tm_mday; break;
10602           case 'h': i = tm->tm_hour; break;
10603           case 'm': i = tm->tm_min; break;
10604           case 's': i = tm->tm_sec; break;
10605           default:  i = 0;
10606         }
10607         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10608     }
10609     return buf;
10610 }
10611
10612 int
10613 CountPlayers (char *p)
10614 {
10615     int n = 0;
10616     while(p = strchr(p, '\n')) p++, n++; // count participants
10617     return n;
10618 }
10619
10620 FILE *
10621 WriteTourneyFile (char *results, FILE *f)
10622 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10623     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10624     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10625         // create a file with tournament description
10626         fprintf(f, "-participants {%s}\n", appData.participants);
10627         fprintf(f, "-seedBase %d\n", appData.seedBase);
10628         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10629         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10630         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10631         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10632         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10633         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10634         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10635         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10636         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10637         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10638         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10639         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10640         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10641         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10642         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10643         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10644         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10645         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10646         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10647         fprintf(f, "-smpCores %d\n", appData.smpCores);
10648         if(searchTime > 0)
10649                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10650         else {
10651                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10652                 fprintf(f, "-tc %s\n", appData.timeControl);
10653                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10654         }
10655         fprintf(f, "-results \"%s\"\n", results);
10656     }
10657     return f;
10658 }
10659
10660 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10661
10662 void
10663 Substitute (char *participants, int expunge)
10664 {
10665     int i, changed, changes=0, nPlayers=0;
10666     char *p, *q, *r, buf[MSG_SIZ];
10667     if(participants == NULL) return;
10668     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10669     r = p = participants; q = appData.participants;
10670     while(*p && *p == *q) {
10671         if(*p == '\n') r = p+1, nPlayers++;
10672         p++; q++;
10673     }
10674     if(*p) { // difference
10675         while(*p && *p++ != '\n');
10676         while(*q && *q++ != '\n');
10677       changed = nPlayers;
10678         changes = 1 + (strcmp(p, q) != 0);
10679     }
10680     if(changes == 1) { // a single engine mnemonic was changed
10681         q = r; while(*q) nPlayers += (*q++ == '\n');
10682         p = buf; while(*r && (*p = *r++) != '\n') p++;
10683         *p = NULLCHAR;
10684         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10685         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10686         if(mnemonic[i]) { // The substitute is valid
10687             FILE *f;
10688             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10689                 flock(fileno(f), LOCK_EX);
10690                 ParseArgsFromFile(f);
10691                 fseek(f, 0, SEEK_SET);
10692                 FREE(appData.participants); appData.participants = participants;
10693                 if(expunge) { // erase results of replaced engine
10694                     int len = strlen(appData.results), w, b, dummy;
10695                     for(i=0; i<len; i++) {
10696                         Pairing(i, nPlayers, &w, &b, &dummy);
10697                         if((w == changed || b == changed) && appData.results[i] == '*') {
10698                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10699                             fclose(f);
10700                             return;
10701                         }
10702                     }
10703                     for(i=0; i<len; i++) {
10704                         Pairing(i, nPlayers, &w, &b, &dummy);
10705                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10706                     }
10707                 }
10708                 WriteTourneyFile(appData.results, f);
10709                 fclose(f); // release lock
10710                 return;
10711             }
10712         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10713     }
10714     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10715     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10716     free(participants);
10717     return;
10718 }
10719
10720 int
10721 CheckPlayers (char *participants)
10722 {
10723         int i;
10724         char buf[MSG_SIZ], *p;
10725         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10726         while(p = strchr(participants, '\n')) {
10727             *p = NULLCHAR;
10728             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10729             if(!mnemonic[i]) {
10730                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10731                 *p = '\n';
10732                 DisplayError(buf, 0);
10733                 return 1;
10734             }
10735             *p = '\n';
10736             participants = p + 1;
10737         }
10738         return 0;
10739 }
10740
10741 int
10742 CreateTourney (char *name)
10743 {
10744         FILE *f;
10745         if(matchMode && strcmp(name, appData.tourneyFile)) {
10746              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10747         }
10748         if(name[0] == NULLCHAR) {
10749             if(appData.participants[0])
10750                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10751             return 0;
10752         }
10753         f = fopen(name, "r");
10754         if(f) { // file exists
10755             ASSIGN(appData.tourneyFile, name);
10756             ParseArgsFromFile(f); // parse it
10757         } else {
10758             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10759             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10760                 DisplayError(_("Not enough participants"), 0);
10761                 return 0;
10762             }
10763             if(CheckPlayers(appData.participants)) return 0;
10764             ASSIGN(appData.tourneyFile, name);
10765             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10766             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10767         }
10768         fclose(f);
10769         appData.noChessProgram = FALSE;
10770         appData.clockMode = TRUE;
10771         SetGNUMode();
10772         return 1;
10773 }
10774
10775 int
10776 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10777 {
10778     char buf[MSG_SIZ], *p, *q;
10779     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10780     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10781     skip = !all && group[0]; // if group requested, we start in skip mode
10782     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10783         p = names; q = buf; header = 0;
10784         while(*p && *p != '\n') *q++ = *p++;
10785         *q = 0;
10786         if(*p == '\n') p++;
10787         if(buf[0] == '#') {
10788             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10789             depth++; // we must be entering a new group
10790             if(all) continue; // suppress printing group headers when complete list requested
10791             header = 1;
10792             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10793         }
10794         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10795         if(engineList[i]) free(engineList[i]);
10796         engineList[i] = strdup(buf);
10797         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10798         if(engineMnemonic[i]) free(engineMnemonic[i]);
10799         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10800             strcat(buf, " (");
10801             sscanf(q + 8, "%s", buf + strlen(buf));
10802             strcat(buf, ")");
10803         }
10804         engineMnemonic[i] = strdup(buf);
10805         i++;
10806     }
10807     engineList[i] = engineMnemonic[i] = NULL;
10808     return i;
10809 }
10810
10811 // following implemented as macro to avoid type limitations
10812 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10813
10814 void
10815 SwapEngines (int n)
10816 {   // swap settings for first engine and other engine (so far only some selected options)
10817     int h;
10818     char *p;
10819     if(n == 0) return;
10820     SWAP(directory, p)
10821     SWAP(chessProgram, p)
10822     SWAP(isUCI, h)
10823     SWAP(hasOwnBookUCI, h)
10824     SWAP(protocolVersion, h)
10825     SWAP(reuse, h)
10826     SWAP(scoreIsAbsolute, h)
10827     SWAP(timeOdds, h)
10828     SWAP(logo, p)
10829     SWAP(pgnName, p)
10830     SWAP(pvSAN, h)
10831     SWAP(engOptions, p)
10832     SWAP(engInitString, p)
10833     SWAP(computerString, p)
10834     SWAP(features, p)
10835     SWAP(fenOverride, p)
10836     SWAP(NPS, h)
10837     SWAP(accumulateTC, h)
10838     SWAP(drawDepth, h)
10839     SWAP(host, p)
10840 }
10841
10842 int
10843 GetEngineLine (char *s, int n)
10844 {
10845     int i;
10846     char buf[MSG_SIZ];
10847     extern char *icsNames;
10848     if(!s || !*s) return 0;
10849     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10850     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10851     if(!mnemonic[i]) return 0;
10852     if(n == 11) return 1; // just testing if there was a match
10853     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10854     if(n == 1) SwapEngines(n);
10855     ParseArgsFromString(buf);
10856     if(n == 1) SwapEngines(n);
10857     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10858         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10859         ParseArgsFromString(buf);
10860     }
10861     return 1;
10862 }
10863
10864 int
10865 SetPlayer (int player, char *p)
10866 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10867     int i;
10868     char buf[MSG_SIZ], *engineName;
10869     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10870     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10871     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10872     if(mnemonic[i]) {
10873         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10874         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10875         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10876         ParseArgsFromString(buf);
10877     } else { // no engine with this nickname is installed!
10878         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10879         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10880         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10881         ModeHighlight();
10882         DisplayError(buf, 0);
10883         return 0;
10884     }
10885     free(engineName);
10886     return i;
10887 }
10888
10889 char *recentEngines;
10890
10891 void
10892 RecentEngineEvent (int nr)
10893 {
10894     int n;
10895 //    SwapEngines(1); // bump first to second
10896 //    ReplaceEngine(&second, 1); // and load it there
10897     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10898     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10899     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10900         ReplaceEngine(&first, 0);
10901         FloatToFront(&appData.recentEngineList, command[n]);
10902     }
10903 }
10904
10905 int
10906 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10907 {   // determine players from game number
10908     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10909
10910     if(appData.tourneyType == 0) {
10911         roundsPerCycle = (nPlayers - 1) | 1;
10912         pairingsPerRound = nPlayers / 2;
10913     } else if(appData.tourneyType > 0) {
10914         roundsPerCycle = nPlayers - appData.tourneyType;
10915         pairingsPerRound = appData.tourneyType;
10916     }
10917     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10918     gamesPerCycle = gamesPerRound * roundsPerCycle;
10919     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10920     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10921     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10922     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10923     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10924     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10925
10926     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10927     if(appData.roundSync) *syncInterval = gamesPerRound;
10928
10929     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10930
10931     if(appData.tourneyType == 0) {
10932         if(curPairing == (nPlayers-1)/2 ) {
10933             *whitePlayer = curRound;
10934             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10935         } else {
10936             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10937             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10938             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10939             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10940         }
10941     } else if(appData.tourneyType > 1) {
10942         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10943         *whitePlayer = curRound + appData.tourneyType;
10944     } else if(appData.tourneyType > 0) {
10945         *whitePlayer = curPairing;
10946         *blackPlayer = curRound + appData.tourneyType;
10947     }
10948
10949     // take care of white/black alternation per round.
10950     // For cycles and games this is already taken care of by default, derived from matchGame!
10951     return curRound & 1;
10952 }
10953
10954 int
10955 NextTourneyGame (int nr, int *swapColors)
10956 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10957     char *p, *q;
10958     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10959     FILE *tf;
10960     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10961     tf = fopen(appData.tourneyFile, "r");
10962     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10963     ParseArgsFromFile(tf); fclose(tf);
10964     InitTimeControls(); // TC might be altered from tourney file
10965
10966     nPlayers = CountPlayers(appData.participants); // count participants
10967     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10968     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10969
10970     if(syncInterval) {
10971         p = q = appData.results;
10972         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10973         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10974             DisplayMessage(_("Waiting for other game(s)"),"");
10975             waitingForGame = TRUE;
10976             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10977             return 0;
10978         }
10979         waitingForGame = FALSE;
10980     }
10981
10982     if(appData.tourneyType < 0) {
10983         if(nr>=0 && !pairingReceived) {
10984             char buf[1<<16];
10985             if(pairing.pr == NoProc) {
10986                 if(!appData.pairingEngine[0]) {
10987                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10988                     return 0;
10989                 }
10990                 StartChessProgram(&pairing); // starts the pairing engine
10991             }
10992             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10993             SendToProgram(buf, &pairing);
10994             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10995             SendToProgram(buf, &pairing);
10996             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10997         }
10998         pairingReceived = 0;                              // ... so we continue here
10999         *swapColors = 0;
11000         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11001         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11002         matchGame = 1; roundNr = nr / syncInterval + 1;
11003     }
11004
11005     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11006
11007     // redefine engines, engine dir, etc.
11008     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11009     if(first.pr == NoProc) {
11010       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11011       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11012     }
11013     if(second.pr == NoProc) {
11014       SwapEngines(1);
11015       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11016       SwapEngines(1);         // and make that valid for second engine by swapping
11017       InitEngine(&second, 1);
11018     }
11019     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11020     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11021     return OK;
11022 }
11023
11024 void
11025 NextMatchGame ()
11026 {   // performs game initialization that does not invoke engines, and then tries to start the game
11027     int res, firstWhite, swapColors = 0;
11028     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11029     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
11030         char buf[MSG_SIZ];
11031         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11032         if(strcmp(buf, currentDebugFile)) { // name has changed
11033             FILE *f = fopen(buf, "w");
11034             if(f) { // if opening the new file failed, just keep using the old one
11035                 ASSIGN(currentDebugFile, buf);
11036                 fclose(debugFP);
11037                 debugFP = f;
11038             }
11039             if(appData.serverFileName) {
11040                 if(serverFP) fclose(serverFP);
11041                 serverFP = fopen(appData.serverFileName, "w");
11042                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11043                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11044             }
11045         }
11046     }
11047     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11048     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11049     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11050     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11051     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11052     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11053     Reset(FALSE, first.pr != NoProc);
11054     res = LoadGameOrPosition(matchGame); // setup game
11055     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11056     if(!res) return; // abort when bad game/pos file
11057     TwoMachinesEvent();
11058 }
11059
11060 void
11061 UserAdjudicationEvent (int result)
11062 {
11063     ChessMove gameResult = GameIsDrawn;
11064
11065     if( result > 0 ) {
11066         gameResult = WhiteWins;
11067     }
11068     else if( result < 0 ) {
11069         gameResult = BlackWins;
11070     }
11071
11072     if( gameMode == TwoMachinesPlay ) {
11073         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11074     }
11075 }
11076
11077
11078 // [HGM] save: calculate checksum of game to make games easily identifiable
11079 int
11080 StringCheckSum (char *s)
11081 {
11082         int i = 0;
11083         if(s==NULL) return 0;
11084         while(*s) i = i*259 + *s++;
11085         return i;
11086 }
11087
11088 int
11089 GameCheckSum ()
11090 {
11091         int i, sum=0;
11092         for(i=backwardMostMove; i<forwardMostMove; i++) {
11093                 sum += pvInfoList[i].depth;
11094                 sum += StringCheckSum(parseList[i]);
11095                 sum += StringCheckSum(commentList[i]);
11096                 sum *= 261;
11097         }
11098         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11099         return sum + StringCheckSum(commentList[i]);
11100 } // end of save patch
11101
11102 void
11103 GameEnds (ChessMove result, char *resultDetails, int whosays)
11104 {
11105     GameMode nextGameMode;
11106     int isIcsGame;
11107     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11108
11109     if(endingGame) return; /* [HGM] crash: forbid recursion */
11110     endingGame = 1;
11111     if(twoBoards) { // [HGM] dual: switch back to one board
11112         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11113         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11114     }
11115     if (appData.debugMode) {
11116       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11117               result, resultDetails ? resultDetails : "(null)", whosays);
11118     }
11119
11120     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11121
11122     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11123
11124     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11125         /* If we are playing on ICS, the server decides when the
11126            game is over, but the engine can offer to draw, claim
11127            a draw, or resign.
11128          */
11129 #if ZIPPY
11130         if (appData.zippyPlay && first.initDone) {
11131             if (result == GameIsDrawn) {
11132                 /* In case draw still needs to be claimed */
11133                 SendToICS(ics_prefix);
11134                 SendToICS("draw\n");
11135             } else if (StrCaseStr(resultDetails, "resign")) {
11136                 SendToICS(ics_prefix);
11137                 SendToICS("resign\n");
11138             }
11139         }
11140 #endif
11141         endingGame = 0; /* [HGM] crash */
11142         return;
11143     }
11144
11145     /* If we're loading the game from a file, stop */
11146     if (whosays == GE_FILE) {
11147       (void) StopLoadGameTimer();
11148       gameFileFP = NULL;
11149     }
11150
11151     /* Cancel draw offers */
11152     first.offeredDraw = second.offeredDraw = 0;
11153
11154     /* If this is an ICS game, only ICS can really say it's done;
11155        if not, anyone can. */
11156     isIcsGame = (gameMode == IcsPlayingWhite ||
11157                  gameMode == IcsPlayingBlack ||
11158                  gameMode == IcsObserving    ||
11159                  gameMode == IcsExamining);
11160
11161     if (!isIcsGame || whosays == GE_ICS) {
11162         /* OK -- not an ICS game, or ICS said it was done */
11163         StopClocks();
11164         if (!isIcsGame && !appData.noChessProgram)
11165           SetUserThinkingEnables();
11166
11167         /* [HGM] if a machine claims the game end we verify this claim */
11168         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11169             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11170                 char claimer;
11171                 ChessMove trueResult = (ChessMove) -1;
11172
11173                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11174                                             first.twoMachinesColor[0] :
11175                                             second.twoMachinesColor[0] ;
11176
11177                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11178                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11179                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11180                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11181                 } else
11182                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11183                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11184                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11185                 } else
11186                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11187                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11188                 }
11189
11190                 // now verify win claims, but not in drop games, as we don't understand those yet
11191                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11192                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11193                     (result == WhiteWins && claimer == 'w' ||
11194                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11195                       if (appData.debugMode) {
11196                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11197                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11198                       }
11199                       if(result != trueResult) {
11200                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11201                               result = claimer == 'w' ? BlackWins : WhiteWins;
11202                               resultDetails = buf;
11203                       }
11204                 } else
11205                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11206                     && (forwardMostMove <= backwardMostMove ||
11207                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11208                         (claimer=='b')==(forwardMostMove&1))
11209                                                                                   ) {
11210                       /* [HGM] verify: draws that were not flagged are false claims */
11211                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11212                       result = claimer == 'w' ? BlackWins : WhiteWins;
11213                       resultDetails = buf;
11214                 }
11215                 /* (Claiming a loss is accepted no questions asked!) */
11216             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11217                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11218                 result = GameUnfinished;
11219                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11220             }
11221             /* [HGM] bare: don't allow bare King to win */
11222             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11223                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11224                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11225                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11226                && result != GameIsDrawn)
11227             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11228                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11229                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11230                         if(p >= 0 && p <= (int)WhiteKing) k++;
11231                 }
11232                 if (appData.debugMode) {
11233                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11234                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11235                 }
11236                 if(k <= 1) {
11237                         result = GameIsDrawn;
11238                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11239                         resultDetails = buf;
11240                 }
11241             }
11242         }
11243
11244
11245         if(serverMoves != NULL && !loadFlag) { char c = '=';
11246             if(result==WhiteWins) c = '+';
11247             if(result==BlackWins) c = '-';
11248             if(resultDetails != NULL)
11249                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11250         }
11251         if (resultDetails != NULL) {
11252             gameInfo.result = result;
11253             gameInfo.resultDetails = StrSave(resultDetails);
11254
11255             /* display last move only if game was not loaded from file */
11256             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11257                 DisplayMove(currentMove - 1);
11258
11259             if (forwardMostMove != 0) {
11260                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11261                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11262                                                                 ) {
11263                     if (*appData.saveGameFile != NULLCHAR) {
11264                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11265                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11266                         else
11267                         SaveGameToFile(appData.saveGameFile, TRUE);
11268                     } else if (appData.autoSaveGames) {
11269                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11270                     }
11271                     if (*appData.savePositionFile != NULLCHAR) {
11272                         SavePositionToFile(appData.savePositionFile);
11273                     }
11274                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11275                 }
11276             }
11277
11278             /* Tell program how game ended in case it is learning */
11279             /* [HGM] Moved this to after saving the PGN, just in case */
11280             /* engine died and we got here through time loss. In that */
11281             /* case we will get a fatal error writing the pipe, which */
11282             /* would otherwise lose us the PGN.                       */
11283             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11284             /* output during GameEnds should never be fatal anymore   */
11285             if (gameMode == MachinePlaysWhite ||
11286                 gameMode == MachinePlaysBlack ||
11287                 gameMode == TwoMachinesPlay ||
11288                 gameMode == IcsPlayingWhite ||
11289                 gameMode == IcsPlayingBlack ||
11290                 gameMode == BeginningOfGame) {
11291                 char buf[MSG_SIZ];
11292                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11293                         resultDetails);
11294                 if (first.pr != NoProc) {
11295                     SendToProgram(buf, &first);
11296                 }
11297                 if (second.pr != NoProc &&
11298                     gameMode == TwoMachinesPlay) {
11299                     SendToProgram(buf, &second);
11300                 }
11301             }
11302         }
11303
11304         if (appData.icsActive) {
11305             if (appData.quietPlay &&
11306                 (gameMode == IcsPlayingWhite ||
11307                  gameMode == IcsPlayingBlack)) {
11308                 SendToICS(ics_prefix);
11309                 SendToICS("set shout 1\n");
11310             }
11311             nextGameMode = IcsIdle;
11312             ics_user_moved = FALSE;
11313             /* clean up premove.  It's ugly when the game has ended and the
11314              * premove highlights are still on the board.
11315              */
11316             if (gotPremove) {
11317               gotPremove = FALSE;
11318               ClearPremoveHighlights();
11319               DrawPosition(FALSE, boards[currentMove]);
11320             }
11321             if (whosays == GE_ICS) {
11322                 switch (result) {
11323                 case WhiteWins:
11324                     if (gameMode == IcsPlayingWhite)
11325                         PlayIcsWinSound();
11326                     else if(gameMode == IcsPlayingBlack)
11327                         PlayIcsLossSound();
11328                     break;
11329                 case BlackWins:
11330                     if (gameMode == IcsPlayingBlack)
11331                         PlayIcsWinSound();
11332                     else if(gameMode == IcsPlayingWhite)
11333                         PlayIcsLossSound();
11334                     break;
11335                 case GameIsDrawn:
11336                     PlayIcsDrawSound();
11337                     break;
11338                 default:
11339                     PlayIcsUnfinishedSound();
11340                 }
11341             }
11342             if(appData.quitNext) { ExitEvent(0); return; }
11343         } else if (gameMode == EditGame ||
11344                    gameMode == PlayFromGameFile ||
11345                    gameMode == AnalyzeMode ||
11346                    gameMode == AnalyzeFile) {
11347             nextGameMode = gameMode;
11348         } else {
11349             nextGameMode = EndOfGame;
11350         }
11351         pausing = FALSE;
11352         ModeHighlight();
11353     } else {
11354         nextGameMode = gameMode;
11355     }
11356
11357     if (appData.noChessProgram) {
11358         gameMode = nextGameMode;
11359         ModeHighlight();
11360         endingGame = 0; /* [HGM] crash */
11361         return;
11362     }
11363
11364     if (first.reuse) {
11365         /* Put first chess program into idle state */
11366         if (first.pr != NoProc &&
11367             (gameMode == MachinePlaysWhite ||
11368              gameMode == MachinePlaysBlack ||
11369              gameMode == TwoMachinesPlay ||
11370              gameMode == IcsPlayingWhite ||
11371              gameMode == IcsPlayingBlack ||
11372              gameMode == BeginningOfGame)) {
11373             SendToProgram("force\n", &first);
11374             if (first.usePing) {
11375               char buf[MSG_SIZ];
11376               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11377               SendToProgram(buf, &first);
11378             }
11379         }
11380     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11381         /* Kill off first chess program */
11382         if (first.isr != NULL)
11383           RemoveInputSource(first.isr);
11384         first.isr = NULL;
11385
11386         if (first.pr != NoProc) {
11387             ExitAnalyzeMode();
11388             DoSleep( appData.delayBeforeQuit );
11389             SendToProgram("quit\n", &first);
11390             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11391             first.reload = TRUE;
11392         }
11393         first.pr = NoProc;
11394     }
11395     if (second.reuse) {
11396         /* Put second chess program into idle state */
11397         if (second.pr != NoProc &&
11398             gameMode == TwoMachinesPlay) {
11399             SendToProgram("force\n", &second);
11400             if (second.usePing) {
11401               char buf[MSG_SIZ];
11402               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11403               SendToProgram(buf, &second);
11404             }
11405         }
11406     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11407         /* Kill off second chess program */
11408         if (second.isr != NULL)
11409           RemoveInputSource(second.isr);
11410         second.isr = NULL;
11411
11412         if (second.pr != NoProc) {
11413             DoSleep( appData.delayBeforeQuit );
11414             SendToProgram("quit\n", &second);
11415             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11416             second.reload = TRUE;
11417         }
11418         second.pr = NoProc;
11419     }
11420
11421     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11422         char resChar = '=';
11423         switch (result) {
11424         case WhiteWins:
11425           resChar = '+';
11426           if (first.twoMachinesColor[0] == 'w') {
11427             first.matchWins++;
11428           } else {
11429             second.matchWins++;
11430           }
11431           break;
11432         case BlackWins:
11433           resChar = '-';
11434           if (first.twoMachinesColor[0] == 'b') {
11435             first.matchWins++;
11436           } else {
11437             second.matchWins++;
11438           }
11439           break;
11440         case GameUnfinished:
11441           resChar = ' ';
11442         default:
11443           break;
11444         }
11445
11446         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11447         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11448             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11449             ReserveGame(nextGame, resChar); // sets nextGame
11450             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11451             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11452         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11453
11454         if (nextGame <= appData.matchGames && !abortMatch) {
11455             gameMode = nextGameMode;
11456             matchGame = nextGame; // this will be overruled in tourney mode!
11457             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11458             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11459             endingGame = 0; /* [HGM] crash */
11460             return;
11461         } else {
11462             gameMode = nextGameMode;
11463             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11464                      first.tidy, second.tidy,
11465                      first.matchWins, second.matchWins,
11466                      appData.matchGames - (first.matchWins + second.matchWins));
11467             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11468             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11469             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11470             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11471                 first.twoMachinesColor = "black\n";
11472                 second.twoMachinesColor = "white\n";
11473             } else {
11474                 first.twoMachinesColor = "white\n";
11475                 second.twoMachinesColor = "black\n";
11476             }
11477         }
11478     }
11479     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11480         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11481       ExitAnalyzeMode();
11482     gameMode = nextGameMode;
11483     ModeHighlight();
11484     endingGame = 0;  /* [HGM] crash */
11485     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11486         if(matchMode == TRUE) { // match through command line: exit with or without popup
11487             if(ranking) {
11488                 ToNrEvent(forwardMostMove);
11489                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11490                 else ExitEvent(0);
11491             } else DisplayFatalError(buf, 0, 0);
11492         } else { // match through menu; just stop, with or without popup
11493             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11494             ModeHighlight();
11495             if(ranking){
11496                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11497             } else DisplayNote(buf);
11498       }
11499       if(ranking) free(ranking);
11500     }
11501 }
11502
11503 /* Assumes program was just initialized (initString sent).
11504    Leaves program in force mode. */
11505 void
11506 FeedMovesToProgram (ChessProgramState *cps, int upto)
11507 {
11508     int i;
11509
11510     if (appData.debugMode)
11511       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11512               startedFromSetupPosition ? "position and " : "",
11513               backwardMostMove, upto, cps->which);
11514     if(currentlyInitializedVariant != gameInfo.variant) {
11515       char buf[MSG_SIZ];
11516         // [HGM] variantswitch: make engine aware of new variant
11517         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11518                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11519                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11520         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11521         SendToProgram(buf, cps);
11522         currentlyInitializedVariant = gameInfo.variant;
11523     }
11524     SendToProgram("force\n", cps);
11525     if (startedFromSetupPosition) {
11526         SendBoard(cps, backwardMostMove);
11527     if (appData.debugMode) {
11528         fprintf(debugFP, "feedMoves\n");
11529     }
11530     }
11531     for (i = backwardMostMove; i < upto; i++) {
11532         SendMoveToProgram(i, cps);
11533     }
11534 }
11535
11536
11537 int
11538 ResurrectChessProgram ()
11539 {
11540      /* The chess program may have exited.
11541         If so, restart it and feed it all the moves made so far. */
11542     static int doInit = 0;
11543
11544     if (appData.noChessProgram) return 1;
11545
11546     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11547         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11548         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11549         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11550     } else {
11551         if (first.pr != NoProc) return 1;
11552         StartChessProgram(&first);
11553     }
11554     InitChessProgram(&first, FALSE);
11555     FeedMovesToProgram(&first, currentMove);
11556
11557     if (!first.sendTime) {
11558         /* can't tell gnuchess what its clock should read,
11559            so we bow to its notion. */
11560         ResetClocks();
11561         timeRemaining[0][currentMove] = whiteTimeRemaining;
11562         timeRemaining[1][currentMove] = blackTimeRemaining;
11563     }
11564
11565     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11566                 appData.icsEngineAnalyze) && first.analysisSupport) {
11567       SendToProgram("analyze\n", &first);
11568       first.analyzing = TRUE;
11569     }
11570     return 1;
11571 }
11572
11573 /*
11574  * Button procedures
11575  */
11576 void
11577 Reset (int redraw, int init)
11578 {
11579     int i;
11580
11581     if (appData.debugMode) {
11582         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11583                 redraw, init, gameMode);
11584     }
11585     CleanupTail(); // [HGM] vari: delete any stored variations
11586     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11587     pausing = pauseExamInvalid = FALSE;
11588     startedFromSetupPosition = blackPlaysFirst = FALSE;
11589     firstMove = TRUE;
11590     whiteFlag = blackFlag = FALSE;
11591     userOfferedDraw = FALSE;
11592     hintRequested = bookRequested = FALSE;
11593     first.maybeThinking = FALSE;
11594     second.maybeThinking = FALSE;
11595     first.bookSuspend = FALSE; // [HGM] book
11596     second.bookSuspend = FALSE;
11597     thinkOutput[0] = NULLCHAR;
11598     lastHint[0] = NULLCHAR;
11599     ClearGameInfo(&gameInfo);
11600     gameInfo.variant = StringToVariant(appData.variant);
11601     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11602     ics_user_moved = ics_clock_paused = FALSE;
11603     ics_getting_history = H_FALSE;
11604     ics_gamenum = -1;
11605     white_holding[0] = black_holding[0] = NULLCHAR;
11606     ClearProgramStats();
11607     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11608
11609     ResetFrontEnd();
11610     ClearHighlights();
11611     flipView = appData.flipView;
11612     ClearPremoveHighlights();
11613     gotPremove = FALSE;
11614     alarmSounded = FALSE;
11615     killX = killY = -1; // [HGM] lion
11616
11617     GameEnds(EndOfFile, NULL, GE_PLAYER);
11618     if(appData.serverMovesName != NULL) {
11619         /* [HGM] prepare to make moves file for broadcasting */
11620         clock_t t = clock();
11621         if(serverMoves != NULL) fclose(serverMoves);
11622         serverMoves = fopen(appData.serverMovesName, "r");
11623         if(serverMoves != NULL) {
11624             fclose(serverMoves);
11625             /* delay 15 sec before overwriting, so all clients can see end */
11626             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11627         }
11628         serverMoves = fopen(appData.serverMovesName, "w");
11629     }
11630
11631     ExitAnalyzeMode();
11632     gameMode = BeginningOfGame;
11633     ModeHighlight();
11634     if(appData.icsActive) gameInfo.variant = VariantNormal;
11635     currentMove = forwardMostMove = backwardMostMove = 0;
11636     MarkTargetSquares(1);
11637     InitPosition(redraw);
11638     for (i = 0; i < MAX_MOVES; i++) {
11639         if (commentList[i] != NULL) {
11640             free(commentList[i]);
11641             commentList[i] = NULL;
11642         }
11643     }
11644     ResetClocks();
11645     timeRemaining[0][0] = whiteTimeRemaining;
11646     timeRemaining[1][0] = blackTimeRemaining;
11647
11648     if (first.pr == NoProc) {
11649         StartChessProgram(&first);
11650     }
11651     if (init) {
11652             InitChessProgram(&first, startedFromSetupPosition);
11653     }
11654     DisplayTitle("");
11655     DisplayMessage("", "");
11656     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11657     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11658     ClearMap();        // [HGM] exclude: invalidate map
11659 }
11660
11661 void
11662 AutoPlayGameLoop ()
11663 {
11664     for (;;) {
11665         if (!AutoPlayOneMove())
11666           return;
11667         if (matchMode || appData.timeDelay == 0)
11668           continue;
11669         if (appData.timeDelay < 0)
11670           return;
11671         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11672         break;
11673     }
11674 }
11675
11676 void
11677 AnalyzeNextGame()
11678 {
11679     ReloadGame(1); // next game
11680 }
11681
11682 int
11683 AutoPlayOneMove ()
11684 {
11685     int fromX, fromY, toX, toY;
11686
11687     if (appData.debugMode) {
11688       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11689     }
11690
11691     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11692       return FALSE;
11693
11694     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11695       pvInfoList[currentMove].depth = programStats.depth;
11696       pvInfoList[currentMove].score = programStats.score;
11697       pvInfoList[currentMove].time  = 0;
11698       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11699       else { // append analysis of final position as comment
11700         char buf[MSG_SIZ];
11701         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11702         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11703       }
11704       programStats.depth = 0;
11705     }
11706
11707     if (currentMove >= forwardMostMove) {
11708       if(gameMode == AnalyzeFile) {
11709           if(appData.loadGameIndex == -1) {
11710             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11711           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11712           } else {
11713           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11714         }
11715       }
11716 //      gameMode = EndOfGame;
11717 //      ModeHighlight();
11718
11719       /* [AS] Clear current move marker at the end of a game */
11720       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11721
11722       return FALSE;
11723     }
11724
11725     toX = moveList[currentMove][2] - AAA;
11726     toY = moveList[currentMove][3] - ONE;
11727
11728     if (moveList[currentMove][1] == '@') {
11729         if (appData.highlightLastMove) {
11730             SetHighlights(-1, -1, toX, toY);
11731         }
11732     } else {
11733         fromX = moveList[currentMove][0] - AAA;
11734         fromY = moveList[currentMove][1] - ONE;
11735
11736         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11737
11738         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11739
11740         if (appData.highlightLastMove) {
11741             SetHighlights(fromX, fromY, toX, toY);
11742         }
11743     }
11744     DisplayMove(currentMove);
11745     SendMoveToProgram(currentMove++, &first);
11746     DisplayBothClocks();
11747     DrawPosition(FALSE, boards[currentMove]);
11748     // [HGM] PV info: always display, routine tests if empty
11749     DisplayComment(currentMove - 1, commentList[currentMove]);
11750     return TRUE;
11751 }
11752
11753
11754 int
11755 LoadGameOneMove (ChessMove readAhead)
11756 {
11757     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11758     char promoChar = NULLCHAR;
11759     ChessMove moveType;
11760     char move[MSG_SIZ];
11761     char *p, *q;
11762
11763     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11764         gameMode != AnalyzeMode && gameMode != Training) {
11765         gameFileFP = NULL;
11766         return FALSE;
11767     }
11768
11769     yyboardindex = forwardMostMove;
11770     if (readAhead != EndOfFile) {
11771       moveType = readAhead;
11772     } else {
11773       if (gameFileFP == NULL)
11774           return FALSE;
11775       moveType = (ChessMove) Myylex();
11776     }
11777
11778     done = FALSE;
11779     switch (moveType) {
11780       case Comment:
11781         if (appData.debugMode)
11782           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11783         p = yy_text;
11784
11785         /* append the comment but don't display it */
11786         AppendComment(currentMove, p, FALSE);
11787         return TRUE;
11788
11789       case WhiteCapturesEnPassant:
11790       case BlackCapturesEnPassant:
11791       case WhitePromotion:
11792       case BlackPromotion:
11793       case WhiteNonPromotion:
11794       case BlackNonPromotion:
11795       case NormalMove:
11796       case FirstLeg:
11797       case WhiteKingSideCastle:
11798       case WhiteQueenSideCastle:
11799       case BlackKingSideCastle:
11800       case BlackQueenSideCastle:
11801       case WhiteKingSideCastleWild:
11802       case WhiteQueenSideCastleWild:
11803       case BlackKingSideCastleWild:
11804       case BlackQueenSideCastleWild:
11805       /* PUSH Fabien */
11806       case WhiteHSideCastleFR:
11807       case WhiteASideCastleFR:
11808       case BlackHSideCastleFR:
11809       case BlackASideCastleFR:
11810       /* POP Fabien */
11811         if (appData.debugMode)
11812           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11813         fromX = currentMoveString[0] - AAA;
11814         fromY = currentMoveString[1] - ONE;
11815         toX = currentMoveString[2] - AAA;
11816         toY = currentMoveString[3] - ONE;
11817         promoChar = currentMoveString[4];
11818         if(promoChar == ';') promoChar = NULLCHAR;
11819         break;
11820
11821       case WhiteDrop:
11822       case BlackDrop:
11823         if (appData.debugMode)
11824           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11825         fromX = moveType == WhiteDrop ?
11826           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11827         (int) CharToPiece(ToLower(currentMoveString[0]));
11828         fromY = DROP_RANK;
11829         toX = currentMoveString[2] - AAA;
11830         toY = currentMoveString[3] - ONE;
11831         break;
11832
11833       case WhiteWins:
11834       case BlackWins:
11835       case GameIsDrawn:
11836       case GameUnfinished:
11837         if (appData.debugMode)
11838           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11839         p = strchr(yy_text, '{');
11840         if (p == NULL) p = strchr(yy_text, '(');
11841         if (p == NULL) {
11842             p = yy_text;
11843             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11844         } else {
11845             q = strchr(p, *p == '{' ? '}' : ')');
11846             if (q != NULL) *q = NULLCHAR;
11847             p++;
11848         }
11849         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11850         GameEnds(moveType, p, GE_FILE);
11851         done = TRUE;
11852         if (cmailMsgLoaded) {
11853             ClearHighlights();
11854             flipView = WhiteOnMove(currentMove);
11855             if (moveType == GameUnfinished) flipView = !flipView;
11856             if (appData.debugMode)
11857               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11858         }
11859         break;
11860
11861       case EndOfFile:
11862         if (appData.debugMode)
11863           fprintf(debugFP, "Parser hit end of file\n");
11864         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11865           case MT_NONE:
11866           case MT_CHECK:
11867             break;
11868           case MT_CHECKMATE:
11869           case MT_STAINMATE:
11870             if (WhiteOnMove(currentMove)) {
11871                 GameEnds(BlackWins, "Black mates", GE_FILE);
11872             } else {
11873                 GameEnds(WhiteWins, "White mates", GE_FILE);
11874             }
11875             break;
11876           case MT_STALEMATE:
11877             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11878             break;
11879         }
11880         done = TRUE;
11881         break;
11882
11883       case MoveNumberOne:
11884         if (lastLoadGameStart == GNUChessGame) {
11885             /* GNUChessGames have numbers, but they aren't move numbers */
11886             if (appData.debugMode)
11887               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11888                       yy_text, (int) moveType);
11889             return LoadGameOneMove(EndOfFile); /* tail recursion */
11890         }
11891         /* else fall thru */
11892
11893       case XBoardGame:
11894       case GNUChessGame:
11895       case PGNTag:
11896         /* Reached start of next game in file */
11897         if (appData.debugMode)
11898           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11899         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11900           case MT_NONE:
11901           case MT_CHECK:
11902             break;
11903           case MT_CHECKMATE:
11904           case MT_STAINMATE:
11905             if (WhiteOnMove(currentMove)) {
11906                 GameEnds(BlackWins, "Black mates", GE_FILE);
11907             } else {
11908                 GameEnds(WhiteWins, "White mates", GE_FILE);
11909             }
11910             break;
11911           case MT_STALEMATE:
11912             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11913             break;
11914         }
11915         done = TRUE;
11916         break;
11917
11918       case PositionDiagram:     /* should not happen; ignore */
11919       case ElapsedTime:         /* ignore */
11920       case NAG:                 /* ignore */
11921         if (appData.debugMode)
11922           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11923                   yy_text, (int) moveType);
11924         return LoadGameOneMove(EndOfFile); /* tail recursion */
11925
11926       case IllegalMove:
11927         if (appData.testLegality) {
11928             if (appData.debugMode)
11929               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11930             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11931                     (forwardMostMove / 2) + 1,
11932                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11933             DisplayError(move, 0);
11934             done = TRUE;
11935         } else {
11936             if (appData.debugMode)
11937               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11938                       yy_text, currentMoveString);
11939             fromX = currentMoveString[0] - AAA;
11940             fromY = currentMoveString[1] - ONE;
11941             toX = currentMoveString[2] - AAA;
11942             toY = currentMoveString[3] - ONE;
11943             promoChar = currentMoveString[4];
11944         }
11945         break;
11946
11947       case AmbiguousMove:
11948         if (appData.debugMode)
11949           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11950         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11951                 (forwardMostMove / 2) + 1,
11952                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11953         DisplayError(move, 0);
11954         done = TRUE;
11955         break;
11956
11957       default:
11958       case ImpossibleMove:
11959         if (appData.debugMode)
11960           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11961         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11962                 (forwardMostMove / 2) + 1,
11963                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11964         DisplayError(move, 0);
11965         done = TRUE;
11966         break;
11967     }
11968
11969     if (done) {
11970         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11971             DrawPosition(FALSE, boards[currentMove]);
11972             DisplayBothClocks();
11973             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11974               DisplayComment(currentMove - 1, commentList[currentMove]);
11975         }
11976         (void) StopLoadGameTimer();
11977         gameFileFP = NULL;
11978         cmailOldMove = forwardMostMove;
11979         return FALSE;
11980     } else {
11981         /* currentMoveString is set as a side-effect of yylex */
11982
11983         thinkOutput[0] = NULLCHAR;
11984         MakeMove(fromX, fromY, toX, toY, promoChar);
11985         killX = killY = -1; // [HGM] lion: used up
11986         currentMove = forwardMostMove;
11987         return TRUE;
11988     }
11989 }
11990
11991 /* Load the nth game from the given file */
11992 int
11993 LoadGameFromFile (char *filename, int n, char *title, int useList)
11994 {
11995     FILE *f;
11996     char buf[MSG_SIZ];
11997
11998     if (strcmp(filename, "-") == 0) {
11999         f = stdin;
12000         title = "stdin";
12001     } else {
12002         f = fopen(filename, "rb");
12003         if (f == NULL) {
12004           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12005             DisplayError(buf, errno);
12006             return FALSE;
12007         }
12008     }
12009     if (fseek(f, 0, 0) == -1) {
12010         /* f is not seekable; probably a pipe */
12011         useList = FALSE;
12012     }
12013     if (useList && n == 0) {
12014         int error = GameListBuild(f);
12015         if (error) {
12016             DisplayError(_("Cannot build game list"), error);
12017         } else if (!ListEmpty(&gameList) &&
12018                    ((ListGame *) gameList.tailPred)->number > 1) {
12019             GameListPopUp(f, title);
12020             return TRUE;
12021         }
12022         GameListDestroy();
12023         n = 1;
12024     }
12025     if (n == 0) n = 1;
12026     return LoadGame(f, n, title, FALSE);
12027 }
12028
12029
12030 void
12031 MakeRegisteredMove ()
12032 {
12033     int fromX, fromY, toX, toY;
12034     char promoChar;
12035     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12036         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12037           case CMAIL_MOVE:
12038           case CMAIL_DRAW:
12039             if (appData.debugMode)
12040               fprintf(debugFP, "Restoring %s for game %d\n",
12041                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12042
12043             thinkOutput[0] = NULLCHAR;
12044             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12045             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12046             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12047             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12048             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12049             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12050             MakeMove(fromX, fromY, toX, toY, promoChar);
12051             ShowMove(fromX, fromY, toX, toY);
12052
12053             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12054               case MT_NONE:
12055               case MT_CHECK:
12056                 break;
12057
12058               case MT_CHECKMATE:
12059               case MT_STAINMATE:
12060                 if (WhiteOnMove(currentMove)) {
12061                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12062                 } else {
12063                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12064                 }
12065                 break;
12066
12067               case MT_STALEMATE:
12068                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12069                 break;
12070             }
12071
12072             break;
12073
12074           case CMAIL_RESIGN:
12075             if (WhiteOnMove(currentMove)) {
12076                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12077             } else {
12078                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12079             }
12080             break;
12081
12082           case CMAIL_ACCEPT:
12083             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12084             break;
12085
12086           default:
12087             break;
12088         }
12089     }
12090
12091     return;
12092 }
12093
12094 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12095 int
12096 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12097 {
12098     int retVal;
12099
12100     if (gameNumber > nCmailGames) {
12101         DisplayError(_("No more games in this message"), 0);
12102         return FALSE;
12103     }
12104     if (f == lastLoadGameFP) {
12105         int offset = gameNumber - lastLoadGameNumber;
12106         if (offset == 0) {
12107             cmailMsg[0] = NULLCHAR;
12108             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12109                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12110                 nCmailMovesRegistered--;
12111             }
12112             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12113             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12114                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12115             }
12116         } else {
12117             if (! RegisterMove()) return FALSE;
12118         }
12119     }
12120
12121     retVal = LoadGame(f, gameNumber, title, useList);
12122
12123     /* Make move registered during previous look at this game, if any */
12124     MakeRegisteredMove();
12125
12126     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12127         commentList[currentMove]
12128           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12129         DisplayComment(currentMove - 1, commentList[currentMove]);
12130     }
12131
12132     return retVal;
12133 }
12134
12135 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12136 int
12137 ReloadGame (int offset)
12138 {
12139     int gameNumber = lastLoadGameNumber + offset;
12140     if (lastLoadGameFP == NULL) {
12141         DisplayError(_("No game has been loaded yet"), 0);
12142         return FALSE;
12143     }
12144     if (gameNumber <= 0) {
12145         DisplayError(_("Can't back up any further"), 0);
12146         return FALSE;
12147     }
12148     if (cmailMsgLoaded) {
12149         return CmailLoadGame(lastLoadGameFP, gameNumber,
12150                              lastLoadGameTitle, lastLoadGameUseList);
12151     } else {
12152         return LoadGame(lastLoadGameFP, gameNumber,
12153                         lastLoadGameTitle, lastLoadGameUseList);
12154     }
12155 }
12156
12157 int keys[EmptySquare+1];
12158
12159 int
12160 PositionMatches (Board b1, Board b2)
12161 {
12162     int r, f, sum=0;
12163     switch(appData.searchMode) {
12164         case 1: return CompareWithRights(b1, b2);
12165         case 2:
12166             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12167                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12168             }
12169             return TRUE;
12170         case 3:
12171             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12172               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12173                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12174             }
12175             return sum==0;
12176         case 4:
12177             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12178                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12179             }
12180             return sum==0;
12181     }
12182     return TRUE;
12183 }
12184
12185 #define Q_PROMO  4
12186 #define Q_EP     3
12187 #define Q_BCASTL 2
12188 #define Q_WCASTL 1
12189
12190 int pieceList[256], quickBoard[256];
12191 ChessSquare pieceType[256] = { EmptySquare };
12192 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12193 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12194 int soughtTotal, turn;
12195 Boolean epOK, flipSearch;
12196
12197 typedef struct {
12198     unsigned char piece, to;
12199 } Move;
12200
12201 #define DSIZE (250000)
12202
12203 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12204 Move *moveDatabase = initialSpace;
12205 unsigned int movePtr, dataSize = DSIZE;
12206
12207 int
12208 MakePieceList (Board board, int *counts)
12209 {
12210     int r, f, n=Q_PROMO, total=0;
12211     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12212     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12213         int sq = f + (r<<4);
12214         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12215             quickBoard[sq] = ++n;
12216             pieceList[n] = sq;
12217             pieceType[n] = board[r][f];
12218             counts[board[r][f]]++;
12219             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12220             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12221             total++;
12222         }
12223     }
12224     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12225     return total;
12226 }
12227
12228 void
12229 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12230 {
12231     int sq = fromX + (fromY<<4);
12232     int piece = quickBoard[sq], rook;
12233     quickBoard[sq] = 0;
12234     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12235     if(piece == pieceList[1] && fromY == toY) {
12236       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12237         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12238         moveDatabase[movePtr++].piece = Q_WCASTL;
12239         quickBoard[sq] = piece;
12240         piece = quickBoard[from]; quickBoard[from] = 0;
12241         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12242       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12243         quickBoard[sq] = 0; // remove Rook
12244         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12245         moveDatabase[movePtr++].piece = Q_WCASTL;
12246         quickBoard[sq] = pieceList[1]; // put King
12247         piece = rook;
12248         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12249       }
12250     } else
12251     if(piece == pieceList[2] && fromY == toY) {
12252       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12253         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12254         moveDatabase[movePtr++].piece = Q_BCASTL;
12255         quickBoard[sq] = piece;
12256         piece = quickBoard[from]; quickBoard[from] = 0;
12257         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12258       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12259         quickBoard[sq] = 0; // remove Rook
12260         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12261         moveDatabase[movePtr++].piece = Q_BCASTL;
12262         quickBoard[sq] = pieceList[2]; // put King
12263         piece = rook;
12264         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12265       }
12266     } else
12267     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12268         quickBoard[(fromY<<4)+toX] = 0;
12269         moveDatabase[movePtr].piece = Q_EP;
12270         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12271         moveDatabase[movePtr].to = sq;
12272     } else
12273     if(promoPiece != pieceType[piece]) {
12274         moveDatabase[movePtr++].piece = Q_PROMO;
12275         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12276     }
12277     moveDatabase[movePtr].piece = piece;
12278     quickBoard[sq] = piece;
12279     movePtr++;
12280 }
12281
12282 int
12283 PackGame (Board board)
12284 {
12285     Move *newSpace = NULL;
12286     moveDatabase[movePtr].piece = 0; // terminate previous game
12287     if(movePtr > dataSize) {
12288         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12289         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12290         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12291         if(newSpace) {
12292             int i;
12293             Move *p = moveDatabase, *q = newSpace;
12294             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12295             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12296             moveDatabase = newSpace;
12297         } else { // calloc failed, we must be out of memory. Too bad...
12298             dataSize = 0; // prevent calloc events for all subsequent games
12299             return 0;     // and signal this one isn't cached
12300         }
12301     }
12302     movePtr++;
12303     MakePieceList(board, counts);
12304     return movePtr;
12305 }
12306
12307 int
12308 QuickCompare (Board board, int *minCounts, int *maxCounts)
12309 {   // compare according to search mode
12310     int r, f;
12311     switch(appData.searchMode)
12312     {
12313       case 1: // exact position match
12314         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12315         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12316             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12317         }
12318         break;
12319       case 2: // can have extra material on empty squares
12320         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12321             if(board[r][f] == EmptySquare) continue;
12322             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12323         }
12324         break;
12325       case 3: // material with exact Pawn structure
12326         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12327             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12328             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12329         } // fall through to material comparison
12330       case 4: // exact material
12331         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12332         break;
12333       case 6: // material range with given imbalance
12334         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12335         // fall through to range comparison
12336       case 5: // material range
12337         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12338     }
12339     return TRUE;
12340 }
12341
12342 int
12343 QuickScan (Board board, Move *move)
12344 {   // reconstruct game,and compare all positions in it
12345     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12346     do {
12347         int piece = move->piece;
12348         int to = move->to, from = pieceList[piece];
12349         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12350           if(!piece) return -1;
12351           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12352             piece = (++move)->piece;
12353             from = pieceList[piece];
12354             counts[pieceType[piece]]--;
12355             pieceType[piece] = (ChessSquare) move->to;
12356             counts[move->to]++;
12357           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12358             counts[pieceType[quickBoard[to]]]--;
12359             quickBoard[to] = 0; total--;
12360             move++;
12361             continue;
12362           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12363             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12364             from  = pieceList[piece]; // so this must be King
12365             quickBoard[from] = 0;
12366             pieceList[piece] = to;
12367             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12368             quickBoard[from] = 0; // rook
12369             quickBoard[to] = piece;
12370             to = move->to; piece = move->piece;
12371             goto aftercastle;
12372           }
12373         }
12374         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12375         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12376         quickBoard[from] = 0;
12377       aftercastle:
12378         quickBoard[to] = piece;
12379         pieceList[piece] = to;
12380         cnt++; turn ^= 3;
12381         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12382            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12383            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12384                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12385           ) {
12386             static int lastCounts[EmptySquare+1];
12387             int i;
12388             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12389             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12390         } else stretch = 0;
12391         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12392         move++;
12393     } while(1);
12394 }
12395
12396 void
12397 InitSearch ()
12398 {
12399     int r, f;
12400     flipSearch = FALSE;
12401     CopyBoard(soughtBoard, boards[currentMove]);
12402     soughtTotal = MakePieceList(soughtBoard, maxSought);
12403     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12404     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12405     CopyBoard(reverseBoard, boards[currentMove]);
12406     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12407         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12408         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12409         reverseBoard[r][f] = piece;
12410     }
12411     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12412     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12413     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12414                  || (boards[currentMove][CASTLING][2] == NoRights ||
12415                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12416                  && (boards[currentMove][CASTLING][5] == NoRights ||
12417                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12418       ) {
12419         flipSearch = TRUE;
12420         CopyBoard(flipBoard, soughtBoard);
12421         CopyBoard(rotateBoard, reverseBoard);
12422         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12423             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12424             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12425         }
12426     }
12427     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12428     if(appData.searchMode >= 5) {
12429         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12430         MakePieceList(soughtBoard, minSought);
12431         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12432     }
12433     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12434         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12435 }
12436
12437 GameInfo dummyInfo;
12438 static int creatingBook;
12439
12440 int
12441 GameContainsPosition (FILE *f, ListGame *lg)
12442 {
12443     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12444     int fromX, fromY, toX, toY;
12445     char promoChar;
12446     static int initDone=FALSE;
12447
12448     // weed out games based on numerical tag comparison
12449     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12450     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12451     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12452     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12453     if(!initDone) {
12454         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12455         initDone = TRUE;
12456     }
12457     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12458     else CopyBoard(boards[scratch], initialPosition); // default start position
12459     if(lg->moves) {
12460         turn = btm + 1;
12461         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12462         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12463     }
12464     if(btm) plyNr++;
12465     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12466     fseek(f, lg->offset, 0);
12467     yynewfile(f);
12468     while(1) {
12469         yyboardindex = scratch;
12470         quickFlag = plyNr+1;
12471         next = Myylex();
12472         quickFlag = 0;
12473         switch(next) {
12474             case PGNTag:
12475                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12476             default:
12477                 continue;
12478
12479             case XBoardGame:
12480             case GNUChessGame:
12481                 if(plyNr) return -1; // after we have seen moves, this is for new game
12482               continue;
12483
12484             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12485             case ImpossibleMove:
12486             case WhiteWins: // game ends here with these four
12487             case BlackWins:
12488             case GameIsDrawn:
12489             case GameUnfinished:
12490                 return -1;
12491
12492             case IllegalMove:
12493                 if(appData.testLegality) return -1;
12494             case WhiteCapturesEnPassant:
12495             case BlackCapturesEnPassant:
12496             case WhitePromotion:
12497             case BlackPromotion:
12498             case WhiteNonPromotion:
12499             case BlackNonPromotion:
12500             case NormalMove:
12501             case FirstLeg:
12502             case WhiteKingSideCastle:
12503             case WhiteQueenSideCastle:
12504             case BlackKingSideCastle:
12505             case BlackQueenSideCastle:
12506             case WhiteKingSideCastleWild:
12507             case WhiteQueenSideCastleWild:
12508             case BlackKingSideCastleWild:
12509             case BlackQueenSideCastleWild:
12510             case WhiteHSideCastleFR:
12511             case WhiteASideCastleFR:
12512             case BlackHSideCastleFR:
12513             case BlackASideCastleFR:
12514                 fromX = currentMoveString[0] - AAA;
12515                 fromY = currentMoveString[1] - ONE;
12516                 toX = currentMoveString[2] - AAA;
12517                 toY = currentMoveString[3] - ONE;
12518                 promoChar = currentMoveString[4];
12519                 break;
12520             case WhiteDrop:
12521             case BlackDrop:
12522                 fromX = next == WhiteDrop ?
12523                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12524                   (int) CharToPiece(ToLower(currentMoveString[0]));
12525                 fromY = DROP_RANK;
12526                 toX = currentMoveString[2] - AAA;
12527                 toY = currentMoveString[3] - ONE;
12528                 promoChar = 0;
12529                 break;
12530         }
12531         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12532         plyNr++;
12533         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12534         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12535         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12536         if(appData.findMirror) {
12537             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12538             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12539         }
12540     }
12541 }
12542
12543 /* Load the nth game from open file f */
12544 int
12545 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12546 {
12547     ChessMove cm;
12548     char buf[MSG_SIZ];
12549     int gn = gameNumber;
12550     ListGame *lg = NULL;
12551     int numPGNTags = 0;
12552     int err, pos = -1;
12553     GameMode oldGameMode;
12554     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12555
12556     if (appData.debugMode)
12557         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12558
12559     if (gameMode == Training )
12560         SetTrainingModeOff();
12561
12562     oldGameMode = gameMode;
12563     if (gameMode != BeginningOfGame) {
12564       Reset(FALSE, TRUE);
12565     }
12566     killX = killY = -1; // [HGM] lion: in case we did not Reset
12567
12568     gameFileFP = f;
12569     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12570         fclose(lastLoadGameFP);
12571     }
12572
12573     if (useList) {
12574         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12575
12576         if (lg) {
12577             fseek(f, lg->offset, 0);
12578             GameListHighlight(gameNumber);
12579             pos = lg->position;
12580             gn = 1;
12581         }
12582         else {
12583             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12584               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12585             else
12586             DisplayError(_("Game number out of range"), 0);
12587             return FALSE;
12588         }
12589     } else {
12590         GameListDestroy();
12591         if (fseek(f, 0, 0) == -1) {
12592             if (f == lastLoadGameFP ?
12593                 gameNumber == lastLoadGameNumber + 1 :
12594                 gameNumber == 1) {
12595                 gn = 1;
12596             } else {
12597                 DisplayError(_("Can't seek on game file"), 0);
12598                 return FALSE;
12599             }
12600         }
12601     }
12602     lastLoadGameFP = f;
12603     lastLoadGameNumber = gameNumber;
12604     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12605     lastLoadGameUseList = useList;
12606
12607     yynewfile(f);
12608
12609     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12610       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12611                 lg->gameInfo.black);
12612             DisplayTitle(buf);
12613     } else if (*title != NULLCHAR) {
12614         if (gameNumber > 1) {
12615           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12616             DisplayTitle(buf);
12617         } else {
12618             DisplayTitle(title);
12619         }
12620     }
12621
12622     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12623         gameMode = PlayFromGameFile;
12624         ModeHighlight();
12625     }
12626
12627     currentMove = forwardMostMove = backwardMostMove = 0;
12628     CopyBoard(boards[0], initialPosition);
12629     StopClocks();
12630
12631     /*
12632      * Skip the first gn-1 games in the file.
12633      * Also skip over anything that precedes an identifiable
12634      * start of game marker, to avoid being confused by
12635      * garbage at the start of the file.  Currently
12636      * recognized start of game markers are the move number "1",
12637      * the pattern "gnuchess .* game", the pattern
12638      * "^[#;%] [^ ]* game file", and a PGN tag block.
12639      * A game that starts with one of the latter two patterns
12640      * will also have a move number 1, possibly
12641      * following a position diagram.
12642      * 5-4-02: Let's try being more lenient and allowing a game to
12643      * start with an unnumbered move.  Does that break anything?
12644      */
12645     cm = lastLoadGameStart = EndOfFile;
12646     while (gn > 0) {
12647         yyboardindex = forwardMostMove;
12648         cm = (ChessMove) Myylex();
12649         switch (cm) {
12650           case EndOfFile:
12651             if (cmailMsgLoaded) {
12652                 nCmailGames = CMAIL_MAX_GAMES - gn;
12653             } else {
12654                 Reset(TRUE, TRUE);
12655                 DisplayError(_("Game not found in file"), 0);
12656             }
12657             return FALSE;
12658
12659           case GNUChessGame:
12660           case XBoardGame:
12661             gn--;
12662             lastLoadGameStart = cm;
12663             break;
12664
12665           case MoveNumberOne:
12666             switch (lastLoadGameStart) {
12667               case GNUChessGame:
12668               case XBoardGame:
12669               case PGNTag:
12670                 break;
12671               case MoveNumberOne:
12672               case EndOfFile:
12673                 gn--;           /* count this game */
12674                 lastLoadGameStart = cm;
12675                 break;
12676               default:
12677                 /* impossible */
12678                 break;
12679             }
12680             break;
12681
12682           case PGNTag:
12683             switch (lastLoadGameStart) {
12684               case GNUChessGame:
12685               case PGNTag:
12686               case MoveNumberOne:
12687               case EndOfFile:
12688                 gn--;           /* count this game */
12689                 lastLoadGameStart = cm;
12690                 break;
12691               case XBoardGame:
12692                 lastLoadGameStart = cm; /* game counted already */
12693                 break;
12694               default:
12695                 /* impossible */
12696                 break;
12697             }
12698             if (gn > 0) {
12699                 do {
12700                     yyboardindex = forwardMostMove;
12701                     cm = (ChessMove) Myylex();
12702                 } while (cm == PGNTag || cm == Comment);
12703             }
12704             break;
12705
12706           case WhiteWins:
12707           case BlackWins:
12708           case GameIsDrawn:
12709             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12710                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12711                     != CMAIL_OLD_RESULT) {
12712                     nCmailResults ++ ;
12713                     cmailResult[  CMAIL_MAX_GAMES
12714                                 - gn - 1] = CMAIL_OLD_RESULT;
12715                 }
12716             }
12717             break;
12718
12719           case NormalMove:
12720           case FirstLeg:
12721             /* Only a NormalMove can be at the start of a game
12722              * without a position diagram. */
12723             if (lastLoadGameStart == EndOfFile ) {
12724               gn--;
12725               lastLoadGameStart = MoveNumberOne;
12726             }
12727             break;
12728
12729           default:
12730             break;
12731         }
12732     }
12733
12734     if (appData.debugMode)
12735       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12736
12737     if (cm == XBoardGame) {
12738         /* Skip any header junk before position diagram and/or move 1 */
12739         for (;;) {
12740             yyboardindex = forwardMostMove;
12741             cm = (ChessMove) Myylex();
12742
12743             if (cm == EndOfFile ||
12744                 cm == GNUChessGame || cm == XBoardGame) {
12745                 /* Empty game; pretend end-of-file and handle later */
12746                 cm = EndOfFile;
12747                 break;
12748             }
12749
12750             if (cm == MoveNumberOne || cm == PositionDiagram ||
12751                 cm == PGNTag || cm == Comment)
12752               break;
12753         }
12754     } else if (cm == GNUChessGame) {
12755         if (gameInfo.event != NULL) {
12756             free(gameInfo.event);
12757         }
12758         gameInfo.event = StrSave(yy_text);
12759     }
12760
12761     startedFromSetupPosition = FALSE;
12762     while (cm == PGNTag) {
12763         if (appData.debugMode)
12764           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12765         err = ParsePGNTag(yy_text, &gameInfo);
12766         if (!err) numPGNTags++;
12767
12768         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12769         if(gameInfo.variant != oldVariant) {
12770             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12771             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12772             InitPosition(TRUE);
12773             oldVariant = gameInfo.variant;
12774             if (appData.debugMode)
12775               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12776         }
12777
12778
12779         if (gameInfo.fen != NULL) {
12780           Board initial_position;
12781           startedFromSetupPosition = TRUE;
12782           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12783             Reset(TRUE, TRUE);
12784             DisplayError(_("Bad FEN position in file"), 0);
12785             return FALSE;
12786           }
12787           CopyBoard(boards[0], initial_position);
12788           if (blackPlaysFirst) {
12789             currentMove = forwardMostMove = backwardMostMove = 1;
12790             CopyBoard(boards[1], initial_position);
12791             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12792             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12793             timeRemaining[0][1] = whiteTimeRemaining;
12794             timeRemaining[1][1] = blackTimeRemaining;
12795             if (commentList[0] != NULL) {
12796               commentList[1] = commentList[0];
12797               commentList[0] = NULL;
12798             }
12799           } else {
12800             currentMove = forwardMostMove = backwardMostMove = 0;
12801           }
12802           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12803           {   int i;
12804               initialRulePlies = FENrulePlies;
12805               for( i=0; i< nrCastlingRights; i++ )
12806                   initialRights[i] = initial_position[CASTLING][i];
12807           }
12808           yyboardindex = forwardMostMove;
12809           free(gameInfo.fen);
12810           gameInfo.fen = NULL;
12811         }
12812
12813         yyboardindex = forwardMostMove;
12814         cm = (ChessMove) Myylex();
12815
12816         /* Handle comments interspersed among the tags */
12817         while (cm == Comment) {
12818             char *p;
12819             if (appData.debugMode)
12820               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12821             p = yy_text;
12822             AppendComment(currentMove, p, FALSE);
12823             yyboardindex = forwardMostMove;
12824             cm = (ChessMove) Myylex();
12825         }
12826     }
12827
12828     /* don't rely on existence of Event tag since if game was
12829      * pasted from clipboard the Event tag may not exist
12830      */
12831     if (numPGNTags > 0){
12832         char *tags;
12833         if (gameInfo.variant == VariantNormal) {
12834           VariantClass v = StringToVariant(gameInfo.event);
12835           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12836           if(v < VariantShogi) gameInfo.variant = v;
12837         }
12838         if (!matchMode) {
12839           if( appData.autoDisplayTags ) {
12840             tags = PGNTags(&gameInfo);
12841             TagsPopUp(tags, CmailMsg());
12842             free(tags);
12843           }
12844         }
12845     } else {
12846         /* Make something up, but don't display it now */
12847         SetGameInfo();
12848         TagsPopDown();
12849     }
12850
12851     if (cm == PositionDiagram) {
12852         int i, j;
12853         char *p;
12854         Board initial_position;
12855
12856         if (appData.debugMode)
12857           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12858
12859         if (!startedFromSetupPosition) {
12860             p = yy_text;
12861             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12862               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12863                 switch (*p) {
12864                   case '{':
12865                   case '[':
12866                   case '-':
12867                   case ' ':
12868                   case '\t':
12869                   case '\n':
12870                   case '\r':
12871                     break;
12872                   default:
12873                     initial_position[i][j++] = CharToPiece(*p);
12874                     break;
12875                 }
12876             while (*p == ' ' || *p == '\t' ||
12877                    *p == '\n' || *p == '\r') p++;
12878
12879             if (strncmp(p, "black", strlen("black"))==0)
12880               blackPlaysFirst = TRUE;
12881             else
12882               blackPlaysFirst = FALSE;
12883             startedFromSetupPosition = TRUE;
12884
12885             CopyBoard(boards[0], initial_position);
12886             if (blackPlaysFirst) {
12887                 currentMove = forwardMostMove = backwardMostMove = 1;
12888                 CopyBoard(boards[1], initial_position);
12889                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12890                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12891                 timeRemaining[0][1] = whiteTimeRemaining;
12892                 timeRemaining[1][1] = blackTimeRemaining;
12893                 if (commentList[0] != NULL) {
12894                     commentList[1] = commentList[0];
12895                     commentList[0] = NULL;
12896                 }
12897             } else {
12898                 currentMove = forwardMostMove = backwardMostMove = 0;
12899             }
12900         }
12901         yyboardindex = forwardMostMove;
12902         cm = (ChessMove) Myylex();
12903     }
12904
12905   if(!creatingBook) {
12906     if (first.pr == NoProc) {
12907         StartChessProgram(&first);
12908     }
12909     InitChessProgram(&first, FALSE);
12910     SendToProgram("force\n", &first);
12911     if (startedFromSetupPosition) {
12912         SendBoard(&first, forwardMostMove);
12913     if (appData.debugMode) {
12914         fprintf(debugFP, "Load Game\n");
12915     }
12916         DisplayBothClocks();
12917     }
12918   }
12919
12920     /* [HGM] server: flag to write setup moves in broadcast file as one */
12921     loadFlag = appData.suppressLoadMoves;
12922
12923     while (cm == Comment) {
12924         char *p;
12925         if (appData.debugMode)
12926           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12927         p = yy_text;
12928         AppendComment(currentMove, p, FALSE);
12929         yyboardindex = forwardMostMove;
12930         cm = (ChessMove) Myylex();
12931     }
12932
12933     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12934         cm == WhiteWins || cm == BlackWins ||
12935         cm == GameIsDrawn || cm == GameUnfinished) {
12936         DisplayMessage("", _("No moves in game"));
12937         if (cmailMsgLoaded) {
12938             if (appData.debugMode)
12939               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12940             ClearHighlights();
12941             flipView = FALSE;
12942         }
12943         DrawPosition(FALSE, boards[currentMove]);
12944         DisplayBothClocks();
12945         gameMode = EditGame;
12946         ModeHighlight();
12947         gameFileFP = NULL;
12948         cmailOldMove = 0;
12949         return TRUE;
12950     }
12951
12952     // [HGM] PV info: routine tests if comment empty
12953     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12954         DisplayComment(currentMove - 1, commentList[currentMove]);
12955     }
12956     if (!matchMode && appData.timeDelay != 0)
12957       DrawPosition(FALSE, boards[currentMove]);
12958
12959     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12960       programStats.ok_to_send = 1;
12961     }
12962
12963     /* if the first token after the PGN tags is a move
12964      * and not move number 1, retrieve it from the parser
12965      */
12966     if (cm != MoveNumberOne)
12967         LoadGameOneMove(cm);
12968
12969     /* load the remaining moves from the file */
12970     while (LoadGameOneMove(EndOfFile)) {
12971       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12972       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12973     }
12974
12975     /* rewind to the start of the game */
12976     currentMove = backwardMostMove;
12977
12978     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12979
12980     if (oldGameMode == AnalyzeFile) {
12981       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12982       AnalyzeFileEvent();
12983     } else
12984     if (oldGameMode == AnalyzeMode) {
12985       AnalyzeFileEvent();
12986     }
12987
12988     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12989         long int w, b; // [HGM] adjourn: restore saved clock times
12990         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12991         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12992             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12993             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12994         }
12995     }
12996
12997     if(creatingBook) return TRUE;
12998     if (!matchMode && pos > 0) {
12999         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13000     } else
13001     if (matchMode || appData.timeDelay == 0) {
13002       ToEndEvent();
13003     } else if (appData.timeDelay > 0) {
13004       AutoPlayGameLoop();
13005     }
13006
13007     if (appData.debugMode)
13008         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13009
13010     loadFlag = 0; /* [HGM] true game starts */
13011     return TRUE;
13012 }
13013
13014 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13015 int
13016 ReloadPosition (int offset)
13017 {
13018     int positionNumber = lastLoadPositionNumber + offset;
13019     if (lastLoadPositionFP == NULL) {
13020         DisplayError(_("No position has been loaded yet"), 0);
13021         return FALSE;
13022     }
13023     if (positionNumber <= 0) {
13024         DisplayError(_("Can't back up any further"), 0);
13025         return FALSE;
13026     }
13027     return LoadPosition(lastLoadPositionFP, positionNumber,
13028                         lastLoadPositionTitle);
13029 }
13030
13031 /* Load the nth position from the given file */
13032 int
13033 LoadPositionFromFile (char *filename, int n, char *title)
13034 {
13035     FILE *f;
13036     char buf[MSG_SIZ];
13037
13038     if (strcmp(filename, "-") == 0) {
13039         return LoadPosition(stdin, n, "stdin");
13040     } else {
13041         f = fopen(filename, "rb");
13042         if (f == NULL) {
13043             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13044             DisplayError(buf, errno);
13045             return FALSE;
13046         } else {
13047             return LoadPosition(f, n, title);
13048         }
13049     }
13050 }
13051
13052 /* Load the nth position from the given open file, and close it */
13053 int
13054 LoadPosition (FILE *f, int positionNumber, char *title)
13055 {
13056     char *p, line[MSG_SIZ];
13057     Board initial_position;
13058     int i, j, fenMode, pn;
13059
13060     if (gameMode == Training )
13061         SetTrainingModeOff();
13062
13063     if (gameMode != BeginningOfGame) {
13064         Reset(FALSE, TRUE);
13065     }
13066     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13067         fclose(lastLoadPositionFP);
13068     }
13069     if (positionNumber == 0) positionNumber = 1;
13070     lastLoadPositionFP = f;
13071     lastLoadPositionNumber = positionNumber;
13072     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13073     if (first.pr == NoProc && !appData.noChessProgram) {
13074       StartChessProgram(&first);
13075       InitChessProgram(&first, FALSE);
13076     }
13077     pn = positionNumber;
13078     if (positionNumber < 0) {
13079         /* Negative position number means to seek to that byte offset */
13080         if (fseek(f, -positionNumber, 0) == -1) {
13081             DisplayError(_("Can't seek on position file"), 0);
13082             return FALSE;
13083         };
13084         pn = 1;
13085     } else {
13086         if (fseek(f, 0, 0) == -1) {
13087             if (f == lastLoadPositionFP ?
13088                 positionNumber == lastLoadPositionNumber + 1 :
13089                 positionNumber == 1) {
13090                 pn = 1;
13091             } else {
13092                 DisplayError(_("Can't seek on position file"), 0);
13093                 return FALSE;
13094             }
13095         }
13096     }
13097     /* See if this file is FEN or old-style xboard */
13098     if (fgets(line, MSG_SIZ, f) == NULL) {
13099         DisplayError(_("Position not found in file"), 0);
13100         return FALSE;
13101     }
13102     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13103     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13104
13105     if (pn >= 2) {
13106         if (fenMode || line[0] == '#') pn--;
13107         while (pn > 0) {
13108             /* skip positions before number pn */
13109             if (fgets(line, MSG_SIZ, f) == NULL) {
13110                 Reset(TRUE, TRUE);
13111                 DisplayError(_("Position not found in file"), 0);
13112                 return FALSE;
13113             }
13114             if (fenMode || line[0] == '#') pn--;
13115         }
13116     }
13117
13118     if (fenMode) {
13119         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13120             DisplayError(_("Bad FEN position in file"), 0);
13121             return FALSE;
13122         }
13123     } else {
13124         (void) fgets(line, MSG_SIZ, f);
13125         (void) fgets(line, MSG_SIZ, f);
13126
13127         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13128             (void) fgets(line, MSG_SIZ, f);
13129             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13130                 if (*p == ' ')
13131                   continue;
13132                 initial_position[i][j++] = CharToPiece(*p);
13133             }
13134         }
13135
13136         blackPlaysFirst = FALSE;
13137         if (!feof(f)) {
13138             (void) fgets(line, MSG_SIZ, f);
13139             if (strncmp(line, "black", strlen("black"))==0)
13140               blackPlaysFirst = TRUE;
13141         }
13142     }
13143     startedFromSetupPosition = TRUE;
13144
13145     CopyBoard(boards[0], initial_position);
13146     if (blackPlaysFirst) {
13147         currentMove = forwardMostMove = backwardMostMove = 1;
13148         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13149         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13150         CopyBoard(boards[1], initial_position);
13151         DisplayMessage("", _("Black to play"));
13152     } else {
13153         currentMove = forwardMostMove = backwardMostMove = 0;
13154         DisplayMessage("", _("White to play"));
13155     }
13156     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13157     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13158         SendToProgram("force\n", &first);
13159         SendBoard(&first, forwardMostMove);
13160     }
13161     if (appData.debugMode) {
13162 int i, j;
13163   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13164   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13165         fprintf(debugFP, "Load Position\n");
13166     }
13167
13168     if (positionNumber > 1) {
13169       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13170         DisplayTitle(line);
13171     } else {
13172         DisplayTitle(title);
13173     }
13174     gameMode = EditGame;
13175     ModeHighlight();
13176     ResetClocks();
13177     timeRemaining[0][1] = whiteTimeRemaining;
13178     timeRemaining[1][1] = blackTimeRemaining;
13179     DrawPosition(FALSE, boards[currentMove]);
13180
13181     return TRUE;
13182 }
13183
13184
13185 void
13186 CopyPlayerNameIntoFileName (char **dest, char *src)
13187 {
13188     while (*src != NULLCHAR && *src != ',') {
13189         if (*src == ' ') {
13190             *(*dest)++ = '_';
13191             src++;
13192         } else {
13193             *(*dest)++ = *src++;
13194         }
13195     }
13196 }
13197
13198 char *
13199 DefaultFileName (char *ext)
13200 {
13201     static char def[MSG_SIZ];
13202     char *p;
13203
13204     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13205         p = def;
13206         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13207         *p++ = '-';
13208         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13209         *p++ = '.';
13210         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13211     } else {
13212         def[0] = NULLCHAR;
13213     }
13214     return def;
13215 }
13216
13217 /* Save the current game to the given file */
13218 int
13219 SaveGameToFile (char *filename, int append)
13220 {
13221     FILE *f;
13222     char buf[MSG_SIZ];
13223     int result, i, t,tot=0;
13224
13225     if (strcmp(filename, "-") == 0) {
13226         return SaveGame(stdout, 0, NULL);
13227     } else {
13228         for(i=0; i<10; i++) { // upto 10 tries
13229              f = fopen(filename, append ? "a" : "w");
13230              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13231              if(f || errno != 13) break;
13232              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13233              tot += t;
13234         }
13235         if (f == NULL) {
13236             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13237             DisplayError(buf, errno);
13238             return FALSE;
13239         } else {
13240             safeStrCpy(buf, lastMsg, MSG_SIZ);
13241             DisplayMessage(_("Waiting for access to save file"), "");
13242             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13243             DisplayMessage(_("Saving game"), "");
13244             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13245             result = SaveGame(f, 0, NULL);
13246             DisplayMessage(buf, "");
13247             return result;
13248         }
13249     }
13250 }
13251
13252 char *
13253 SavePart (char *str)
13254 {
13255     static char buf[MSG_SIZ];
13256     char *p;
13257
13258     p = strchr(str, ' ');
13259     if (p == NULL) return str;
13260     strncpy(buf, str, p - str);
13261     buf[p - str] = NULLCHAR;
13262     return buf;
13263 }
13264
13265 #define PGN_MAX_LINE 75
13266
13267 #define PGN_SIDE_WHITE  0
13268 #define PGN_SIDE_BLACK  1
13269
13270 static int
13271 FindFirstMoveOutOfBook (int side)
13272 {
13273     int result = -1;
13274
13275     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13276         int index = backwardMostMove;
13277         int has_book_hit = 0;
13278
13279         if( (index % 2) != side ) {
13280             index++;
13281         }
13282
13283         while( index < forwardMostMove ) {
13284             /* Check to see if engine is in book */
13285             int depth = pvInfoList[index].depth;
13286             int score = pvInfoList[index].score;
13287             int in_book = 0;
13288
13289             if( depth <= 2 ) {
13290                 in_book = 1;
13291             }
13292             else if( score == 0 && depth == 63 ) {
13293                 in_book = 1; /* Zappa */
13294             }
13295             else if( score == 2 && depth == 99 ) {
13296                 in_book = 1; /* Abrok */
13297             }
13298
13299             has_book_hit += in_book;
13300
13301             if( ! in_book ) {
13302                 result = index;
13303
13304                 break;
13305             }
13306
13307             index += 2;
13308         }
13309     }
13310
13311     return result;
13312 }
13313
13314 void
13315 GetOutOfBookInfo (char * buf)
13316 {
13317     int oob[2];
13318     int i;
13319     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13320
13321     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13322     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13323
13324     *buf = '\0';
13325
13326     if( oob[0] >= 0 || oob[1] >= 0 ) {
13327         for( i=0; i<2; i++ ) {
13328             int idx = oob[i];
13329
13330             if( idx >= 0 ) {
13331                 if( i > 0 && oob[0] >= 0 ) {
13332                     strcat( buf, "   " );
13333                 }
13334
13335                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13336                 sprintf( buf+strlen(buf), "%s%.2f",
13337                     pvInfoList[idx].score >= 0 ? "+" : "",
13338                     pvInfoList[idx].score / 100.0 );
13339             }
13340         }
13341     }
13342 }
13343
13344 /* Save game in PGN style and close the file */
13345 int
13346 SaveGamePGN (FILE *f)
13347 {
13348     int i, offset, linelen, newblock;
13349 //    char *movetext;
13350     char numtext[32];
13351     int movelen, numlen, blank;
13352     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13353
13354     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13355
13356     PrintPGNTags(f, &gameInfo);
13357
13358     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13359
13360     if (backwardMostMove > 0 || startedFromSetupPosition) {
13361         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13362         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13363         fprintf(f, "\n{--------------\n");
13364         PrintPosition(f, backwardMostMove);
13365         fprintf(f, "--------------}\n");
13366         free(fen);
13367     }
13368     else {
13369         /* [AS] Out of book annotation */
13370         if( appData.saveOutOfBookInfo ) {
13371             char buf[64];
13372
13373             GetOutOfBookInfo( buf );
13374
13375             if( buf[0] != '\0' ) {
13376                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13377             }
13378         }
13379
13380         fprintf(f, "\n");
13381     }
13382
13383     i = backwardMostMove;
13384     linelen = 0;
13385     newblock = TRUE;
13386
13387     while (i < forwardMostMove) {
13388         /* Print comments preceding this move */
13389         if (commentList[i] != NULL) {
13390             if (linelen > 0) fprintf(f, "\n");
13391             fprintf(f, "%s", commentList[i]);
13392             linelen = 0;
13393             newblock = TRUE;
13394         }
13395
13396         /* Format move number */
13397         if ((i % 2) == 0)
13398           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13399         else
13400           if (newblock)
13401             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13402           else
13403             numtext[0] = NULLCHAR;
13404
13405         numlen = strlen(numtext);
13406         newblock = FALSE;
13407
13408         /* Print move number */
13409         blank = linelen > 0 && numlen > 0;
13410         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13411             fprintf(f, "\n");
13412             linelen = 0;
13413             blank = 0;
13414         }
13415         if (blank) {
13416             fprintf(f, " ");
13417             linelen++;
13418         }
13419         fprintf(f, "%s", numtext);
13420         linelen += numlen;
13421
13422         /* Get move */
13423         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13424         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13425
13426         /* Print move */
13427         blank = linelen > 0 && movelen > 0;
13428         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13429             fprintf(f, "\n");
13430             linelen = 0;
13431             blank = 0;
13432         }
13433         if (blank) {
13434             fprintf(f, " ");
13435             linelen++;
13436         }
13437         fprintf(f, "%s", move_buffer);
13438         linelen += movelen;
13439
13440         /* [AS] Add PV info if present */
13441         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13442             /* [HGM] add time */
13443             char buf[MSG_SIZ]; int seconds;
13444
13445             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13446
13447             if( seconds <= 0)
13448               buf[0] = 0;
13449             else
13450               if( seconds < 30 )
13451                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13452               else
13453                 {
13454                   seconds = (seconds + 4)/10; // round to full seconds
13455                   if( seconds < 60 )
13456                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13457                   else
13458                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13459                 }
13460
13461             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13462                       pvInfoList[i].score >= 0 ? "+" : "",
13463                       pvInfoList[i].score / 100.0,
13464                       pvInfoList[i].depth,
13465                       buf );
13466
13467             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13468
13469             /* Print score/depth */
13470             blank = linelen > 0 && movelen > 0;
13471             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13472                 fprintf(f, "\n");
13473                 linelen = 0;
13474                 blank = 0;
13475             }
13476             if (blank) {
13477                 fprintf(f, " ");
13478                 linelen++;
13479             }
13480             fprintf(f, "%s", move_buffer);
13481             linelen += movelen;
13482         }
13483
13484         i++;
13485     }
13486
13487     /* Start a new line */
13488     if (linelen > 0) fprintf(f, "\n");
13489
13490     /* Print comments after last move */
13491     if (commentList[i] != NULL) {
13492         fprintf(f, "%s\n", commentList[i]);
13493     }
13494
13495     /* Print result */
13496     if (gameInfo.resultDetails != NULL &&
13497         gameInfo.resultDetails[0] != NULLCHAR) {
13498         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13499         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13500            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13501             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13502         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13503     } else {
13504         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13505     }
13506
13507     fclose(f);
13508     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13509     return TRUE;
13510 }
13511
13512 /* Save game in old style and close the file */
13513 int
13514 SaveGameOldStyle (FILE *f)
13515 {
13516     int i, offset;
13517     time_t tm;
13518
13519     tm = time((time_t *) NULL);
13520
13521     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13522     PrintOpponents(f);
13523
13524     if (backwardMostMove > 0 || startedFromSetupPosition) {
13525         fprintf(f, "\n[--------------\n");
13526         PrintPosition(f, backwardMostMove);
13527         fprintf(f, "--------------]\n");
13528     } else {
13529         fprintf(f, "\n");
13530     }
13531
13532     i = backwardMostMove;
13533     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13534
13535     while (i < forwardMostMove) {
13536         if (commentList[i] != NULL) {
13537             fprintf(f, "[%s]\n", commentList[i]);
13538         }
13539
13540         if ((i % 2) == 1) {
13541             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13542             i++;
13543         } else {
13544             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13545             i++;
13546             if (commentList[i] != NULL) {
13547                 fprintf(f, "\n");
13548                 continue;
13549             }
13550             if (i >= forwardMostMove) {
13551                 fprintf(f, "\n");
13552                 break;
13553             }
13554             fprintf(f, "%s\n", parseList[i]);
13555             i++;
13556         }
13557     }
13558
13559     if (commentList[i] != NULL) {
13560         fprintf(f, "[%s]\n", commentList[i]);
13561     }
13562
13563     /* This isn't really the old style, but it's close enough */
13564     if (gameInfo.resultDetails != NULL &&
13565         gameInfo.resultDetails[0] != NULLCHAR) {
13566         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13567                 gameInfo.resultDetails);
13568     } else {
13569         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13570     }
13571
13572     fclose(f);
13573     return TRUE;
13574 }
13575
13576 /* Save the current game to open file f and close the file */
13577 int
13578 SaveGame (FILE *f, int dummy, char *dummy2)
13579 {
13580     if (gameMode == EditPosition) EditPositionDone(TRUE);
13581     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13582     if (appData.oldSaveStyle)
13583       return SaveGameOldStyle(f);
13584     else
13585       return SaveGamePGN(f);
13586 }
13587
13588 /* Save the current position to the given file */
13589 int
13590 SavePositionToFile (char *filename)
13591 {
13592     FILE *f;
13593     char buf[MSG_SIZ];
13594
13595     if (strcmp(filename, "-") == 0) {
13596         return SavePosition(stdout, 0, NULL);
13597     } else {
13598         f = fopen(filename, "a");
13599         if (f == NULL) {
13600             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13601             DisplayError(buf, errno);
13602             return FALSE;
13603         } else {
13604             safeStrCpy(buf, lastMsg, MSG_SIZ);
13605             DisplayMessage(_("Waiting for access to save file"), "");
13606             flock(fileno(f), LOCK_EX); // [HGM] lock
13607             DisplayMessage(_("Saving position"), "");
13608             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13609             SavePosition(f, 0, NULL);
13610             DisplayMessage(buf, "");
13611             return TRUE;
13612         }
13613     }
13614 }
13615
13616 /* Save the current position to the given open file and close the file */
13617 int
13618 SavePosition (FILE *f, int dummy, char *dummy2)
13619 {
13620     time_t tm;
13621     char *fen;
13622
13623     if (gameMode == EditPosition) EditPositionDone(TRUE);
13624     if (appData.oldSaveStyle) {
13625         tm = time((time_t *) NULL);
13626
13627         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13628         PrintOpponents(f);
13629         fprintf(f, "[--------------\n");
13630         PrintPosition(f, currentMove);
13631         fprintf(f, "--------------]\n");
13632     } else {
13633         fen = PositionToFEN(currentMove, NULL, 1);
13634         fprintf(f, "%s\n", fen);
13635         free(fen);
13636     }
13637     fclose(f);
13638     return TRUE;
13639 }
13640
13641 void
13642 ReloadCmailMsgEvent (int unregister)
13643 {
13644 #if !WIN32
13645     static char *inFilename = NULL;
13646     static char *outFilename;
13647     int i;
13648     struct stat inbuf, outbuf;
13649     int status;
13650
13651     /* Any registered moves are unregistered if unregister is set, */
13652     /* i.e. invoked by the signal handler */
13653     if (unregister) {
13654         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13655             cmailMoveRegistered[i] = FALSE;
13656             if (cmailCommentList[i] != NULL) {
13657                 free(cmailCommentList[i]);
13658                 cmailCommentList[i] = NULL;
13659             }
13660         }
13661         nCmailMovesRegistered = 0;
13662     }
13663
13664     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13665         cmailResult[i] = CMAIL_NOT_RESULT;
13666     }
13667     nCmailResults = 0;
13668
13669     if (inFilename == NULL) {
13670         /* Because the filenames are static they only get malloced once  */
13671         /* and they never get freed                                      */
13672         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13673         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13674
13675         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13676         sprintf(outFilename, "%s.out", appData.cmailGameName);
13677     }
13678
13679     status = stat(outFilename, &outbuf);
13680     if (status < 0) {
13681         cmailMailedMove = FALSE;
13682     } else {
13683         status = stat(inFilename, &inbuf);
13684         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13685     }
13686
13687     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13688        counts the games, notes how each one terminated, etc.
13689
13690        It would be nice to remove this kludge and instead gather all
13691        the information while building the game list.  (And to keep it
13692        in the game list nodes instead of having a bunch of fixed-size
13693        parallel arrays.)  Note this will require getting each game's
13694        termination from the PGN tags, as the game list builder does
13695        not process the game moves.  --mann
13696        */
13697     cmailMsgLoaded = TRUE;
13698     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13699
13700     /* Load first game in the file or popup game menu */
13701     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13702
13703 #endif /* !WIN32 */
13704     return;
13705 }
13706
13707 int
13708 RegisterMove ()
13709 {
13710     FILE *f;
13711     char string[MSG_SIZ];
13712
13713     if (   cmailMailedMove
13714         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13715         return TRUE;            /* Allow free viewing  */
13716     }
13717
13718     /* Unregister move to ensure that we don't leave RegisterMove        */
13719     /* with the move registered when the conditions for registering no   */
13720     /* longer hold                                                       */
13721     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13722         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13723         nCmailMovesRegistered --;
13724
13725         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13726           {
13727               free(cmailCommentList[lastLoadGameNumber - 1]);
13728               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13729           }
13730     }
13731
13732     if (cmailOldMove == -1) {
13733         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13734         return FALSE;
13735     }
13736
13737     if (currentMove > cmailOldMove + 1) {
13738         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13739         return FALSE;
13740     }
13741
13742     if (currentMove < cmailOldMove) {
13743         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13744         return FALSE;
13745     }
13746
13747     if (forwardMostMove > currentMove) {
13748         /* Silently truncate extra moves */
13749         TruncateGame();
13750     }
13751
13752     if (   (currentMove == cmailOldMove + 1)
13753         || (   (currentMove == cmailOldMove)
13754             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13755                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13756         if (gameInfo.result != GameUnfinished) {
13757             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13758         }
13759
13760         if (commentList[currentMove] != NULL) {
13761             cmailCommentList[lastLoadGameNumber - 1]
13762               = StrSave(commentList[currentMove]);
13763         }
13764         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13765
13766         if (appData.debugMode)
13767           fprintf(debugFP, "Saving %s for game %d\n",
13768                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13769
13770         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13771
13772         f = fopen(string, "w");
13773         if (appData.oldSaveStyle) {
13774             SaveGameOldStyle(f); /* also closes the file */
13775
13776             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13777             f = fopen(string, "w");
13778             SavePosition(f, 0, NULL); /* also closes the file */
13779         } else {
13780             fprintf(f, "{--------------\n");
13781             PrintPosition(f, currentMove);
13782             fprintf(f, "--------------}\n\n");
13783
13784             SaveGame(f, 0, NULL); /* also closes the file*/
13785         }
13786
13787         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13788         nCmailMovesRegistered ++;
13789     } else if (nCmailGames == 1) {
13790         DisplayError(_("You have not made a move yet"), 0);
13791         return FALSE;
13792     }
13793
13794     return TRUE;
13795 }
13796
13797 void
13798 MailMoveEvent ()
13799 {
13800 #if !WIN32
13801     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13802     FILE *commandOutput;
13803     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13804     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13805     int nBuffers;
13806     int i;
13807     int archived;
13808     char *arcDir;
13809
13810     if (! cmailMsgLoaded) {
13811         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13812         return;
13813     }
13814
13815     if (nCmailGames == nCmailResults) {
13816         DisplayError(_("No unfinished games"), 0);
13817         return;
13818     }
13819
13820 #if CMAIL_PROHIBIT_REMAIL
13821     if (cmailMailedMove) {
13822       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);
13823         DisplayError(msg, 0);
13824         return;
13825     }
13826 #endif
13827
13828     if (! (cmailMailedMove || RegisterMove())) return;
13829
13830     if (   cmailMailedMove
13831         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13832       snprintf(string, MSG_SIZ, partCommandString,
13833                appData.debugMode ? " -v" : "", appData.cmailGameName);
13834         commandOutput = popen(string, "r");
13835
13836         if (commandOutput == NULL) {
13837             DisplayError(_("Failed to invoke cmail"), 0);
13838         } else {
13839             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13840                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13841             }
13842             if (nBuffers > 1) {
13843                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13844                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13845                 nBytes = MSG_SIZ - 1;
13846             } else {
13847                 (void) memcpy(msg, buffer, nBytes);
13848             }
13849             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13850
13851             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13852                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13853
13854                 archived = TRUE;
13855                 for (i = 0; i < nCmailGames; i ++) {
13856                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13857                         archived = FALSE;
13858                     }
13859                 }
13860                 if (   archived
13861                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13862                         != NULL)) {
13863                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13864                            arcDir,
13865                            appData.cmailGameName,
13866                            gameInfo.date);
13867                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13868                     cmailMsgLoaded = FALSE;
13869                 }
13870             }
13871
13872             DisplayInformation(msg);
13873             pclose(commandOutput);
13874         }
13875     } else {
13876         if ((*cmailMsg) != '\0') {
13877             DisplayInformation(cmailMsg);
13878         }
13879     }
13880
13881     return;
13882 #endif /* !WIN32 */
13883 }
13884
13885 char *
13886 CmailMsg ()
13887 {
13888 #if WIN32
13889     return NULL;
13890 #else
13891     int  prependComma = 0;
13892     char number[5];
13893     char string[MSG_SIZ];       /* Space for game-list */
13894     int  i;
13895
13896     if (!cmailMsgLoaded) return "";
13897
13898     if (cmailMailedMove) {
13899       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13900     } else {
13901         /* Create a list of games left */
13902       snprintf(string, MSG_SIZ, "[");
13903         for (i = 0; i < nCmailGames; i ++) {
13904             if (! (   cmailMoveRegistered[i]
13905                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13906                 if (prependComma) {
13907                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13908                 } else {
13909                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13910                     prependComma = 1;
13911                 }
13912
13913                 strcat(string, number);
13914             }
13915         }
13916         strcat(string, "]");
13917
13918         if (nCmailMovesRegistered + nCmailResults == 0) {
13919             switch (nCmailGames) {
13920               case 1:
13921                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13922                 break;
13923
13924               case 2:
13925                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13926                 break;
13927
13928               default:
13929                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13930                          nCmailGames);
13931                 break;
13932             }
13933         } else {
13934             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13935               case 1:
13936                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13937                          string);
13938                 break;
13939
13940               case 0:
13941                 if (nCmailResults == nCmailGames) {
13942                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13943                 } else {
13944                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13945                 }
13946                 break;
13947
13948               default:
13949                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13950                          string);
13951             }
13952         }
13953     }
13954     return cmailMsg;
13955 #endif /* WIN32 */
13956 }
13957
13958 void
13959 ResetGameEvent ()
13960 {
13961     if (gameMode == Training)
13962       SetTrainingModeOff();
13963
13964     Reset(TRUE, TRUE);
13965     cmailMsgLoaded = FALSE;
13966     if (appData.icsActive) {
13967       SendToICS(ics_prefix);
13968       SendToICS("refresh\n");
13969     }
13970 }
13971
13972 void
13973 ExitEvent (int status)
13974 {
13975     exiting++;
13976     if (exiting > 2) {
13977       /* Give up on clean exit */
13978       exit(status);
13979     }
13980     if (exiting > 1) {
13981       /* Keep trying for clean exit */
13982       return;
13983     }
13984
13985     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
13986     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13987
13988     if (telnetISR != NULL) {
13989       RemoveInputSource(telnetISR);
13990     }
13991     if (icsPR != NoProc) {
13992       DestroyChildProcess(icsPR, TRUE);
13993     }
13994
13995     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13996     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13997
13998     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13999     /* make sure this other one finishes before killing it!                  */
14000     if(endingGame) { int count = 0;
14001         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14002         while(endingGame && count++ < 10) DoSleep(1);
14003         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14004     }
14005
14006     /* Kill off chess programs */
14007     if (first.pr != NoProc) {
14008         ExitAnalyzeMode();
14009
14010         DoSleep( appData.delayBeforeQuit );
14011         SendToProgram("quit\n", &first);
14012         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14013     }
14014     if (second.pr != NoProc) {
14015         DoSleep( appData.delayBeforeQuit );
14016         SendToProgram("quit\n", &second);
14017         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14018     }
14019     if (first.isr != NULL) {
14020         RemoveInputSource(first.isr);
14021     }
14022     if (second.isr != NULL) {
14023         RemoveInputSource(second.isr);
14024     }
14025
14026     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14027     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14028
14029     ShutDownFrontEnd();
14030     exit(status);
14031 }
14032
14033 void
14034 PauseEngine (ChessProgramState *cps)
14035 {
14036     SendToProgram("pause\n", cps);
14037     cps->pause = 2;
14038 }
14039
14040 void
14041 UnPauseEngine (ChessProgramState *cps)
14042 {
14043     SendToProgram("resume\n", cps);
14044     cps->pause = 1;
14045 }
14046
14047 void
14048 PauseEvent ()
14049 {
14050     if (appData.debugMode)
14051         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14052     if (pausing) {
14053         pausing = FALSE;
14054         ModeHighlight();
14055         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14056             StartClocks();
14057             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14058                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14059                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14060             }
14061             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14062             HandleMachineMove(stashedInputMove, stalledEngine);
14063             stalledEngine = NULL;
14064             return;
14065         }
14066         if (gameMode == MachinePlaysWhite ||
14067             gameMode == TwoMachinesPlay   ||
14068             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14069             if(first.pause)  UnPauseEngine(&first);
14070             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14071             if(second.pause) UnPauseEngine(&second);
14072             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14073             StartClocks();
14074         } else {
14075             DisplayBothClocks();
14076         }
14077         if (gameMode == PlayFromGameFile) {
14078             if (appData.timeDelay >= 0)
14079                 AutoPlayGameLoop();
14080         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14081             Reset(FALSE, TRUE);
14082             SendToICS(ics_prefix);
14083             SendToICS("refresh\n");
14084         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14085             ForwardInner(forwardMostMove);
14086         }
14087         pauseExamInvalid = FALSE;
14088     } else {
14089         switch (gameMode) {
14090           default:
14091             return;
14092           case IcsExamining:
14093             pauseExamForwardMostMove = forwardMostMove;
14094             pauseExamInvalid = FALSE;
14095             /* fall through */
14096           case IcsObserving:
14097           case IcsPlayingWhite:
14098           case IcsPlayingBlack:
14099             pausing = TRUE;
14100             ModeHighlight();
14101             return;
14102           case PlayFromGameFile:
14103             (void) StopLoadGameTimer();
14104             pausing = TRUE;
14105             ModeHighlight();
14106             break;
14107           case BeginningOfGame:
14108             if (appData.icsActive) return;
14109             /* else fall through */
14110           case MachinePlaysWhite:
14111           case MachinePlaysBlack:
14112           case TwoMachinesPlay:
14113             if (forwardMostMove == 0)
14114               return;           /* don't pause if no one has moved */
14115             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14116                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14117                 if(onMove->pause) {           // thinking engine can be paused
14118                     PauseEngine(onMove);      // do it
14119                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14120                         PauseEngine(onMove->other);
14121                     else
14122                         SendToProgram("easy\n", onMove->other);
14123                     StopClocks();
14124                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14125             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14126                 if(first.pause) {
14127                     PauseEngine(&first);
14128                     StopClocks();
14129                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14130             } else { // human on move, pause pondering by either method
14131                 if(first.pause)
14132                     PauseEngine(&first);
14133                 else if(appData.ponderNextMove)
14134                     SendToProgram("easy\n", &first);
14135                 StopClocks();
14136             }
14137             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14138           case AnalyzeMode:
14139             pausing = TRUE;
14140             ModeHighlight();
14141             break;
14142         }
14143     }
14144 }
14145
14146 void
14147 EditCommentEvent ()
14148 {
14149     char title[MSG_SIZ];
14150
14151     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14152       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14153     } else {
14154       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14155                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14156                parseList[currentMove - 1]);
14157     }
14158
14159     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14160 }
14161
14162
14163 void
14164 EditTagsEvent ()
14165 {
14166     char *tags = PGNTags(&gameInfo);
14167     bookUp = FALSE;
14168     EditTagsPopUp(tags, NULL);
14169     free(tags);
14170 }
14171
14172 void
14173 ToggleSecond ()
14174 {
14175   if(second.analyzing) {
14176     SendToProgram("exit\n", &second);
14177     second.analyzing = FALSE;
14178   } else {
14179     if (second.pr == NoProc) StartChessProgram(&second);
14180     InitChessProgram(&second, FALSE);
14181     FeedMovesToProgram(&second, currentMove);
14182
14183     SendToProgram("analyze\n", &second);
14184     second.analyzing = TRUE;
14185   }
14186 }
14187
14188 /* Toggle ShowThinking */
14189 void
14190 ToggleShowThinking()
14191 {
14192   appData.showThinking = !appData.showThinking;
14193   ShowThinkingEvent();
14194 }
14195
14196 int
14197 AnalyzeModeEvent ()
14198 {
14199     char buf[MSG_SIZ];
14200
14201     if (!first.analysisSupport) {
14202       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14203       DisplayError(buf, 0);
14204       return 0;
14205     }
14206     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14207     if (appData.icsActive) {
14208         if (gameMode != IcsObserving) {
14209           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14210             DisplayError(buf, 0);
14211             /* secure check */
14212             if (appData.icsEngineAnalyze) {
14213                 if (appData.debugMode)
14214                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14215                 ExitAnalyzeMode();
14216                 ModeHighlight();
14217             }
14218             return 0;
14219         }
14220         /* if enable, user wants to disable icsEngineAnalyze */
14221         if (appData.icsEngineAnalyze) {
14222                 ExitAnalyzeMode();
14223                 ModeHighlight();
14224                 return 0;
14225         }
14226         appData.icsEngineAnalyze = TRUE;
14227         if (appData.debugMode)
14228             fprintf(debugFP, "ICS engine analyze starting... \n");
14229     }
14230
14231     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14232     if (appData.noChessProgram || gameMode == AnalyzeMode)
14233       return 0;
14234
14235     if (gameMode != AnalyzeFile) {
14236         if (!appData.icsEngineAnalyze) {
14237                EditGameEvent();
14238                if (gameMode != EditGame) return 0;
14239         }
14240         if (!appData.showThinking) ToggleShowThinking();
14241         ResurrectChessProgram();
14242         SendToProgram("analyze\n", &first);
14243         first.analyzing = TRUE;
14244         /*first.maybeThinking = TRUE;*/
14245         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14246         EngineOutputPopUp();
14247     }
14248     if (!appData.icsEngineAnalyze) {
14249         gameMode = AnalyzeMode;
14250         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14251     }
14252     pausing = FALSE;
14253     ModeHighlight();
14254     SetGameInfo();
14255
14256     StartAnalysisClock();
14257     GetTimeMark(&lastNodeCountTime);
14258     lastNodeCount = 0;
14259     return 1;
14260 }
14261
14262 void
14263 AnalyzeFileEvent ()
14264 {
14265     if (appData.noChessProgram || gameMode == AnalyzeFile)
14266       return;
14267
14268     if (!first.analysisSupport) {
14269       char buf[MSG_SIZ];
14270       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14271       DisplayError(buf, 0);
14272       return;
14273     }
14274
14275     if (gameMode != AnalyzeMode) {
14276         keepInfo = 1; // mere annotating should not alter PGN tags
14277         EditGameEvent();
14278         keepInfo = 0;
14279         if (gameMode != EditGame) return;
14280         if (!appData.showThinking) ToggleShowThinking();
14281         ResurrectChessProgram();
14282         SendToProgram("analyze\n", &first);
14283         first.analyzing = TRUE;
14284         /*first.maybeThinking = TRUE;*/
14285         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14286         EngineOutputPopUp();
14287     }
14288     gameMode = AnalyzeFile;
14289     pausing = FALSE;
14290     ModeHighlight();
14291
14292     StartAnalysisClock();
14293     GetTimeMark(&lastNodeCountTime);
14294     lastNodeCount = 0;
14295     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14296     AnalysisPeriodicEvent(1);
14297 }
14298
14299 void
14300 MachineWhiteEvent ()
14301 {
14302     char buf[MSG_SIZ];
14303     char *bookHit = NULL;
14304
14305     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14306       return;
14307
14308
14309     if (gameMode == PlayFromGameFile ||
14310         gameMode == TwoMachinesPlay  ||
14311         gameMode == Training         ||
14312         gameMode == AnalyzeMode      ||
14313         gameMode == EndOfGame)
14314         EditGameEvent();
14315
14316     if (gameMode == EditPosition)
14317         EditPositionDone(TRUE);
14318
14319     if (!WhiteOnMove(currentMove)) {
14320         DisplayError(_("It is not White's turn"), 0);
14321         return;
14322     }
14323
14324     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14325       ExitAnalyzeMode();
14326
14327     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14328         gameMode == AnalyzeFile)
14329         TruncateGame();
14330
14331     ResurrectChessProgram();    /* in case it isn't running */
14332     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14333         gameMode = MachinePlaysWhite;
14334         ResetClocks();
14335     } else
14336     gameMode = MachinePlaysWhite;
14337     pausing = FALSE;
14338     ModeHighlight();
14339     SetGameInfo();
14340     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14341     DisplayTitle(buf);
14342     if (first.sendName) {
14343       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14344       SendToProgram(buf, &first);
14345     }
14346     if (first.sendTime) {
14347       if (first.useColors) {
14348         SendToProgram("black\n", &first); /*gnu kludge*/
14349       }
14350       SendTimeRemaining(&first, TRUE);
14351     }
14352     if (first.useColors) {
14353       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14354     }
14355     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14356     SetMachineThinkingEnables();
14357     first.maybeThinking = TRUE;
14358     StartClocks();
14359     firstMove = FALSE;
14360
14361     if (appData.autoFlipView && !flipView) {
14362       flipView = !flipView;
14363       DrawPosition(FALSE, NULL);
14364       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14365     }
14366
14367     if(bookHit) { // [HGM] book: simulate book reply
14368         static char bookMove[MSG_SIZ]; // a bit generous?
14369
14370         programStats.nodes = programStats.depth = programStats.time =
14371         programStats.score = programStats.got_only_move = 0;
14372         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14373
14374         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14375         strcat(bookMove, bookHit);
14376         HandleMachineMove(bookMove, &first);
14377     }
14378 }
14379
14380 void
14381 MachineBlackEvent ()
14382 {
14383   char buf[MSG_SIZ];
14384   char *bookHit = NULL;
14385
14386     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14387         return;
14388
14389
14390     if (gameMode == PlayFromGameFile ||
14391         gameMode == TwoMachinesPlay  ||
14392         gameMode == Training         ||
14393         gameMode == AnalyzeMode      ||
14394         gameMode == EndOfGame)
14395         EditGameEvent();
14396
14397     if (gameMode == EditPosition)
14398         EditPositionDone(TRUE);
14399
14400     if (WhiteOnMove(currentMove)) {
14401         DisplayError(_("It is not Black's turn"), 0);
14402         return;
14403     }
14404
14405     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14406       ExitAnalyzeMode();
14407
14408     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14409         gameMode == AnalyzeFile)
14410         TruncateGame();
14411
14412     ResurrectChessProgram();    /* in case it isn't running */
14413     gameMode = MachinePlaysBlack;
14414     pausing = FALSE;
14415     ModeHighlight();
14416     SetGameInfo();
14417     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14418     DisplayTitle(buf);
14419     if (first.sendName) {
14420       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14421       SendToProgram(buf, &first);
14422     }
14423     if (first.sendTime) {
14424       if (first.useColors) {
14425         SendToProgram("white\n", &first); /*gnu kludge*/
14426       }
14427       SendTimeRemaining(&first, FALSE);
14428     }
14429     if (first.useColors) {
14430       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14431     }
14432     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14433     SetMachineThinkingEnables();
14434     first.maybeThinking = TRUE;
14435     StartClocks();
14436
14437     if (appData.autoFlipView && flipView) {
14438       flipView = !flipView;
14439       DrawPosition(FALSE, NULL);
14440       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14441     }
14442     if(bookHit) { // [HGM] book: simulate book reply
14443         static char bookMove[MSG_SIZ]; // a bit generous?
14444
14445         programStats.nodes = programStats.depth = programStats.time =
14446         programStats.score = programStats.got_only_move = 0;
14447         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14448
14449         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14450         strcat(bookMove, bookHit);
14451         HandleMachineMove(bookMove, &first);
14452     }
14453 }
14454
14455
14456 void
14457 DisplayTwoMachinesTitle ()
14458 {
14459     char buf[MSG_SIZ];
14460     if (appData.matchGames > 0) {
14461         if(appData.tourneyFile[0]) {
14462           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14463                    gameInfo.white, _("vs."), gameInfo.black,
14464                    nextGame+1, appData.matchGames+1,
14465                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14466         } else
14467         if (first.twoMachinesColor[0] == 'w') {
14468           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14469                    gameInfo.white, _("vs."),  gameInfo.black,
14470                    first.matchWins, second.matchWins,
14471                    matchGame - 1 - (first.matchWins + second.matchWins));
14472         } else {
14473           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14474                    gameInfo.white, _("vs."), gameInfo.black,
14475                    second.matchWins, first.matchWins,
14476                    matchGame - 1 - (first.matchWins + second.matchWins));
14477         }
14478     } else {
14479       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14480     }
14481     DisplayTitle(buf);
14482 }
14483
14484 void
14485 SettingsMenuIfReady ()
14486 {
14487   if (second.lastPing != second.lastPong) {
14488     DisplayMessage("", _("Waiting for second chess program"));
14489     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14490     return;
14491   }
14492   ThawUI();
14493   DisplayMessage("", "");
14494   SettingsPopUp(&second);
14495 }
14496
14497 int
14498 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14499 {
14500     char buf[MSG_SIZ];
14501     if (cps->pr == NoProc) {
14502         StartChessProgram(cps);
14503         if (cps->protocolVersion == 1) {
14504           retry();
14505           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14506         } else {
14507           /* kludge: allow timeout for initial "feature" command */
14508           if(retry != TwoMachinesEventIfReady) FreezeUI();
14509           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14510           DisplayMessage("", buf);
14511           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14512         }
14513         return 1;
14514     }
14515     return 0;
14516 }
14517
14518 void
14519 TwoMachinesEvent P((void))
14520 {
14521     int i;
14522     char buf[MSG_SIZ];
14523     ChessProgramState *onmove;
14524     char *bookHit = NULL;
14525     static int stalling = 0;
14526     TimeMark now;
14527     long wait;
14528
14529     if (appData.noChessProgram) return;
14530
14531     switch (gameMode) {
14532       case TwoMachinesPlay:
14533         return;
14534       case MachinePlaysWhite:
14535       case MachinePlaysBlack:
14536         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14537             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14538             return;
14539         }
14540         /* fall through */
14541       case BeginningOfGame:
14542       case PlayFromGameFile:
14543       case EndOfGame:
14544         EditGameEvent();
14545         if (gameMode != EditGame) return;
14546         break;
14547       case EditPosition:
14548         EditPositionDone(TRUE);
14549         break;
14550       case AnalyzeMode:
14551       case AnalyzeFile:
14552         ExitAnalyzeMode();
14553         break;
14554       case EditGame:
14555       default:
14556         break;
14557     }
14558
14559 //    forwardMostMove = currentMove;
14560     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14561     startingEngine = TRUE;
14562
14563     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14564
14565     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14566     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14567       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14568       return;
14569     }
14570     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14571
14572     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14573                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14574         startingEngine = FALSE;
14575         DisplayError("second engine does not play this", 0);
14576         return;
14577     }
14578
14579     if(!stalling) {
14580       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14581       SendToProgram("force\n", &second);
14582       stalling = 1;
14583       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14584       return;
14585     }
14586     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14587     if(appData.matchPause>10000 || appData.matchPause<10)
14588                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14589     wait = SubtractTimeMarks(&now, &pauseStart);
14590     if(wait < appData.matchPause) {
14591         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14592         return;
14593     }
14594     // we are now committed to starting the game
14595     stalling = 0;
14596     DisplayMessage("", "");
14597     if (startedFromSetupPosition) {
14598         SendBoard(&second, backwardMostMove);
14599     if (appData.debugMode) {
14600         fprintf(debugFP, "Two Machines\n");
14601     }
14602     }
14603     for (i = backwardMostMove; i < forwardMostMove; i++) {
14604         SendMoveToProgram(i, &second);
14605     }
14606
14607     gameMode = TwoMachinesPlay;
14608     pausing = startingEngine = FALSE;
14609     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14610     SetGameInfo();
14611     DisplayTwoMachinesTitle();
14612     firstMove = TRUE;
14613     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14614         onmove = &first;
14615     } else {
14616         onmove = &second;
14617     }
14618     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14619     SendToProgram(first.computerString, &first);
14620     if (first.sendName) {
14621       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14622       SendToProgram(buf, &first);
14623     }
14624     SendToProgram(second.computerString, &second);
14625     if (second.sendName) {
14626       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14627       SendToProgram(buf, &second);
14628     }
14629
14630     ResetClocks();
14631     if (!first.sendTime || !second.sendTime) {
14632         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14633         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14634     }
14635     if (onmove->sendTime) {
14636       if (onmove->useColors) {
14637         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14638       }
14639       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14640     }
14641     if (onmove->useColors) {
14642       SendToProgram(onmove->twoMachinesColor, onmove);
14643     }
14644     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14645 //    SendToProgram("go\n", onmove);
14646     onmove->maybeThinking = TRUE;
14647     SetMachineThinkingEnables();
14648
14649     StartClocks();
14650
14651     if(bookHit) { // [HGM] book: simulate book reply
14652         static char bookMove[MSG_SIZ]; // a bit generous?
14653
14654         programStats.nodes = programStats.depth = programStats.time =
14655         programStats.score = programStats.got_only_move = 0;
14656         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14657
14658         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14659         strcat(bookMove, bookHit);
14660         savedMessage = bookMove; // args for deferred call
14661         savedState = onmove;
14662         ScheduleDelayedEvent(DeferredBookMove, 1);
14663     }
14664 }
14665
14666 void
14667 TrainingEvent ()
14668 {
14669     if (gameMode == Training) {
14670       SetTrainingModeOff();
14671       gameMode = PlayFromGameFile;
14672       DisplayMessage("", _("Training mode off"));
14673     } else {
14674       gameMode = Training;
14675       animateTraining = appData.animate;
14676
14677       /* make sure we are not already at the end of the game */
14678       if (currentMove < forwardMostMove) {
14679         SetTrainingModeOn();
14680         DisplayMessage("", _("Training mode on"));
14681       } else {
14682         gameMode = PlayFromGameFile;
14683         DisplayError(_("Already at end of game"), 0);
14684       }
14685     }
14686     ModeHighlight();
14687 }
14688
14689 void
14690 IcsClientEvent ()
14691 {
14692     if (!appData.icsActive) return;
14693     switch (gameMode) {
14694       case IcsPlayingWhite:
14695       case IcsPlayingBlack:
14696       case IcsObserving:
14697       case IcsIdle:
14698       case BeginningOfGame:
14699       case IcsExamining:
14700         return;
14701
14702       case EditGame:
14703         break;
14704
14705       case EditPosition:
14706         EditPositionDone(TRUE);
14707         break;
14708
14709       case AnalyzeMode:
14710       case AnalyzeFile:
14711         ExitAnalyzeMode();
14712         break;
14713
14714       default:
14715         EditGameEvent();
14716         break;
14717     }
14718
14719     gameMode = IcsIdle;
14720     ModeHighlight();
14721     return;
14722 }
14723
14724 void
14725 EditGameEvent ()
14726 {
14727     int i;
14728
14729     switch (gameMode) {
14730       case Training:
14731         SetTrainingModeOff();
14732         break;
14733       case MachinePlaysWhite:
14734       case MachinePlaysBlack:
14735       case BeginningOfGame:
14736         SendToProgram("force\n", &first);
14737         SetUserThinkingEnables();
14738         break;
14739       case PlayFromGameFile:
14740         (void) StopLoadGameTimer();
14741         if (gameFileFP != NULL) {
14742             gameFileFP = NULL;
14743         }
14744         break;
14745       case EditPosition:
14746         EditPositionDone(TRUE);
14747         break;
14748       case AnalyzeMode:
14749       case AnalyzeFile:
14750         ExitAnalyzeMode();
14751         SendToProgram("force\n", &first);
14752         break;
14753       case TwoMachinesPlay:
14754         GameEnds(EndOfFile, NULL, GE_PLAYER);
14755         ResurrectChessProgram();
14756         SetUserThinkingEnables();
14757         break;
14758       case EndOfGame:
14759         ResurrectChessProgram();
14760         break;
14761       case IcsPlayingBlack:
14762       case IcsPlayingWhite:
14763         DisplayError(_("Warning: You are still playing a game"), 0);
14764         break;
14765       case IcsObserving:
14766         DisplayError(_("Warning: You are still observing a game"), 0);
14767         break;
14768       case IcsExamining:
14769         DisplayError(_("Warning: You are still examining a game"), 0);
14770         break;
14771       case IcsIdle:
14772         break;
14773       case EditGame:
14774       default:
14775         return;
14776     }
14777
14778     pausing = FALSE;
14779     StopClocks();
14780     first.offeredDraw = second.offeredDraw = 0;
14781
14782     if (gameMode == PlayFromGameFile) {
14783         whiteTimeRemaining = timeRemaining[0][currentMove];
14784         blackTimeRemaining = timeRemaining[1][currentMove];
14785         DisplayTitle("");
14786     }
14787
14788     if (gameMode == MachinePlaysWhite ||
14789         gameMode == MachinePlaysBlack ||
14790         gameMode == TwoMachinesPlay ||
14791         gameMode == EndOfGame) {
14792         i = forwardMostMove;
14793         while (i > currentMove) {
14794             SendToProgram("undo\n", &first);
14795             i--;
14796         }
14797         if(!adjustedClock) {
14798         whiteTimeRemaining = timeRemaining[0][currentMove];
14799         blackTimeRemaining = timeRemaining[1][currentMove];
14800         DisplayBothClocks();
14801         }
14802         if (whiteFlag || blackFlag) {
14803             whiteFlag = blackFlag = 0;
14804         }
14805         DisplayTitle("");
14806     }
14807
14808     gameMode = EditGame;
14809     ModeHighlight();
14810     SetGameInfo();
14811 }
14812
14813
14814 void
14815 EditPositionEvent ()
14816 {
14817     if (gameMode == EditPosition) {
14818         EditGameEvent();
14819         return;
14820     }
14821
14822     EditGameEvent();
14823     if (gameMode != EditGame) return;
14824
14825     gameMode = EditPosition;
14826     ModeHighlight();
14827     SetGameInfo();
14828     if (currentMove > 0)
14829       CopyBoard(boards[0], boards[currentMove]);
14830
14831     blackPlaysFirst = !WhiteOnMove(currentMove);
14832     ResetClocks();
14833     currentMove = forwardMostMove = backwardMostMove = 0;
14834     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14835     DisplayMove(-1);
14836     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14837 }
14838
14839 void
14840 ExitAnalyzeMode ()
14841 {
14842     /* [DM] icsEngineAnalyze - possible call from other functions */
14843     if (appData.icsEngineAnalyze) {
14844         appData.icsEngineAnalyze = FALSE;
14845
14846         DisplayMessage("",_("Close ICS engine analyze..."));
14847     }
14848     if (first.analysisSupport && first.analyzing) {
14849       SendToBoth("exit\n");
14850       first.analyzing = second.analyzing = FALSE;
14851     }
14852     thinkOutput[0] = NULLCHAR;
14853 }
14854
14855 void
14856 EditPositionDone (Boolean fakeRights)
14857 {
14858     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14859
14860     startedFromSetupPosition = TRUE;
14861     InitChessProgram(&first, FALSE);
14862     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14863       boards[0][EP_STATUS] = EP_NONE;
14864       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14865       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14866         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14867         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14868       } else boards[0][CASTLING][2] = NoRights;
14869       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14870         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14871         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14872       } else boards[0][CASTLING][5] = NoRights;
14873       if(gameInfo.variant == VariantSChess) {
14874         int i;
14875         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14876           boards[0][VIRGIN][i] = 0;
14877           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14878           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14879         }
14880       }
14881     }
14882     SendToProgram("force\n", &first);
14883     if (blackPlaysFirst) {
14884         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14885         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14886         currentMove = forwardMostMove = backwardMostMove = 1;
14887         CopyBoard(boards[1], boards[0]);
14888     } else {
14889         currentMove = forwardMostMove = backwardMostMove = 0;
14890     }
14891     SendBoard(&first, forwardMostMove);
14892     if (appData.debugMode) {
14893         fprintf(debugFP, "EditPosDone\n");
14894     }
14895     DisplayTitle("");
14896     DisplayMessage("", "");
14897     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14898     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14899     gameMode = EditGame;
14900     ModeHighlight();
14901     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14902     ClearHighlights(); /* [AS] */
14903 }
14904
14905 /* Pause for `ms' milliseconds */
14906 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14907 void
14908 TimeDelay (long ms)
14909 {
14910     TimeMark m1, m2;
14911
14912     GetTimeMark(&m1);
14913     do {
14914         GetTimeMark(&m2);
14915     } while (SubtractTimeMarks(&m2, &m1) < ms);
14916 }
14917
14918 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14919 void
14920 SendMultiLineToICS (char *buf)
14921 {
14922     char temp[MSG_SIZ+1], *p;
14923     int len;
14924
14925     len = strlen(buf);
14926     if (len > MSG_SIZ)
14927       len = MSG_SIZ;
14928
14929     strncpy(temp, buf, len);
14930     temp[len] = 0;
14931
14932     p = temp;
14933     while (*p) {
14934         if (*p == '\n' || *p == '\r')
14935           *p = ' ';
14936         ++p;
14937     }
14938
14939     strcat(temp, "\n");
14940     SendToICS(temp);
14941     SendToPlayer(temp, strlen(temp));
14942 }
14943
14944 void
14945 SetWhiteToPlayEvent ()
14946 {
14947     if (gameMode == EditPosition) {
14948         blackPlaysFirst = FALSE;
14949         DisplayBothClocks();    /* works because currentMove is 0 */
14950     } else if (gameMode == IcsExamining) {
14951         SendToICS(ics_prefix);
14952         SendToICS("tomove white\n");
14953     }
14954 }
14955
14956 void
14957 SetBlackToPlayEvent ()
14958 {
14959     if (gameMode == EditPosition) {
14960         blackPlaysFirst = TRUE;
14961         currentMove = 1;        /* kludge */
14962         DisplayBothClocks();
14963         currentMove = 0;
14964     } else if (gameMode == IcsExamining) {
14965         SendToICS(ics_prefix);
14966         SendToICS("tomove black\n");
14967     }
14968 }
14969
14970 void
14971 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14972 {
14973     char buf[MSG_SIZ];
14974     ChessSquare piece = boards[0][y][x];
14975     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14976     static int lastVariant;
14977
14978     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14979
14980     switch (selection) {
14981       case ClearBoard:
14982         CopyBoard(currentBoard, boards[0]);
14983         CopyBoard(menuBoard, initialPosition);
14984         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14985             SendToICS(ics_prefix);
14986             SendToICS("bsetup clear\n");
14987         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14988             SendToICS(ics_prefix);
14989             SendToICS("clearboard\n");
14990         } else {
14991             int nonEmpty = 0;
14992             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14993                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14994                 for (y = 0; y < BOARD_HEIGHT; y++) {
14995                     if (gameMode == IcsExamining) {
14996                         if (boards[currentMove][y][x] != EmptySquare) {
14997                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14998                                     AAA + x, ONE + y);
14999                             SendToICS(buf);
15000                         }
15001                     } else {
15002                         if(boards[0][y][x] != p) nonEmpty++;
15003                         boards[0][y][x] = p;
15004                     }
15005                 }
15006                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
15007             }
15008             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15009                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15010                     ChessSquare p = menuBoard[0][x];
15011                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
15012                     p = menuBoard[BOARD_HEIGHT-1][x];
15013                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
15014                 }
15015                 DisplayMessage("Clicking clock again restores position", "");
15016                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15017                 if(!nonEmpty) { // asked to clear an empty board
15018                     CopyBoard(boards[0], menuBoard);
15019                 } else
15020                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15021                     CopyBoard(boards[0], initialPosition);
15022                 } else
15023                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15024                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15025                     CopyBoard(boards[0], erasedBoard);
15026                 } else
15027                     CopyBoard(erasedBoard, currentBoard);
15028
15029             }
15030         }
15031         if (gameMode == EditPosition) {
15032             DrawPosition(FALSE, boards[0]);
15033         }
15034         break;
15035
15036       case WhitePlay:
15037         SetWhiteToPlayEvent();
15038         break;
15039
15040       case BlackPlay:
15041         SetBlackToPlayEvent();
15042         break;
15043
15044       case EmptySquare:
15045         if (gameMode == IcsExamining) {
15046             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15047             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15048             SendToICS(buf);
15049         } else {
15050             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15051                 if(x == BOARD_LEFT-2) {
15052                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15053                     boards[0][y][1] = 0;
15054                 } else
15055                 if(x == BOARD_RGHT+1) {
15056                     if(y >= gameInfo.holdingsSize) break;
15057                     boards[0][y][BOARD_WIDTH-2] = 0;
15058                 } else break;
15059             }
15060             boards[0][y][x] = EmptySquare;
15061             DrawPosition(FALSE, boards[0]);
15062         }
15063         break;
15064
15065       case PromotePiece:
15066         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15067            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15068             selection = (ChessSquare) (PROMOTED piece);
15069         } else if(piece == EmptySquare) selection = WhiteSilver;
15070         else selection = (ChessSquare)((int)piece - 1);
15071         goto defaultlabel;
15072
15073       case DemotePiece:
15074         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15075            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15076             selection = (ChessSquare) (DEMOTED piece);
15077         } else if(piece == EmptySquare) selection = BlackSilver;
15078         else selection = (ChessSquare)((int)piece + 1);
15079         goto defaultlabel;
15080
15081       case WhiteQueen:
15082       case BlackQueen:
15083         if(gameInfo.variant == VariantShatranj ||
15084            gameInfo.variant == VariantXiangqi  ||
15085            gameInfo.variant == VariantCourier  ||
15086            gameInfo.variant == VariantASEAN    ||
15087            gameInfo.variant == VariantMakruk     )
15088             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15089         goto defaultlabel;
15090
15091       case WhiteKing:
15092       case BlackKing:
15093         if(gameInfo.variant == VariantXiangqi)
15094             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15095         if(gameInfo.variant == VariantKnightmate)
15096             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15097       default:
15098         defaultlabel:
15099         if (gameMode == IcsExamining) {
15100             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15101             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15102                      PieceToChar(selection), AAA + x, ONE + y);
15103             SendToICS(buf);
15104         } else {
15105             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15106                 int n;
15107                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15108                     n = PieceToNumber(selection - BlackPawn);
15109                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15110                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15111                     boards[0][BOARD_HEIGHT-1-n][1]++;
15112                 } else
15113                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15114                     n = PieceToNumber(selection);
15115                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15116                     boards[0][n][BOARD_WIDTH-1] = selection;
15117                     boards[0][n][BOARD_WIDTH-2]++;
15118                 }
15119             } else
15120             boards[0][y][x] = selection;
15121             DrawPosition(TRUE, boards[0]);
15122             ClearHighlights();
15123             fromX = fromY = -1;
15124         }
15125         break;
15126     }
15127 }
15128
15129
15130 void
15131 DropMenuEvent (ChessSquare selection, int x, int y)
15132 {
15133     ChessMove moveType;
15134
15135     switch (gameMode) {
15136       case IcsPlayingWhite:
15137       case MachinePlaysBlack:
15138         if (!WhiteOnMove(currentMove)) {
15139             DisplayMoveError(_("It is Black's turn"));
15140             return;
15141         }
15142         moveType = WhiteDrop;
15143         break;
15144       case IcsPlayingBlack:
15145       case MachinePlaysWhite:
15146         if (WhiteOnMove(currentMove)) {
15147             DisplayMoveError(_("It is White's turn"));
15148             return;
15149         }
15150         moveType = BlackDrop;
15151         break;
15152       case EditGame:
15153         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15154         break;
15155       default:
15156         return;
15157     }
15158
15159     if (moveType == BlackDrop && selection < BlackPawn) {
15160       selection = (ChessSquare) ((int) selection
15161                                  + (int) BlackPawn - (int) WhitePawn);
15162     }
15163     if (boards[currentMove][y][x] != EmptySquare) {
15164         DisplayMoveError(_("That square is occupied"));
15165         return;
15166     }
15167
15168     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15169 }
15170
15171 void
15172 AcceptEvent ()
15173 {
15174     /* Accept a pending offer of any kind from opponent */
15175
15176     if (appData.icsActive) {
15177         SendToICS(ics_prefix);
15178         SendToICS("accept\n");
15179     } else if (cmailMsgLoaded) {
15180         if (currentMove == cmailOldMove &&
15181             commentList[cmailOldMove] != NULL &&
15182             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15183                    "Black offers a draw" : "White offers a draw")) {
15184             TruncateGame();
15185             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15186             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15187         } else {
15188             DisplayError(_("There is no pending offer on this move"), 0);
15189             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15190         }
15191     } else {
15192         /* Not used for offers from chess program */
15193     }
15194 }
15195
15196 void
15197 DeclineEvent ()
15198 {
15199     /* Decline a pending offer of any kind from opponent */
15200
15201     if (appData.icsActive) {
15202         SendToICS(ics_prefix);
15203         SendToICS("decline\n");
15204     } else if (cmailMsgLoaded) {
15205         if (currentMove == cmailOldMove &&
15206             commentList[cmailOldMove] != NULL &&
15207             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15208                    "Black offers a draw" : "White offers a draw")) {
15209 #ifdef NOTDEF
15210             AppendComment(cmailOldMove, "Draw declined", TRUE);
15211             DisplayComment(cmailOldMove - 1, "Draw declined");
15212 #endif /*NOTDEF*/
15213         } else {
15214             DisplayError(_("There is no pending offer on this move"), 0);
15215         }
15216     } else {
15217         /* Not used for offers from chess program */
15218     }
15219 }
15220
15221 void
15222 RematchEvent ()
15223 {
15224     /* Issue ICS rematch command */
15225     if (appData.icsActive) {
15226         SendToICS(ics_prefix);
15227         SendToICS("rematch\n");
15228     }
15229 }
15230
15231 void
15232 CallFlagEvent ()
15233 {
15234     /* Call your opponent's flag (claim a win on time) */
15235     if (appData.icsActive) {
15236         SendToICS(ics_prefix);
15237         SendToICS("flag\n");
15238     } else {
15239         switch (gameMode) {
15240           default:
15241             return;
15242           case MachinePlaysWhite:
15243             if (whiteFlag) {
15244                 if (blackFlag)
15245                   GameEnds(GameIsDrawn, "Both players ran out of time",
15246                            GE_PLAYER);
15247                 else
15248                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15249             } else {
15250                 DisplayError(_("Your opponent is not out of time"), 0);
15251             }
15252             break;
15253           case MachinePlaysBlack:
15254             if (blackFlag) {
15255                 if (whiteFlag)
15256                   GameEnds(GameIsDrawn, "Both players ran out of time",
15257                            GE_PLAYER);
15258                 else
15259                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15260             } else {
15261                 DisplayError(_("Your opponent is not out of time"), 0);
15262             }
15263             break;
15264         }
15265     }
15266 }
15267
15268 void
15269 ClockClick (int which)
15270 {       // [HGM] code moved to back-end from winboard.c
15271         if(which) { // black clock
15272           if (gameMode == EditPosition || gameMode == IcsExamining) {
15273             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15274             SetBlackToPlayEvent();
15275           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15276           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15277           } else if (shiftKey) {
15278             AdjustClock(which, -1);
15279           } else if (gameMode == IcsPlayingWhite ||
15280                      gameMode == MachinePlaysBlack) {
15281             CallFlagEvent();
15282           }
15283         } else { // white clock
15284           if (gameMode == EditPosition || gameMode == IcsExamining) {
15285             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15286             SetWhiteToPlayEvent();
15287           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15288           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15289           } else if (shiftKey) {
15290             AdjustClock(which, -1);
15291           } else if (gameMode == IcsPlayingBlack ||
15292                    gameMode == MachinePlaysWhite) {
15293             CallFlagEvent();
15294           }
15295         }
15296 }
15297
15298 void
15299 DrawEvent ()
15300 {
15301     /* Offer draw or accept pending draw offer from opponent */
15302
15303     if (appData.icsActive) {
15304         /* Note: tournament rules require draw offers to be
15305            made after you make your move but before you punch
15306            your clock.  Currently ICS doesn't let you do that;
15307            instead, you immediately punch your clock after making
15308            a move, but you can offer a draw at any time. */
15309
15310         SendToICS(ics_prefix);
15311         SendToICS("draw\n");
15312         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15313     } else if (cmailMsgLoaded) {
15314         if (currentMove == cmailOldMove &&
15315             commentList[cmailOldMove] != NULL &&
15316             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15317                    "Black offers a draw" : "White offers a draw")) {
15318             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15319             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15320         } else if (currentMove == cmailOldMove + 1) {
15321             char *offer = WhiteOnMove(cmailOldMove) ?
15322               "White offers a draw" : "Black offers a draw";
15323             AppendComment(currentMove, offer, TRUE);
15324             DisplayComment(currentMove - 1, offer);
15325             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15326         } else {
15327             DisplayError(_("You must make your move before offering a draw"), 0);
15328             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15329         }
15330     } else if (first.offeredDraw) {
15331         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15332     } else {
15333         if (first.sendDrawOffers) {
15334             SendToProgram("draw\n", &first);
15335             userOfferedDraw = TRUE;
15336         }
15337     }
15338 }
15339
15340 void
15341 AdjournEvent ()
15342 {
15343     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15344
15345     if (appData.icsActive) {
15346         SendToICS(ics_prefix);
15347         SendToICS("adjourn\n");
15348     } else {
15349         /* Currently GNU Chess doesn't offer or accept Adjourns */
15350     }
15351 }
15352
15353
15354 void
15355 AbortEvent ()
15356 {
15357     /* Offer Abort or accept pending Abort offer from opponent */
15358
15359     if (appData.icsActive) {
15360         SendToICS(ics_prefix);
15361         SendToICS("abort\n");
15362     } else {
15363         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15364     }
15365 }
15366
15367 void
15368 ResignEvent ()
15369 {
15370     /* Resign.  You can do this even if it's not your turn. */
15371
15372     if (appData.icsActive) {
15373         SendToICS(ics_prefix);
15374         SendToICS("resign\n");
15375     } else {
15376         switch (gameMode) {
15377           case MachinePlaysWhite:
15378             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15379             break;
15380           case MachinePlaysBlack:
15381             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15382             break;
15383           case EditGame:
15384             if (cmailMsgLoaded) {
15385                 TruncateGame();
15386                 if (WhiteOnMove(cmailOldMove)) {
15387                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15388                 } else {
15389                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15390                 }
15391                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15392             }
15393             break;
15394           default:
15395             break;
15396         }
15397     }
15398 }
15399
15400
15401 void
15402 StopObservingEvent ()
15403 {
15404     /* Stop observing current games */
15405     SendToICS(ics_prefix);
15406     SendToICS("unobserve\n");
15407 }
15408
15409 void
15410 StopExaminingEvent ()
15411 {
15412     /* Stop observing current game */
15413     SendToICS(ics_prefix);
15414     SendToICS("unexamine\n");
15415 }
15416
15417 void
15418 ForwardInner (int target)
15419 {
15420     int limit; int oldSeekGraphUp = seekGraphUp;
15421
15422     if (appData.debugMode)
15423         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15424                 target, currentMove, forwardMostMove);
15425
15426     if (gameMode == EditPosition)
15427       return;
15428
15429     seekGraphUp = FALSE;
15430     MarkTargetSquares(1);
15431
15432     if (gameMode == PlayFromGameFile && !pausing)
15433       PauseEvent();
15434
15435     if (gameMode == IcsExamining && pausing)
15436       limit = pauseExamForwardMostMove;
15437     else
15438       limit = forwardMostMove;
15439
15440     if (target > limit) target = limit;
15441
15442     if (target > 0 && moveList[target - 1][0]) {
15443         int fromX, fromY, toX, toY;
15444         toX = moveList[target - 1][2] - AAA;
15445         toY = moveList[target - 1][3] - ONE;
15446         if (moveList[target - 1][1] == '@') {
15447             if (appData.highlightLastMove) {
15448                 SetHighlights(-1, -1, toX, toY);
15449             }
15450         } else {
15451             fromX = moveList[target - 1][0] - AAA;
15452             fromY = moveList[target - 1][1] - ONE;
15453             if (target == currentMove + 1) {
15454                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15455             }
15456             if (appData.highlightLastMove) {
15457                 SetHighlights(fromX, fromY, toX, toY);
15458             }
15459         }
15460     }
15461     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15462         gameMode == Training || gameMode == PlayFromGameFile ||
15463         gameMode == AnalyzeFile) {
15464         while (currentMove < target) {
15465             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15466             SendMoveToProgram(currentMove++, &first);
15467         }
15468     } else {
15469         currentMove = target;
15470     }
15471
15472     if (gameMode == EditGame || gameMode == EndOfGame) {
15473         whiteTimeRemaining = timeRemaining[0][currentMove];
15474         blackTimeRemaining = timeRemaining[1][currentMove];
15475     }
15476     DisplayBothClocks();
15477     DisplayMove(currentMove - 1);
15478     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15479     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15480     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15481         DisplayComment(currentMove - 1, commentList[currentMove]);
15482     }
15483     ClearMap(); // [HGM] exclude: invalidate map
15484 }
15485
15486
15487 void
15488 ForwardEvent ()
15489 {
15490     if (gameMode == IcsExamining && !pausing) {
15491         SendToICS(ics_prefix);
15492         SendToICS("forward\n");
15493     } else {
15494         ForwardInner(currentMove + 1);
15495     }
15496 }
15497
15498 void
15499 ToEndEvent ()
15500 {
15501     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15502         /* to optimze, we temporarily turn off analysis mode while we feed
15503          * the remaining moves to the engine. Otherwise we get analysis output
15504          * after each move.
15505          */
15506         if (first.analysisSupport) {
15507           SendToProgram("exit\nforce\n", &first);
15508           first.analyzing = FALSE;
15509         }
15510     }
15511
15512     if (gameMode == IcsExamining && !pausing) {
15513         SendToICS(ics_prefix);
15514         SendToICS("forward 999999\n");
15515     } else {
15516         ForwardInner(forwardMostMove);
15517     }
15518
15519     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15520         /* we have fed all the moves, so reactivate analysis mode */
15521         SendToProgram("analyze\n", &first);
15522         first.analyzing = TRUE;
15523         /*first.maybeThinking = TRUE;*/
15524         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15525     }
15526 }
15527
15528 void
15529 BackwardInner (int target)
15530 {
15531     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15532
15533     if (appData.debugMode)
15534         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15535                 target, currentMove, forwardMostMove);
15536
15537     if (gameMode == EditPosition) return;
15538     seekGraphUp = FALSE;
15539     MarkTargetSquares(1);
15540     if (currentMove <= backwardMostMove) {
15541         ClearHighlights();
15542         DrawPosition(full_redraw, boards[currentMove]);
15543         return;
15544     }
15545     if (gameMode == PlayFromGameFile && !pausing)
15546       PauseEvent();
15547
15548     if (moveList[target][0]) {
15549         int fromX, fromY, toX, toY;
15550         toX = moveList[target][2] - AAA;
15551         toY = moveList[target][3] - ONE;
15552         if (moveList[target][1] == '@') {
15553             if (appData.highlightLastMove) {
15554                 SetHighlights(-1, -1, toX, toY);
15555             }
15556         } else {
15557             fromX = moveList[target][0] - AAA;
15558             fromY = moveList[target][1] - ONE;
15559             if (target == currentMove - 1) {
15560                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15561             }
15562             if (appData.highlightLastMove) {
15563                 SetHighlights(fromX, fromY, toX, toY);
15564             }
15565         }
15566     }
15567     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15568         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15569         while (currentMove > target) {
15570             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15571                 // null move cannot be undone. Reload program with move history before it.
15572                 int i;
15573                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15574                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15575                 }
15576                 SendBoard(&first, i);
15577               if(second.analyzing) SendBoard(&second, i);
15578                 for(currentMove=i; currentMove<target; currentMove++) {
15579                     SendMoveToProgram(currentMove, &first);
15580                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15581                 }
15582                 break;
15583             }
15584             SendToBoth("undo\n");
15585             currentMove--;
15586         }
15587     } else {
15588         currentMove = target;
15589     }
15590
15591     if (gameMode == EditGame || gameMode == EndOfGame) {
15592         whiteTimeRemaining = timeRemaining[0][currentMove];
15593         blackTimeRemaining = timeRemaining[1][currentMove];
15594     }
15595     DisplayBothClocks();
15596     DisplayMove(currentMove - 1);
15597     DrawPosition(full_redraw, boards[currentMove]);
15598     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15599     // [HGM] PV info: routine tests if comment empty
15600     DisplayComment(currentMove - 1, commentList[currentMove]);
15601     ClearMap(); // [HGM] exclude: invalidate map
15602 }
15603
15604 void
15605 BackwardEvent ()
15606 {
15607     if (gameMode == IcsExamining && !pausing) {
15608         SendToICS(ics_prefix);
15609         SendToICS("backward\n");
15610     } else {
15611         BackwardInner(currentMove - 1);
15612     }
15613 }
15614
15615 void
15616 ToStartEvent ()
15617 {
15618     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15619         /* to optimize, we temporarily turn off analysis mode while we undo
15620          * all the moves. Otherwise we get analysis output after each undo.
15621          */
15622         if (first.analysisSupport) {
15623           SendToProgram("exit\nforce\n", &first);
15624           first.analyzing = FALSE;
15625         }
15626     }
15627
15628     if (gameMode == IcsExamining && !pausing) {
15629         SendToICS(ics_prefix);
15630         SendToICS("backward 999999\n");
15631     } else {
15632         BackwardInner(backwardMostMove);
15633     }
15634
15635     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15636         /* we have fed all the moves, so reactivate analysis mode */
15637         SendToProgram("analyze\n", &first);
15638         first.analyzing = TRUE;
15639         /*first.maybeThinking = TRUE;*/
15640         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15641     }
15642 }
15643
15644 void
15645 ToNrEvent (int to)
15646 {
15647   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15648   if (to >= forwardMostMove) to = forwardMostMove;
15649   if (to <= backwardMostMove) to = backwardMostMove;
15650   if (to < currentMove) {
15651     BackwardInner(to);
15652   } else {
15653     ForwardInner(to);
15654   }
15655 }
15656
15657 void
15658 RevertEvent (Boolean annotate)
15659 {
15660     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15661         return;
15662     }
15663     if (gameMode != IcsExamining) {
15664         DisplayError(_("You are not examining a game"), 0);
15665         return;
15666     }
15667     if (pausing) {
15668         DisplayError(_("You can't revert while pausing"), 0);
15669         return;
15670     }
15671     SendToICS(ics_prefix);
15672     SendToICS("revert\n");
15673 }
15674
15675 void
15676 RetractMoveEvent ()
15677 {
15678     switch (gameMode) {
15679       case MachinePlaysWhite:
15680       case MachinePlaysBlack:
15681         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15682             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15683             return;
15684         }
15685         if (forwardMostMove < 2) return;
15686         currentMove = forwardMostMove = forwardMostMove - 2;
15687         whiteTimeRemaining = timeRemaining[0][currentMove];
15688         blackTimeRemaining = timeRemaining[1][currentMove];
15689         DisplayBothClocks();
15690         DisplayMove(currentMove - 1);
15691         ClearHighlights();/*!! could figure this out*/
15692         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15693         SendToProgram("remove\n", &first);
15694         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15695         break;
15696
15697       case BeginningOfGame:
15698       default:
15699         break;
15700
15701       case IcsPlayingWhite:
15702       case IcsPlayingBlack:
15703         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15704             SendToICS(ics_prefix);
15705             SendToICS("takeback 2\n");
15706         } else {
15707             SendToICS(ics_prefix);
15708             SendToICS("takeback 1\n");
15709         }
15710         break;
15711     }
15712 }
15713
15714 void
15715 MoveNowEvent ()
15716 {
15717     ChessProgramState *cps;
15718
15719     switch (gameMode) {
15720       case MachinePlaysWhite:
15721         if (!WhiteOnMove(forwardMostMove)) {
15722             DisplayError(_("It is your turn"), 0);
15723             return;
15724         }
15725         cps = &first;
15726         break;
15727       case MachinePlaysBlack:
15728         if (WhiteOnMove(forwardMostMove)) {
15729             DisplayError(_("It is your turn"), 0);
15730             return;
15731         }
15732         cps = &first;
15733         break;
15734       case TwoMachinesPlay:
15735         if (WhiteOnMove(forwardMostMove) ==
15736             (first.twoMachinesColor[0] == 'w')) {
15737             cps = &first;
15738         } else {
15739             cps = &second;
15740         }
15741         break;
15742       case BeginningOfGame:
15743       default:
15744         return;
15745     }
15746     SendToProgram("?\n", cps);
15747 }
15748
15749 void
15750 TruncateGameEvent ()
15751 {
15752     EditGameEvent();
15753     if (gameMode != EditGame) return;
15754     TruncateGame();
15755 }
15756
15757 void
15758 TruncateGame ()
15759 {
15760     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15761     if (forwardMostMove > currentMove) {
15762         if (gameInfo.resultDetails != NULL) {
15763             free(gameInfo.resultDetails);
15764             gameInfo.resultDetails = NULL;
15765             gameInfo.result = GameUnfinished;
15766         }
15767         forwardMostMove = currentMove;
15768         HistorySet(parseList, backwardMostMove, forwardMostMove,
15769                    currentMove-1);
15770     }
15771 }
15772
15773 void
15774 HintEvent ()
15775 {
15776     if (appData.noChessProgram) return;
15777     switch (gameMode) {
15778       case MachinePlaysWhite:
15779         if (WhiteOnMove(forwardMostMove)) {
15780             DisplayError(_("Wait until your turn."), 0);
15781             return;
15782         }
15783         break;
15784       case BeginningOfGame:
15785       case MachinePlaysBlack:
15786         if (!WhiteOnMove(forwardMostMove)) {
15787             DisplayError(_("Wait until your turn."), 0);
15788             return;
15789         }
15790         break;
15791       default:
15792         DisplayError(_("No hint available"), 0);
15793         return;
15794     }
15795     SendToProgram("hint\n", &first);
15796     hintRequested = TRUE;
15797 }
15798
15799 void
15800 CreateBookEvent ()
15801 {
15802     ListGame * lg = (ListGame *) gameList.head;
15803     FILE *f, *g;
15804     int nItem;
15805     static int secondTime = FALSE;
15806
15807     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15808         DisplayError(_("Game list not loaded or empty"), 0);
15809         return;
15810     }
15811
15812     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15813         fclose(g);
15814         secondTime++;
15815         DisplayNote(_("Book file exists! Try again for overwrite."));
15816         return;
15817     }
15818
15819     creatingBook = TRUE;
15820     secondTime = FALSE;
15821
15822     /* Get list size */
15823     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15824         LoadGame(f, nItem, "", TRUE);
15825         AddGameToBook(TRUE);
15826         lg = (ListGame *) lg->node.succ;
15827     }
15828
15829     creatingBook = FALSE;
15830     FlushBook();
15831 }
15832
15833 void
15834 BookEvent ()
15835 {
15836     if (appData.noChessProgram) return;
15837     switch (gameMode) {
15838       case MachinePlaysWhite:
15839         if (WhiteOnMove(forwardMostMove)) {
15840             DisplayError(_("Wait until your turn."), 0);
15841             return;
15842         }
15843         break;
15844       case BeginningOfGame:
15845       case MachinePlaysBlack:
15846         if (!WhiteOnMove(forwardMostMove)) {
15847             DisplayError(_("Wait until your turn."), 0);
15848             return;
15849         }
15850         break;
15851       case EditPosition:
15852         EditPositionDone(TRUE);
15853         break;
15854       case TwoMachinesPlay:
15855         return;
15856       default:
15857         break;
15858     }
15859     SendToProgram("bk\n", &first);
15860     bookOutput[0] = NULLCHAR;
15861     bookRequested = TRUE;
15862 }
15863
15864 void
15865 AboutGameEvent ()
15866 {
15867     char *tags = PGNTags(&gameInfo);
15868     TagsPopUp(tags, CmailMsg());
15869     free(tags);
15870 }
15871
15872 /* end button procedures */
15873
15874 void
15875 PrintPosition (FILE *fp, int move)
15876 {
15877     int i, j;
15878
15879     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15880         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15881             char c = PieceToChar(boards[move][i][j]);
15882             fputc(c == 'x' ? '.' : c, fp);
15883             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15884         }
15885     }
15886     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15887       fprintf(fp, "white to play\n");
15888     else
15889       fprintf(fp, "black to play\n");
15890 }
15891
15892 void
15893 PrintOpponents (FILE *fp)
15894 {
15895     if (gameInfo.white != NULL) {
15896         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15897     } else {
15898         fprintf(fp, "\n");
15899     }
15900 }
15901
15902 /* Find last component of program's own name, using some heuristics */
15903 void
15904 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15905 {
15906     char *p, *q, c;
15907     int local = (strcmp(host, "localhost") == 0);
15908     while (!local && (p = strchr(prog, ';')) != NULL) {
15909         p++;
15910         while (*p == ' ') p++;
15911         prog = p;
15912     }
15913     if (*prog == '"' || *prog == '\'') {
15914         q = strchr(prog + 1, *prog);
15915     } else {
15916         q = strchr(prog, ' ');
15917     }
15918     if (q == NULL) q = prog + strlen(prog);
15919     p = q;
15920     while (p >= prog && *p != '/' && *p != '\\') p--;
15921     p++;
15922     if(p == prog && *p == '"') p++;
15923     c = *q; *q = 0;
15924     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15925     memcpy(buf, p, q - p);
15926     buf[q - p] = NULLCHAR;
15927     if (!local) {
15928         strcat(buf, "@");
15929         strcat(buf, host);
15930     }
15931 }
15932
15933 char *
15934 TimeControlTagValue ()
15935 {
15936     char buf[MSG_SIZ];
15937     if (!appData.clockMode) {
15938       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15939     } else if (movesPerSession > 0) {
15940       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15941     } else if (timeIncrement == 0) {
15942       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15943     } else {
15944       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15945     }
15946     return StrSave(buf);
15947 }
15948
15949 void
15950 SetGameInfo ()
15951 {
15952     /* This routine is used only for certain modes */
15953     VariantClass v = gameInfo.variant;
15954     ChessMove r = GameUnfinished;
15955     char *p = NULL;
15956
15957     if(keepInfo) return;
15958
15959     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15960         r = gameInfo.result;
15961         p = gameInfo.resultDetails;
15962         gameInfo.resultDetails = NULL;
15963     }
15964     ClearGameInfo(&gameInfo);
15965     gameInfo.variant = v;
15966
15967     switch (gameMode) {
15968       case MachinePlaysWhite:
15969         gameInfo.event = StrSave( appData.pgnEventHeader );
15970         gameInfo.site = StrSave(HostName());
15971         gameInfo.date = PGNDate();
15972         gameInfo.round = StrSave("-");
15973         gameInfo.white = StrSave(first.tidy);
15974         gameInfo.black = StrSave(UserName());
15975         gameInfo.timeControl = TimeControlTagValue();
15976         break;
15977
15978       case MachinePlaysBlack:
15979         gameInfo.event = StrSave( appData.pgnEventHeader );
15980         gameInfo.site = StrSave(HostName());
15981         gameInfo.date = PGNDate();
15982         gameInfo.round = StrSave("-");
15983         gameInfo.white = StrSave(UserName());
15984         gameInfo.black = StrSave(first.tidy);
15985         gameInfo.timeControl = TimeControlTagValue();
15986         break;
15987
15988       case TwoMachinesPlay:
15989         gameInfo.event = StrSave( appData.pgnEventHeader );
15990         gameInfo.site = StrSave(HostName());
15991         gameInfo.date = PGNDate();
15992         if (roundNr > 0) {
15993             char buf[MSG_SIZ];
15994             snprintf(buf, MSG_SIZ, "%d", roundNr);
15995             gameInfo.round = StrSave(buf);
15996         } else {
15997             gameInfo.round = StrSave("-");
15998         }
15999         if (first.twoMachinesColor[0] == 'w') {
16000             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16001             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16002         } else {
16003             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16004             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16005         }
16006         gameInfo.timeControl = TimeControlTagValue();
16007         break;
16008
16009       case EditGame:
16010         gameInfo.event = StrSave("Edited game");
16011         gameInfo.site = StrSave(HostName());
16012         gameInfo.date = PGNDate();
16013         gameInfo.round = StrSave("-");
16014         gameInfo.white = StrSave("-");
16015         gameInfo.black = StrSave("-");
16016         gameInfo.result = r;
16017         gameInfo.resultDetails = p;
16018         break;
16019
16020       case EditPosition:
16021         gameInfo.event = StrSave("Edited position");
16022         gameInfo.site = StrSave(HostName());
16023         gameInfo.date = PGNDate();
16024         gameInfo.round = StrSave("-");
16025         gameInfo.white = StrSave("-");
16026         gameInfo.black = StrSave("-");
16027         break;
16028
16029       case IcsPlayingWhite:
16030       case IcsPlayingBlack:
16031       case IcsObserving:
16032       case IcsExamining:
16033         break;
16034
16035       case PlayFromGameFile:
16036         gameInfo.event = StrSave("Game from non-PGN file");
16037         gameInfo.site = StrSave(HostName());
16038         gameInfo.date = PGNDate();
16039         gameInfo.round = StrSave("-");
16040         gameInfo.white = StrSave("?");
16041         gameInfo.black = StrSave("?");
16042         break;
16043
16044       default:
16045         break;
16046     }
16047 }
16048
16049 void
16050 ReplaceComment (int index, char *text)
16051 {
16052     int len;
16053     char *p;
16054     float score;
16055
16056     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16057        pvInfoList[index-1].depth == len &&
16058        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16059        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16060     while (*text == '\n') text++;
16061     len = strlen(text);
16062     while (len > 0 && text[len - 1] == '\n') len--;
16063
16064     if (commentList[index] != NULL)
16065       free(commentList[index]);
16066
16067     if (len == 0) {
16068         commentList[index] = NULL;
16069         return;
16070     }
16071   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16072       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16073       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16074     commentList[index] = (char *) malloc(len + 2);
16075     strncpy(commentList[index], text, len);
16076     commentList[index][len] = '\n';
16077     commentList[index][len + 1] = NULLCHAR;
16078   } else {
16079     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16080     char *p;
16081     commentList[index] = (char *) malloc(len + 7);
16082     safeStrCpy(commentList[index], "{\n", 3);
16083     safeStrCpy(commentList[index]+2, text, len+1);
16084     commentList[index][len+2] = NULLCHAR;
16085     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16086     strcat(commentList[index], "\n}\n");
16087   }
16088 }
16089
16090 void
16091 CrushCRs (char *text)
16092 {
16093   char *p = text;
16094   char *q = text;
16095   char ch;
16096
16097   do {
16098     ch = *p++;
16099     if (ch == '\r') continue;
16100     *q++ = ch;
16101   } while (ch != '\0');
16102 }
16103
16104 void
16105 AppendComment (int index, char *text, Boolean addBraces)
16106 /* addBraces  tells if we should add {} */
16107 {
16108     int oldlen, len;
16109     char *old;
16110
16111 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16112     if(addBraces == 3) addBraces = 0; else // force appending literally
16113     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16114
16115     CrushCRs(text);
16116     while (*text == '\n') text++;
16117     len = strlen(text);
16118     while (len > 0 && text[len - 1] == '\n') len--;
16119     text[len] = NULLCHAR;
16120
16121     if (len == 0) return;
16122
16123     if (commentList[index] != NULL) {
16124       Boolean addClosingBrace = addBraces;
16125         old = commentList[index];
16126         oldlen = strlen(old);
16127         while(commentList[index][oldlen-1] ==  '\n')
16128           commentList[index][--oldlen] = NULLCHAR;
16129         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16130         safeStrCpy(commentList[index], old, oldlen + len + 6);
16131         free(old);
16132         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16133         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16134           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16135           while (*text == '\n') { text++; len--; }
16136           commentList[index][--oldlen] = NULLCHAR;
16137       }
16138         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16139         else          strcat(commentList[index], "\n");
16140         strcat(commentList[index], text);
16141         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16142         else          strcat(commentList[index], "\n");
16143     } else {
16144         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16145         if(addBraces)
16146           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16147         else commentList[index][0] = NULLCHAR;
16148         strcat(commentList[index], text);
16149         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16150         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16151     }
16152 }
16153
16154 static char *
16155 FindStr (char * text, char * sub_text)
16156 {
16157     char * result = strstr( text, sub_text );
16158
16159     if( result != NULL ) {
16160         result += strlen( sub_text );
16161     }
16162
16163     return result;
16164 }
16165
16166 /* [AS] Try to extract PV info from PGN comment */
16167 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16168 char *
16169 GetInfoFromComment (int index, char * text)
16170 {
16171     char * sep = text, *p;
16172
16173     if( text != NULL && index > 0 ) {
16174         int score = 0;
16175         int depth = 0;
16176         int time = -1, sec = 0, deci;
16177         char * s_eval = FindStr( text, "[%eval " );
16178         char * s_emt = FindStr( text, "[%emt " );
16179 #if 0
16180         if( s_eval != NULL || s_emt != NULL ) {
16181 #else
16182         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16183 #endif
16184             /* New style */
16185             char delim;
16186
16187             if( s_eval != NULL ) {
16188                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16189                     return text;
16190                 }
16191
16192                 if( delim != ']' ) {
16193                     return text;
16194                 }
16195             }
16196
16197             if( s_emt != NULL ) {
16198             }
16199                 return text;
16200         }
16201         else {
16202             /* We expect something like: [+|-]nnn.nn/dd */
16203             int score_lo = 0;
16204
16205             if(*text != '{') return text; // [HGM] braces: must be normal comment
16206
16207             sep = strchr( text, '/' );
16208             if( sep == NULL || sep < (text+4) ) {
16209                 return text;
16210             }
16211
16212             p = text;
16213             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16214             if(p[1] == '(') { // comment starts with PV
16215                p = strchr(p, ')'); // locate end of PV
16216                if(p == NULL || sep < p+5) return text;
16217                // at this point we have something like "{(.*) +0.23/6 ..."
16218                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16219                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16220                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16221             }
16222             time = -1; sec = -1; deci = -1;
16223             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16224                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16225                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16226                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16227                 return text;
16228             }
16229
16230             if( score_lo < 0 || score_lo >= 100 ) {
16231                 return text;
16232             }
16233
16234             if(sec >= 0) time = 600*time + 10*sec; else
16235             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16236
16237             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16238
16239             /* [HGM] PV time: now locate end of PV info */
16240             while( *++sep >= '0' && *sep <= '9'); // strip depth
16241             if(time >= 0)
16242             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16243             if(sec >= 0)
16244             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16245             if(deci >= 0)
16246             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16247             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16248         }
16249
16250         if( depth <= 0 ) {
16251             return text;
16252         }
16253
16254         if( time < 0 ) {
16255             time = -1;
16256         }
16257
16258         pvInfoList[index-1].depth = depth;
16259         pvInfoList[index-1].score = score;
16260         pvInfoList[index-1].time  = 10*time; // centi-sec
16261         if(*sep == '}') *sep = 0; else *--sep = '{';
16262         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16263     }
16264     return sep;
16265 }
16266
16267 void
16268 SendToProgram (char *message, ChessProgramState *cps)
16269 {
16270     int count, outCount, error;
16271     char buf[MSG_SIZ];
16272
16273     if (cps->pr == NoProc) return;
16274     Attention(cps);
16275
16276     if (appData.debugMode) {
16277         TimeMark now;
16278         GetTimeMark(&now);
16279         fprintf(debugFP, "%ld >%-6s: %s",
16280                 SubtractTimeMarks(&now, &programStartTime),
16281                 cps->which, message);
16282         if(serverFP)
16283             fprintf(serverFP, "%ld >%-6s: %s",
16284                 SubtractTimeMarks(&now, &programStartTime),
16285                 cps->which, message), fflush(serverFP);
16286     }
16287
16288     count = strlen(message);
16289     outCount = OutputToProcess(cps->pr, message, count, &error);
16290     if (outCount < count && !exiting
16291                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16292       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16293       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16294         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16295             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16296                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16297                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16298                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16299             } else {
16300                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16301                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16302                 gameInfo.result = res;
16303             }
16304             gameInfo.resultDetails = StrSave(buf);
16305         }
16306         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16307         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16308     }
16309 }
16310
16311 void
16312 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16313 {
16314     char *end_str;
16315     char buf[MSG_SIZ];
16316     ChessProgramState *cps = (ChessProgramState *)closure;
16317
16318     if (isr != cps->isr) return; /* Killed intentionally */
16319     if (count <= 0) {
16320         if (count == 0) {
16321             RemoveInputSource(cps->isr);
16322             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16323                     _(cps->which), cps->program);
16324             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16325             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16326                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16327                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16328                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16329                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16330                 } else {
16331                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16332                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16333                     gameInfo.result = res;
16334                 }
16335                 gameInfo.resultDetails = StrSave(buf);
16336             }
16337             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16338             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16339         } else {
16340             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16341                     _(cps->which), cps->program);
16342             RemoveInputSource(cps->isr);
16343
16344             /* [AS] Program is misbehaving badly... kill it */
16345             if( count == -2 ) {
16346                 DestroyChildProcess( cps->pr, 9 );
16347                 cps->pr = NoProc;
16348             }
16349
16350             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16351         }
16352         return;
16353     }
16354
16355     if ((end_str = strchr(message, '\r')) != NULL)
16356       *end_str = NULLCHAR;
16357     if ((end_str = strchr(message, '\n')) != NULL)
16358       *end_str = NULLCHAR;
16359
16360     if (appData.debugMode) {
16361         TimeMark now; int print = 1;
16362         char *quote = ""; char c; int i;
16363
16364         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16365                 char start = message[0];
16366                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16367                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16368                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16369                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16370                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16371                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16372                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16373                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16374                    sscanf(message, "hint: %c", &c)!=1 &&
16375                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16376                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16377                     print = (appData.engineComments >= 2);
16378                 }
16379                 message[0] = start; // restore original message
16380         }
16381         if(print) {
16382                 GetTimeMark(&now);
16383                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16384                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16385                         quote,
16386                         message);
16387                 if(serverFP)
16388                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16389                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16390                         quote,
16391                         message), fflush(serverFP);
16392         }
16393     }
16394
16395     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16396     if (appData.icsEngineAnalyze) {
16397         if (strstr(message, "whisper") != NULL ||
16398              strstr(message, "kibitz") != NULL ||
16399             strstr(message, "tellics") != NULL) return;
16400     }
16401
16402     HandleMachineMove(message, cps);
16403 }
16404
16405
16406 void
16407 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16408 {
16409     char buf[MSG_SIZ];
16410     int seconds;
16411
16412     if( timeControl_2 > 0 ) {
16413         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16414             tc = timeControl_2;
16415         }
16416     }
16417     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16418     inc /= cps->timeOdds;
16419     st  /= cps->timeOdds;
16420
16421     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16422
16423     if (st > 0) {
16424       /* Set exact time per move, normally using st command */
16425       if (cps->stKludge) {
16426         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16427         seconds = st % 60;
16428         if (seconds == 0) {
16429           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16430         } else {
16431           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16432         }
16433       } else {
16434         snprintf(buf, MSG_SIZ, "st %d\n", st);
16435       }
16436     } else {
16437       /* Set conventional or incremental time control, using level command */
16438       if (seconds == 0) {
16439         /* Note old gnuchess bug -- minutes:seconds used to not work.
16440            Fixed in later versions, but still avoid :seconds
16441            when seconds is 0. */
16442         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16443       } else {
16444         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16445                  seconds, inc/1000.);
16446       }
16447     }
16448     SendToProgram(buf, cps);
16449
16450     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16451     /* Orthogonally, limit search to given depth */
16452     if (sd > 0) {
16453       if (cps->sdKludge) {
16454         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16455       } else {
16456         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16457       }
16458       SendToProgram(buf, cps);
16459     }
16460
16461     if(cps->nps >= 0) { /* [HGM] nps */
16462         if(cps->supportsNPS == FALSE)
16463           cps->nps = -1; // don't use if engine explicitly says not supported!
16464         else {
16465           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16466           SendToProgram(buf, cps);
16467         }
16468     }
16469 }
16470
16471 ChessProgramState *
16472 WhitePlayer ()
16473 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16474 {
16475     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16476        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16477         return &second;
16478     return &first;
16479 }
16480
16481 void
16482 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16483 {
16484     char message[MSG_SIZ];
16485     long time, otime;
16486
16487     /* Note: this routine must be called when the clocks are stopped
16488        or when they have *just* been set or switched; otherwise
16489        it will be off by the time since the current tick started.
16490     */
16491     if (machineWhite) {
16492         time = whiteTimeRemaining / 10;
16493         otime = blackTimeRemaining / 10;
16494     } else {
16495         time = blackTimeRemaining / 10;
16496         otime = whiteTimeRemaining / 10;
16497     }
16498     /* [HGM] translate opponent's time by time-odds factor */
16499     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16500
16501     if (time <= 0) time = 1;
16502     if (otime <= 0) otime = 1;
16503
16504     snprintf(message, MSG_SIZ, "time %ld\n", time);
16505     SendToProgram(message, cps);
16506
16507     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16508     SendToProgram(message, cps);
16509 }
16510
16511 char *
16512 EngineDefinedVariant (ChessProgramState *cps, int n)
16513 {   // return name of n-th unknown variant that engine supports
16514     static char buf[MSG_SIZ];
16515     char *p, *s = cps->variants;
16516     if(!s) return NULL;
16517     do { // parse string from variants feature
16518       VariantClass v;
16519         p = strchr(s, ',');
16520         if(p) *p = NULLCHAR;
16521       v = StringToVariant(s);
16522       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16523         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16524             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16525         }
16526         if(p) *p++ = ',';
16527         if(n < 0) return buf;
16528     } while(s = p);
16529     return NULL;
16530 }
16531
16532 int
16533 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16534 {
16535   char buf[MSG_SIZ];
16536   int len = strlen(name);
16537   int val;
16538
16539   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16540     (*p) += len + 1;
16541     sscanf(*p, "%d", &val);
16542     *loc = (val != 0);
16543     while (**p && **p != ' ')
16544       (*p)++;
16545     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16546     SendToProgram(buf, cps);
16547     return TRUE;
16548   }
16549   return FALSE;
16550 }
16551
16552 int
16553 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16554 {
16555   char buf[MSG_SIZ];
16556   int len = strlen(name);
16557   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16558     (*p) += len + 1;
16559     sscanf(*p, "%d", loc);
16560     while (**p && **p != ' ') (*p)++;
16561     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16562     SendToProgram(buf, cps);
16563     return TRUE;
16564   }
16565   return FALSE;
16566 }
16567
16568 int
16569 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16570 {
16571   char buf[MSG_SIZ];
16572   int len = strlen(name);
16573   if (strncmp((*p), name, len) == 0
16574       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16575     (*p) += len + 2;
16576     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16577     sscanf(*p, "%[^\"]", *loc);
16578     while (**p && **p != '\"') (*p)++;
16579     if (**p == '\"') (*p)++;
16580     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16581     SendToProgram(buf, cps);
16582     return TRUE;
16583   }
16584   return FALSE;
16585 }
16586
16587 int
16588 ParseOption (Option *opt, ChessProgramState *cps)
16589 // [HGM] options: process the string that defines an engine option, and determine
16590 // name, type, default value, and allowed value range
16591 {
16592         char *p, *q, buf[MSG_SIZ];
16593         int n, min = (-1)<<31, max = 1<<31, def;
16594
16595         if(p = strstr(opt->name, " -spin ")) {
16596             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16597             if(max < min) max = min; // enforce consistency
16598             if(def < min) def = min;
16599             if(def > max) def = max;
16600             opt->value = def;
16601             opt->min = min;
16602             opt->max = max;
16603             opt->type = Spin;
16604         } else if((p = strstr(opt->name, " -slider "))) {
16605             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16606             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16607             if(max < min) max = min; // enforce consistency
16608             if(def < min) def = min;
16609             if(def > max) def = max;
16610             opt->value = def;
16611             opt->min = min;
16612             opt->max = max;
16613             opt->type = Spin; // Slider;
16614         } else if((p = strstr(opt->name, " -string "))) {
16615             opt->textValue = p+9;
16616             opt->type = TextBox;
16617         } else if((p = strstr(opt->name, " -file "))) {
16618             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16619             opt->textValue = p+7;
16620             opt->type = FileName; // FileName;
16621         } else if((p = strstr(opt->name, " -path "))) {
16622             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16623             opt->textValue = p+7;
16624             opt->type = PathName; // PathName;
16625         } else if(p = strstr(opt->name, " -check ")) {
16626             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16627             opt->value = (def != 0);
16628             opt->type = CheckBox;
16629         } else if(p = strstr(opt->name, " -combo ")) {
16630             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16631             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16632             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16633             opt->value = n = 0;
16634             while(q = StrStr(q, " /// ")) {
16635                 n++; *q = 0;    // count choices, and null-terminate each of them
16636                 q += 5;
16637                 if(*q == '*') { // remember default, which is marked with * prefix
16638                     q++;
16639                     opt->value = n;
16640                 }
16641                 cps->comboList[cps->comboCnt++] = q;
16642             }
16643             cps->comboList[cps->comboCnt++] = NULL;
16644             opt->max = n + 1;
16645             opt->type = ComboBox;
16646         } else if(p = strstr(opt->name, " -button")) {
16647             opt->type = Button;
16648         } else if(p = strstr(opt->name, " -save")) {
16649             opt->type = SaveButton;
16650         } else return FALSE;
16651         *p = 0; // terminate option name
16652         // now look if the command-line options define a setting for this engine option.
16653         if(cps->optionSettings && cps->optionSettings[0])
16654             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16655         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16656           snprintf(buf, MSG_SIZ, "option %s", p);
16657                 if(p = strstr(buf, ",")) *p = 0;
16658                 if(q = strchr(buf, '=')) switch(opt->type) {
16659                     case ComboBox:
16660                         for(n=0; n<opt->max; n++)
16661                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16662                         break;
16663                     case TextBox:
16664                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16665                         break;
16666                     case Spin:
16667                     case CheckBox:
16668                         opt->value = atoi(q+1);
16669                     default:
16670                         break;
16671                 }
16672                 strcat(buf, "\n");
16673                 SendToProgram(buf, cps);
16674         }
16675         return TRUE;
16676 }
16677
16678 void
16679 FeatureDone (ChessProgramState *cps, int val)
16680 {
16681   DelayedEventCallback cb = GetDelayedEvent();
16682   if ((cb == InitBackEnd3 && cps == &first) ||
16683       (cb == SettingsMenuIfReady && cps == &second) ||
16684       (cb == LoadEngine) ||
16685       (cb == TwoMachinesEventIfReady)) {
16686     CancelDelayedEvent();
16687     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16688   }
16689   cps->initDone = val;
16690   if(val) cps->reload = FALSE;
16691 }
16692
16693 /* Parse feature command from engine */
16694 void
16695 ParseFeatures (char *args, ChessProgramState *cps)
16696 {
16697   char *p = args;
16698   char *q = NULL;
16699   int val;
16700   char buf[MSG_SIZ];
16701
16702   for (;;) {
16703     while (*p == ' ') p++;
16704     if (*p == NULLCHAR) return;
16705
16706     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16707     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16708     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16709     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16710     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16711     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16712     if (BoolFeature(&p, "reuse", &val, cps)) {
16713       /* Engine can disable reuse, but can't enable it if user said no */
16714       if (!val) cps->reuse = FALSE;
16715       continue;
16716     }
16717     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16718     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16719       if (gameMode == TwoMachinesPlay) {
16720         DisplayTwoMachinesTitle();
16721       } else {
16722         DisplayTitle("");
16723       }
16724       continue;
16725     }
16726     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16727     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16728     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16729     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16730     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16731     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16732     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16733     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16734     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16735     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16736     if (IntFeature(&p, "done", &val, cps)) {
16737       FeatureDone(cps, val);
16738       continue;
16739     }
16740     /* Added by Tord: */
16741     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16742     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16743     /* End of additions by Tord */
16744
16745     /* [HGM] added features: */
16746     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16747     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16748     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16749     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16750     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16751     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16752     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16753     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16754         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16755         FREE(cps->option[cps->nrOptions].name);
16756         cps->option[cps->nrOptions].name = q; q = NULL;
16757         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16758           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16759             SendToProgram(buf, cps);
16760             continue;
16761         }
16762         if(cps->nrOptions >= MAX_OPTIONS) {
16763             cps->nrOptions--;
16764             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16765             DisplayError(buf, 0);
16766         }
16767         continue;
16768     }
16769     /* End of additions by HGM */
16770
16771     /* unknown feature: complain and skip */
16772     q = p;
16773     while (*q && *q != '=') q++;
16774     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16775     SendToProgram(buf, cps);
16776     p = q;
16777     if (*p == '=') {
16778       p++;
16779       if (*p == '\"') {
16780         p++;
16781         while (*p && *p != '\"') p++;
16782         if (*p == '\"') p++;
16783       } else {
16784         while (*p && *p != ' ') p++;
16785       }
16786     }
16787   }
16788
16789 }
16790
16791 void
16792 PeriodicUpdatesEvent (int newState)
16793 {
16794     if (newState == appData.periodicUpdates)
16795       return;
16796
16797     appData.periodicUpdates=newState;
16798
16799     /* Display type changes, so update it now */
16800 //    DisplayAnalysis();
16801
16802     /* Get the ball rolling again... */
16803     if (newState) {
16804         AnalysisPeriodicEvent(1);
16805         StartAnalysisClock();
16806     }
16807 }
16808
16809 void
16810 PonderNextMoveEvent (int newState)
16811 {
16812     if (newState == appData.ponderNextMove) return;
16813     if (gameMode == EditPosition) EditPositionDone(TRUE);
16814     if (newState) {
16815         SendToProgram("hard\n", &first);
16816         if (gameMode == TwoMachinesPlay) {
16817             SendToProgram("hard\n", &second);
16818         }
16819     } else {
16820         SendToProgram("easy\n", &first);
16821         thinkOutput[0] = NULLCHAR;
16822         if (gameMode == TwoMachinesPlay) {
16823             SendToProgram("easy\n", &second);
16824         }
16825     }
16826     appData.ponderNextMove = newState;
16827 }
16828
16829 void
16830 NewSettingEvent (int option, int *feature, char *command, int value)
16831 {
16832     char buf[MSG_SIZ];
16833
16834     if (gameMode == EditPosition) EditPositionDone(TRUE);
16835     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16836     if(feature == NULL || *feature) SendToProgram(buf, &first);
16837     if (gameMode == TwoMachinesPlay) {
16838         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16839     }
16840 }
16841
16842 void
16843 ShowThinkingEvent ()
16844 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16845 {
16846     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16847     int newState = appData.showThinking
16848         // [HGM] thinking: other features now need thinking output as well
16849         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16850
16851     if (oldState == newState) return;
16852     oldState = newState;
16853     if (gameMode == EditPosition) EditPositionDone(TRUE);
16854     if (oldState) {
16855         SendToProgram("post\n", &first);
16856         if (gameMode == TwoMachinesPlay) {
16857             SendToProgram("post\n", &second);
16858         }
16859     } else {
16860         SendToProgram("nopost\n", &first);
16861         thinkOutput[0] = NULLCHAR;
16862         if (gameMode == TwoMachinesPlay) {
16863             SendToProgram("nopost\n", &second);
16864         }
16865     }
16866 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16867 }
16868
16869 void
16870 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16871 {
16872   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16873   if (pr == NoProc) return;
16874   AskQuestion(title, question, replyPrefix, pr);
16875 }
16876
16877 void
16878 TypeInEvent (char firstChar)
16879 {
16880     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16881         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16882         gameMode == AnalyzeMode || gameMode == EditGame ||
16883         gameMode == EditPosition || gameMode == IcsExamining ||
16884         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16885         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16886                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16887                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16888         gameMode == Training) PopUpMoveDialog(firstChar);
16889 }
16890
16891 void
16892 TypeInDoneEvent (char *move)
16893 {
16894         Board board;
16895         int n, fromX, fromY, toX, toY;
16896         char promoChar;
16897         ChessMove moveType;
16898
16899         // [HGM] FENedit
16900         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16901                 EditPositionPasteFEN(move);
16902                 return;
16903         }
16904         // [HGM] movenum: allow move number to be typed in any mode
16905         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16906           ToNrEvent(2*n-1);
16907           return;
16908         }
16909         // undocumented kludge: allow command-line option to be typed in!
16910         // (potentially fatal, and does not implement the effect of the option.)
16911         // should only be used for options that are values on which future decisions will be made,
16912         // and definitely not on options that would be used during initialization.
16913         if(strstr(move, "!!! -") == move) {
16914             ParseArgsFromString(move+4);
16915             return;
16916         }
16917
16918       if (gameMode != EditGame && currentMove != forwardMostMove &&
16919         gameMode != Training) {
16920         DisplayMoveError(_("Displayed move is not current"));
16921       } else {
16922         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16923           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16924         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16925         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16926           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16927           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16928         } else {
16929           DisplayMoveError(_("Could not parse move"));
16930         }
16931       }
16932 }
16933
16934 void
16935 DisplayMove (int moveNumber)
16936 {
16937     char message[MSG_SIZ];
16938     char res[MSG_SIZ];
16939     char cpThinkOutput[MSG_SIZ];
16940
16941     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16942
16943     if (moveNumber == forwardMostMove - 1 ||
16944         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16945
16946         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16947
16948         if (strchr(cpThinkOutput, '\n')) {
16949             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16950         }
16951     } else {
16952         *cpThinkOutput = NULLCHAR;
16953     }
16954
16955     /* [AS] Hide thinking from human user */
16956     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16957         *cpThinkOutput = NULLCHAR;
16958         if( thinkOutput[0] != NULLCHAR ) {
16959             int i;
16960
16961             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16962                 cpThinkOutput[i] = '.';
16963             }
16964             cpThinkOutput[i] = NULLCHAR;
16965             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16966         }
16967     }
16968
16969     if (moveNumber == forwardMostMove - 1 &&
16970         gameInfo.resultDetails != NULL) {
16971         if (gameInfo.resultDetails[0] == NULLCHAR) {
16972           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16973         } else {
16974           snprintf(res, MSG_SIZ, " {%s} %s",
16975                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16976         }
16977     } else {
16978         res[0] = NULLCHAR;
16979     }
16980
16981     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16982         DisplayMessage(res, cpThinkOutput);
16983     } else {
16984       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16985                 WhiteOnMove(moveNumber) ? " " : ".. ",
16986                 parseList[moveNumber], res);
16987         DisplayMessage(message, cpThinkOutput);
16988     }
16989 }
16990
16991 void
16992 DisplayComment (int moveNumber, char *text)
16993 {
16994     char title[MSG_SIZ];
16995
16996     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16997       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16998     } else {
16999       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17000               WhiteOnMove(moveNumber) ? " " : ".. ",
17001               parseList[moveNumber]);
17002     }
17003     if (text != NULL && (appData.autoDisplayComment || commentUp))
17004         CommentPopUp(title, text);
17005 }
17006
17007 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17008  * might be busy thinking or pondering.  It can be omitted if your
17009  * gnuchess is configured to stop thinking immediately on any user
17010  * input.  However, that gnuchess feature depends on the FIONREAD
17011  * ioctl, which does not work properly on some flavors of Unix.
17012  */
17013 void
17014 Attention (ChessProgramState *cps)
17015 {
17016 #if ATTENTION
17017     if (!cps->useSigint) return;
17018     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17019     switch (gameMode) {
17020       case MachinePlaysWhite:
17021       case MachinePlaysBlack:
17022       case TwoMachinesPlay:
17023       case IcsPlayingWhite:
17024       case IcsPlayingBlack:
17025       case AnalyzeMode:
17026       case AnalyzeFile:
17027         /* Skip if we know it isn't thinking */
17028         if (!cps->maybeThinking) return;
17029         if (appData.debugMode)
17030           fprintf(debugFP, "Interrupting %s\n", cps->which);
17031         InterruptChildProcess(cps->pr);
17032         cps->maybeThinking = FALSE;
17033         break;
17034       default:
17035         break;
17036     }
17037 #endif /*ATTENTION*/
17038 }
17039
17040 int
17041 CheckFlags ()
17042 {
17043     if (whiteTimeRemaining <= 0) {
17044         if (!whiteFlag) {
17045             whiteFlag = TRUE;
17046             if (appData.icsActive) {
17047                 if (appData.autoCallFlag &&
17048                     gameMode == IcsPlayingBlack && !blackFlag) {
17049                   SendToICS(ics_prefix);
17050                   SendToICS("flag\n");
17051                 }
17052             } else {
17053                 if (blackFlag) {
17054                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17055                 } else {
17056                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17057                     if (appData.autoCallFlag) {
17058                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17059                         return TRUE;
17060                     }
17061                 }
17062             }
17063         }
17064     }
17065     if (blackTimeRemaining <= 0) {
17066         if (!blackFlag) {
17067             blackFlag = TRUE;
17068             if (appData.icsActive) {
17069                 if (appData.autoCallFlag &&
17070                     gameMode == IcsPlayingWhite && !whiteFlag) {
17071                   SendToICS(ics_prefix);
17072                   SendToICS("flag\n");
17073                 }
17074             } else {
17075                 if (whiteFlag) {
17076                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17077                 } else {
17078                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17079                     if (appData.autoCallFlag) {
17080                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17081                         return TRUE;
17082                     }
17083                 }
17084             }
17085         }
17086     }
17087     return FALSE;
17088 }
17089
17090 void
17091 CheckTimeControl ()
17092 {
17093     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17094         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17095
17096     /*
17097      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17098      */
17099     if ( !WhiteOnMove(forwardMostMove) ) {
17100         /* White made time control */
17101         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17102         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17103         /* [HGM] time odds: correct new time quota for time odds! */
17104                                             / WhitePlayer()->timeOdds;
17105         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17106     } else {
17107         lastBlack -= blackTimeRemaining;
17108         /* Black made time control */
17109         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17110                                             / WhitePlayer()->other->timeOdds;
17111         lastWhite = whiteTimeRemaining;
17112     }
17113 }
17114
17115 void
17116 DisplayBothClocks ()
17117 {
17118     int wom = gameMode == EditPosition ?
17119       !blackPlaysFirst : WhiteOnMove(currentMove);
17120     DisplayWhiteClock(whiteTimeRemaining, wom);
17121     DisplayBlackClock(blackTimeRemaining, !wom);
17122 }
17123
17124
17125 /* Timekeeping seems to be a portability nightmare.  I think everyone
17126    has ftime(), but I'm really not sure, so I'm including some ifdefs
17127    to use other calls if you don't.  Clocks will be less accurate if
17128    you have neither ftime nor gettimeofday.
17129 */
17130
17131 /* VS 2008 requires the #include outside of the function */
17132 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17133 #include <sys/timeb.h>
17134 #endif
17135
17136 /* Get the current time as a TimeMark */
17137 void
17138 GetTimeMark (TimeMark *tm)
17139 {
17140 #if HAVE_GETTIMEOFDAY
17141
17142     struct timeval timeVal;
17143     struct timezone timeZone;
17144
17145     gettimeofday(&timeVal, &timeZone);
17146     tm->sec = (long) timeVal.tv_sec;
17147     tm->ms = (int) (timeVal.tv_usec / 1000L);
17148
17149 #else /*!HAVE_GETTIMEOFDAY*/
17150 #if HAVE_FTIME
17151
17152 // include <sys/timeb.h> / moved to just above start of function
17153     struct timeb timeB;
17154
17155     ftime(&timeB);
17156     tm->sec = (long) timeB.time;
17157     tm->ms = (int) timeB.millitm;
17158
17159 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17160     tm->sec = (long) time(NULL);
17161     tm->ms = 0;
17162 #endif
17163 #endif
17164 }
17165
17166 /* Return the difference in milliseconds between two
17167    time marks.  We assume the difference will fit in a long!
17168 */
17169 long
17170 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17171 {
17172     return 1000L*(tm2->sec - tm1->sec) +
17173            (long) (tm2->ms - tm1->ms);
17174 }
17175
17176
17177 /*
17178  * Code to manage the game clocks.
17179  *
17180  * In tournament play, black starts the clock and then white makes a move.
17181  * We give the human user a slight advantage if he is playing white---the
17182  * clocks don't run until he makes his first move, so it takes zero time.
17183  * Also, we don't account for network lag, so we could get out of sync
17184  * with GNU Chess's clock -- but then, referees are always right.
17185  */
17186
17187 static TimeMark tickStartTM;
17188 static long intendedTickLength;
17189
17190 long
17191 NextTickLength (long timeRemaining)
17192 {
17193     long nominalTickLength, nextTickLength;
17194
17195     if (timeRemaining > 0L && timeRemaining <= 10000L)
17196       nominalTickLength = 100L;
17197     else
17198       nominalTickLength = 1000L;
17199     nextTickLength = timeRemaining % nominalTickLength;
17200     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17201
17202     return nextTickLength;
17203 }
17204
17205 /* Adjust clock one minute up or down */
17206 void
17207 AdjustClock (Boolean which, int dir)
17208 {
17209     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17210     if(which) blackTimeRemaining += 60000*dir;
17211     else      whiteTimeRemaining += 60000*dir;
17212     DisplayBothClocks();
17213     adjustedClock = TRUE;
17214 }
17215
17216 /* Stop clocks and reset to a fresh time control */
17217 void
17218 ResetClocks ()
17219 {
17220     (void) StopClockTimer();
17221     if (appData.icsActive) {
17222         whiteTimeRemaining = blackTimeRemaining = 0;
17223     } else if (searchTime) {
17224         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17225         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17226     } else { /* [HGM] correct new time quote for time odds */
17227         whiteTC = blackTC = fullTimeControlString;
17228         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17229         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17230     }
17231     if (whiteFlag || blackFlag) {
17232         DisplayTitle("");
17233         whiteFlag = blackFlag = FALSE;
17234     }
17235     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17236     DisplayBothClocks();
17237     adjustedClock = FALSE;
17238 }
17239
17240 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17241
17242 /* Decrement running clock by amount of time that has passed */
17243 void
17244 DecrementClocks ()
17245 {
17246     long timeRemaining;
17247     long lastTickLength, fudge;
17248     TimeMark now;
17249
17250     if (!appData.clockMode) return;
17251     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17252
17253     GetTimeMark(&now);
17254
17255     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17256
17257     /* Fudge if we woke up a little too soon */
17258     fudge = intendedTickLength - lastTickLength;
17259     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17260
17261     if (WhiteOnMove(forwardMostMove)) {
17262         if(whiteNPS >= 0) lastTickLength = 0;
17263         timeRemaining = whiteTimeRemaining -= lastTickLength;
17264         if(timeRemaining < 0 && !appData.icsActive) {
17265             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17266             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17267                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17268                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17269             }
17270         }
17271         DisplayWhiteClock(whiteTimeRemaining - fudge,
17272                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17273     } else {
17274         if(blackNPS >= 0) lastTickLength = 0;
17275         timeRemaining = blackTimeRemaining -= lastTickLength;
17276         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17277             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17278             if(suddenDeath) {
17279                 blackStartMove = forwardMostMove;
17280                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17281             }
17282         }
17283         DisplayBlackClock(blackTimeRemaining - fudge,
17284                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17285     }
17286     if (CheckFlags()) return;
17287
17288     if(twoBoards) { // count down secondary board's clocks as well
17289         activePartnerTime -= lastTickLength;
17290         partnerUp = 1;
17291         if(activePartner == 'W')
17292             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17293         else
17294             DisplayBlackClock(activePartnerTime, TRUE);
17295         partnerUp = 0;
17296     }
17297
17298     tickStartTM = now;
17299     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17300     StartClockTimer(intendedTickLength);
17301
17302     /* if the time remaining has fallen below the alarm threshold, sound the
17303      * alarm. if the alarm has sounded and (due to a takeback or time control
17304      * with increment) the time remaining has increased to a level above the
17305      * threshold, reset the alarm so it can sound again.
17306      */
17307
17308     if (appData.icsActive && appData.icsAlarm) {
17309
17310         /* make sure we are dealing with the user's clock */
17311         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17312                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17313            )) return;
17314
17315         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17316             alarmSounded = FALSE;
17317         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17318             PlayAlarmSound();
17319             alarmSounded = TRUE;
17320         }
17321     }
17322 }
17323
17324
17325 /* A player has just moved, so stop the previously running
17326    clock and (if in clock mode) start the other one.
17327    We redisplay both clocks in case we're in ICS mode, because
17328    ICS gives us an update to both clocks after every move.
17329    Note that this routine is called *after* forwardMostMove
17330    is updated, so the last fractional tick must be subtracted
17331    from the color that is *not* on move now.
17332 */
17333 void
17334 SwitchClocks (int newMoveNr)
17335 {
17336     long lastTickLength;
17337     TimeMark now;
17338     int flagged = FALSE;
17339
17340     GetTimeMark(&now);
17341
17342     if (StopClockTimer() && appData.clockMode) {
17343         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17344         if (!WhiteOnMove(forwardMostMove)) {
17345             if(blackNPS >= 0) lastTickLength = 0;
17346             blackTimeRemaining -= lastTickLength;
17347            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17348 //         if(pvInfoList[forwardMostMove].time == -1)
17349                  pvInfoList[forwardMostMove].time =               // use GUI time
17350                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17351         } else {
17352            if(whiteNPS >= 0) lastTickLength = 0;
17353            whiteTimeRemaining -= lastTickLength;
17354            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17355 //         if(pvInfoList[forwardMostMove].time == -1)
17356                  pvInfoList[forwardMostMove].time =
17357                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17358         }
17359         flagged = CheckFlags();
17360     }
17361     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17362     CheckTimeControl();
17363
17364     if (flagged || !appData.clockMode) return;
17365
17366     switch (gameMode) {
17367       case MachinePlaysBlack:
17368       case MachinePlaysWhite:
17369       case BeginningOfGame:
17370         if (pausing) return;
17371         break;
17372
17373       case EditGame:
17374       case PlayFromGameFile:
17375       case IcsExamining:
17376         return;
17377
17378       default:
17379         break;
17380     }
17381
17382     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17383         if(WhiteOnMove(forwardMostMove))
17384              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17385         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17386     }
17387
17388     tickStartTM = now;
17389     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17390       whiteTimeRemaining : blackTimeRemaining);
17391     StartClockTimer(intendedTickLength);
17392 }
17393
17394
17395 /* Stop both clocks */
17396 void
17397 StopClocks ()
17398 {
17399     long lastTickLength;
17400     TimeMark now;
17401
17402     if (!StopClockTimer()) return;
17403     if (!appData.clockMode) return;
17404
17405     GetTimeMark(&now);
17406
17407     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17408     if (WhiteOnMove(forwardMostMove)) {
17409         if(whiteNPS >= 0) lastTickLength = 0;
17410         whiteTimeRemaining -= lastTickLength;
17411         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17412     } else {
17413         if(blackNPS >= 0) lastTickLength = 0;
17414         blackTimeRemaining -= lastTickLength;
17415         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17416     }
17417     CheckFlags();
17418 }
17419
17420 /* Start clock of player on move.  Time may have been reset, so
17421    if clock is already running, stop and restart it. */
17422 void
17423 StartClocks ()
17424 {
17425     (void) StopClockTimer(); /* in case it was running already */
17426     DisplayBothClocks();
17427     if (CheckFlags()) return;
17428
17429     if (!appData.clockMode) return;
17430     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17431
17432     GetTimeMark(&tickStartTM);
17433     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17434       whiteTimeRemaining : blackTimeRemaining);
17435
17436    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17437     whiteNPS = blackNPS = -1;
17438     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17439        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17440         whiteNPS = first.nps;
17441     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17442        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17443         blackNPS = first.nps;
17444     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17445         whiteNPS = second.nps;
17446     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17447         blackNPS = second.nps;
17448     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17449
17450     StartClockTimer(intendedTickLength);
17451 }
17452
17453 char *
17454 TimeString (long ms)
17455 {
17456     long second, minute, hour, day;
17457     char *sign = "";
17458     static char buf[32];
17459
17460     if (ms > 0 && ms <= 9900) {
17461       /* convert milliseconds to tenths, rounding up */
17462       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17463
17464       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17465       return buf;
17466     }
17467
17468     /* convert milliseconds to seconds, rounding up */
17469     /* use floating point to avoid strangeness of integer division
17470        with negative dividends on many machines */
17471     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17472
17473     if (second < 0) {
17474         sign = "-";
17475         second = -second;
17476     }
17477
17478     day = second / (60 * 60 * 24);
17479     second = second % (60 * 60 * 24);
17480     hour = second / (60 * 60);
17481     second = second % (60 * 60);
17482     minute = second / 60;
17483     second = second % 60;
17484
17485     if (day > 0)
17486       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17487               sign, day, hour, minute, second);
17488     else if (hour > 0)
17489       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17490     else
17491       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17492
17493     return buf;
17494 }
17495
17496
17497 /*
17498  * This is necessary because some C libraries aren't ANSI C compliant yet.
17499  */
17500 char *
17501 StrStr (char *string, char *match)
17502 {
17503     int i, length;
17504
17505     length = strlen(match);
17506
17507     for (i = strlen(string) - length; i >= 0; i--, string++)
17508       if (!strncmp(match, string, length))
17509         return string;
17510
17511     return NULL;
17512 }
17513
17514 char *
17515 StrCaseStr (char *string, char *match)
17516 {
17517     int i, j, length;
17518
17519     length = strlen(match);
17520
17521     for (i = strlen(string) - length; i >= 0; i--, string++) {
17522         for (j = 0; j < length; j++) {
17523             if (ToLower(match[j]) != ToLower(string[j]))
17524               break;
17525         }
17526         if (j == length) return string;
17527     }
17528
17529     return NULL;
17530 }
17531
17532 #ifndef _amigados
17533 int
17534 StrCaseCmp (char *s1, char *s2)
17535 {
17536     char c1, c2;
17537
17538     for (;;) {
17539         c1 = ToLower(*s1++);
17540         c2 = ToLower(*s2++);
17541         if (c1 > c2) return 1;
17542         if (c1 < c2) return -1;
17543         if (c1 == NULLCHAR) return 0;
17544     }
17545 }
17546
17547
17548 int
17549 ToLower (int c)
17550 {
17551     return isupper(c) ? tolower(c) : c;
17552 }
17553
17554
17555 int
17556 ToUpper (int c)
17557 {
17558     return islower(c) ? toupper(c) : c;
17559 }
17560 #endif /* !_amigados    */
17561
17562 char *
17563 StrSave (char *s)
17564 {
17565   char *ret;
17566
17567   if ((ret = (char *) malloc(strlen(s) + 1)))
17568     {
17569       safeStrCpy(ret, s, strlen(s)+1);
17570     }
17571   return ret;
17572 }
17573
17574 char *
17575 StrSavePtr (char *s, char **savePtr)
17576 {
17577     if (*savePtr) {
17578         free(*savePtr);
17579     }
17580     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17581       safeStrCpy(*savePtr, s, strlen(s)+1);
17582     }
17583     return(*savePtr);
17584 }
17585
17586 char *
17587 PGNDate ()
17588 {
17589     time_t clock;
17590     struct tm *tm;
17591     char buf[MSG_SIZ];
17592
17593     clock = time((time_t *)NULL);
17594     tm = localtime(&clock);
17595     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17596             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17597     return StrSave(buf);
17598 }
17599
17600
17601 char *
17602 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17603 {
17604     int i, j, fromX, fromY, toX, toY;
17605     int whiteToPlay;
17606     char buf[MSG_SIZ];
17607     char *p, *q;
17608     int emptycount;
17609     ChessSquare piece;
17610
17611     whiteToPlay = (gameMode == EditPosition) ?
17612       !blackPlaysFirst : (move % 2 == 0);
17613     p = buf;
17614
17615     /* Piece placement data */
17616     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17617         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17618         emptycount = 0;
17619         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17620             if (boards[move][i][j] == EmptySquare) {
17621                 emptycount++;
17622             } else { ChessSquare piece = boards[move][i][j];
17623                 if (emptycount > 0) {
17624                     if(emptycount<10) /* [HGM] can be >= 10 */
17625                         *p++ = '0' + emptycount;
17626                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17627                     emptycount = 0;
17628                 }
17629                 if(PieceToChar(piece) == '+') {
17630                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17631                     *p++ = '+';
17632                     piece = (ChessSquare)(DEMOTED piece);
17633                 }
17634                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17635                 if(p[-1] == '~') {
17636                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17637                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17638                     *p++ = '~';
17639                 }
17640             }
17641         }
17642         if (emptycount > 0) {
17643             if(emptycount<10) /* [HGM] can be >= 10 */
17644                 *p++ = '0' + emptycount;
17645             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17646             emptycount = 0;
17647         }
17648         *p++ = '/';
17649     }
17650     *(p - 1) = ' ';
17651
17652     /* [HGM] print Crazyhouse or Shogi holdings */
17653     if( gameInfo.holdingsWidth ) {
17654         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17655         q = p;
17656         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17657             piece = boards[move][i][BOARD_WIDTH-1];
17658             if( piece != EmptySquare )
17659               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17660                   *p++ = PieceToChar(piece);
17661         }
17662         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17663             piece = boards[move][BOARD_HEIGHT-i-1][0];
17664             if( piece != EmptySquare )
17665               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17666                   *p++ = PieceToChar(piece);
17667         }
17668
17669         if( q == p ) *p++ = '-';
17670         *p++ = ']';
17671         *p++ = ' ';
17672     }
17673
17674     /* Active color */
17675     *p++ = whiteToPlay ? 'w' : 'b';
17676     *p++ = ' ';
17677
17678   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17679     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17680   } else {
17681   if(nrCastlingRights) {
17682      q = p;
17683      if(appData.fischerCastling) {
17684        /* [HGM] write directly from rights */
17685            if(boards[move][CASTLING][2] != NoRights &&
17686               boards[move][CASTLING][0] != NoRights   )
17687                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17688            if(boards[move][CASTLING][2] != NoRights &&
17689               boards[move][CASTLING][1] != NoRights   )
17690                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17691            if(boards[move][CASTLING][5] != NoRights &&
17692               boards[move][CASTLING][3] != NoRights   )
17693                 *p++ = boards[move][CASTLING][3] + AAA;
17694            if(boards[move][CASTLING][5] != NoRights &&
17695               boards[move][CASTLING][4] != NoRights   )
17696                 *p++ = boards[move][CASTLING][4] + AAA;
17697      } else {
17698
17699         /* [HGM] write true castling rights */
17700         if( nrCastlingRights == 6 ) {
17701             int q, k=0;
17702             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17703                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17704             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17705                  boards[move][CASTLING][2] != NoRights  );
17706             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17707                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17708                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17709                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17710                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17711             }
17712             if(q) *p++ = 'Q';
17713             k = 0;
17714             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17715                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17716             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17717                  boards[move][CASTLING][5] != NoRights  );
17718             if(gameInfo.variant == VariantSChess) {
17719                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17720                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17721                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17722                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17723             }
17724             if(q) *p++ = 'q';
17725         }
17726      }
17727      if (q == p) *p++ = '-'; /* No castling rights */
17728      *p++ = ' ';
17729   }
17730
17731   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17732      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17733      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17734     /* En passant target square */
17735     if (move > backwardMostMove) {
17736         fromX = moveList[move - 1][0] - AAA;
17737         fromY = moveList[move - 1][1] - ONE;
17738         toX = moveList[move - 1][2] - AAA;
17739         toY = moveList[move - 1][3] - ONE;
17740         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17741             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17742             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17743             fromX == toX) {
17744             /* 2-square pawn move just happened */
17745             *p++ = toX + AAA;
17746             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17747         } else {
17748             *p++ = '-';
17749         }
17750     } else if(move == backwardMostMove) {
17751         // [HGM] perhaps we should always do it like this, and forget the above?
17752         if((signed char)boards[move][EP_STATUS] >= 0) {
17753             *p++ = boards[move][EP_STATUS] + AAA;
17754             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17755         } else {
17756             *p++ = '-';
17757         }
17758     } else {
17759         *p++ = '-';
17760     }
17761     *p++ = ' ';
17762   }
17763   }
17764
17765     if(moveCounts)
17766     {   int i = 0, j=move;
17767
17768         /* [HGM] find reversible plies */
17769         if (appData.debugMode) { int k;
17770             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17771             for(k=backwardMostMove; k<=forwardMostMove; k++)
17772                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17773
17774         }
17775
17776         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17777         if( j == backwardMostMove ) i += initialRulePlies;
17778         sprintf(p, "%d ", i);
17779         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17780
17781         /* Fullmove number */
17782         sprintf(p, "%d", (move / 2) + 1);
17783     } else *--p = NULLCHAR;
17784
17785     return StrSave(buf);
17786 }
17787
17788 Boolean
17789 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17790 {
17791     int i, j, k, w=0, subst=0, shuffle=0;
17792     char *p, c;
17793     int emptycount, virgin[BOARD_FILES];
17794     ChessSquare piece;
17795
17796     p = fen;
17797
17798     /* Piece placement data */
17799     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17800         j = 0;
17801         for (;;) {
17802             if (*p == '/' || *p == ' ' || *p == '[' ) {
17803                 if(j > w) w = j;
17804                 emptycount = gameInfo.boardWidth - j;
17805                 while (emptycount--)
17806                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17807                 if (*p == '/') p++;
17808                 else if(autoSize) { // we stumbled unexpectedly into end of board
17809                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17810                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17811                     }
17812                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17813                 }
17814                 break;
17815 #if(BOARD_FILES >= 10)
17816             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17817                 p++; emptycount=10;
17818                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17819                 while (emptycount--)
17820                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17821 #endif
17822             } else if (*p == '*') {
17823                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17824             } else if (isdigit(*p)) {
17825                 emptycount = *p++ - '0';
17826                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17827                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17828                 while (emptycount--)
17829                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17830             } else if (*p == '<') {
17831                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17832                 else if (i != 0 || !shuffle) return FALSE;
17833                 p++;
17834             } else if (shuffle && *p == '>') {
17835                 p++; // for now ignore closing shuffle range, and assume rank-end
17836             } else if (*p == '?') {
17837                 if (j >= gameInfo.boardWidth) return FALSE;
17838                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17839                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17840             } else if (*p == '+' || isalpha(*p)) {
17841                 if (j >= gameInfo.boardWidth) return FALSE;
17842                 if(*p=='+') {
17843                     piece = CharToPiece(*++p);
17844                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17845                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17846                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17847                 } else piece = CharToPiece(*p++);
17848
17849                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17850                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17851                     piece = (ChessSquare) (PROMOTED piece);
17852                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17853                     p++;
17854                 }
17855                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17856             } else {
17857                 return FALSE;
17858             }
17859         }
17860     }
17861     while (*p == '/' || *p == ' ') p++;
17862
17863     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17864
17865     /* [HGM] by default clear Crazyhouse holdings, if present */
17866     if(gameInfo.holdingsWidth) {
17867        for(i=0; i<BOARD_HEIGHT; i++) {
17868            board[i][0]             = EmptySquare; /* black holdings */
17869            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17870            board[i][1]             = (ChessSquare) 0; /* black counts */
17871            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17872        }
17873     }
17874
17875     /* [HGM] look for Crazyhouse holdings here */
17876     while(*p==' ') p++;
17877     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17878         int swap=0, wcnt=0, bcnt=0;
17879         if(*p == '[') p++;
17880         if(*p == '<') swap++, p++;
17881         if(*p == '-' ) p++; /* empty holdings */ else {
17882             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17883             /* if we would allow FEN reading to set board size, we would   */
17884             /* have to add holdings and shift the board read so far here   */
17885             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17886                 p++;
17887                 if((int) piece >= (int) BlackPawn ) {
17888                     i = (int)piece - (int)BlackPawn;
17889                     i = PieceToNumber((ChessSquare)i);
17890                     if( i >= gameInfo.holdingsSize ) return FALSE;
17891                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17892                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17893                     bcnt++;
17894                 } else {
17895                     i = (int)piece - (int)WhitePawn;
17896                     i = PieceToNumber((ChessSquare)i);
17897                     if( i >= gameInfo.holdingsSize ) return FALSE;
17898                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17899                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17900                     wcnt++;
17901                 }
17902             }
17903             if(subst) { // substitute back-rank question marks by holdings pieces
17904                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17905                     int k, m, n = bcnt + 1;
17906                     if(board[0][j] == ClearBoard) {
17907                         if(!wcnt) return FALSE;
17908                         n = rand() % wcnt;
17909                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17910                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17911                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17912                             break;
17913                         }
17914                     }
17915                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17916                         if(!bcnt) return FALSE;
17917                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17918                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17919                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17920                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17921                             break;
17922                         }
17923                     }
17924                 }
17925                 subst = 0;
17926             }
17927         }
17928         if(*p == ']') p++;
17929     }
17930
17931     if(subst) return FALSE; // substitution requested, but no holdings
17932
17933     while(*p == ' ') p++;
17934
17935     /* Active color */
17936     c = *p++;
17937     if(appData.colorNickNames) {
17938       if( c == appData.colorNickNames[0] ) c = 'w'; else
17939       if( c == appData.colorNickNames[1] ) c = 'b';
17940     }
17941     switch (c) {
17942       case 'w':
17943         *blackPlaysFirst = FALSE;
17944         break;
17945       case 'b':
17946         *blackPlaysFirst = TRUE;
17947         break;
17948       default:
17949         return FALSE;
17950     }
17951
17952     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17953     /* return the extra info in global variiables             */
17954
17955     /* set defaults in case FEN is incomplete */
17956     board[EP_STATUS] = EP_UNKNOWN;
17957     for(i=0; i<nrCastlingRights; i++ ) {
17958         board[CASTLING][i] =
17959             appData.fischerCastling ? NoRights : initialRights[i];
17960     }   /* assume possible unless obviously impossible */
17961     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17962     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17963     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17964                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17965     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17966     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17967     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17968                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17969     FENrulePlies = 0;
17970
17971     while(*p==' ') p++;
17972     if(nrCastlingRights) {
17973       int fischer = 0;
17974       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17975       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17976           /* castling indicator present, so default becomes no castlings */
17977           for(i=0; i<nrCastlingRights; i++ ) {
17978                  board[CASTLING][i] = NoRights;
17979           }
17980       }
17981       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17982              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17983              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17984              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17985         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17986
17987         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17988             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17989             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17990         }
17991         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17992             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17993         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17994                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17995         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17996                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17997         switch(c) {
17998           case'K':
17999               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18000               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18001               board[CASTLING][2] = whiteKingFile;
18002               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18003               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18004               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18005               break;
18006           case'Q':
18007               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18008               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18009               board[CASTLING][2] = whiteKingFile;
18010               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18011               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18012               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18013               break;
18014           case'k':
18015               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18016               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18017               board[CASTLING][5] = blackKingFile;
18018               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18019               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18020               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18021               break;
18022           case'q':
18023               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18024               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18025               board[CASTLING][5] = blackKingFile;
18026               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18027               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18028               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18029           case '-':
18030               break;
18031           default: /* FRC castlings */
18032               if(c >= 'a') { /* black rights */
18033                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18034                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18035                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18036                   if(i == BOARD_RGHT) break;
18037                   board[CASTLING][5] = i;
18038                   c -= AAA;
18039                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18040                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18041                   if(c > i)
18042                       board[CASTLING][3] = c;
18043                   else
18044                       board[CASTLING][4] = c;
18045               } else { /* white rights */
18046                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18047                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18048                     if(board[0][i] == WhiteKing) break;
18049                   if(i == BOARD_RGHT) break;
18050                   board[CASTLING][2] = i;
18051                   c -= AAA - 'a' + 'A';
18052                   if(board[0][c] >= WhiteKing) break;
18053                   if(c > i)
18054                       board[CASTLING][0] = c;
18055                   else
18056                       board[CASTLING][1] = c;
18057               }
18058         }
18059       }
18060       for(i=0; i<nrCastlingRights; i++)
18061         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18062       if(gameInfo.variant == VariantSChess)
18063         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18064       if(fischer && shuffle) appData.fischerCastling = TRUE;
18065     if (appData.debugMode) {
18066         fprintf(debugFP, "FEN castling rights:");
18067         for(i=0; i<nrCastlingRights; i++)
18068         fprintf(debugFP, " %d", board[CASTLING][i]);
18069         fprintf(debugFP, "\n");
18070     }
18071
18072       while(*p==' ') p++;
18073     }
18074
18075     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18076
18077     /* read e.p. field in games that know e.p. capture */
18078     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18079        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18080        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18081       if(*p=='-') {
18082         p++; board[EP_STATUS] = EP_NONE;
18083       } else {
18084          char c = *p++ - AAA;
18085
18086          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18087          if(*p >= '0' && *p <='9') p++;
18088          board[EP_STATUS] = c;
18089       }
18090     }
18091
18092
18093     if(sscanf(p, "%d", &i) == 1) {
18094         FENrulePlies = i; /* 50-move ply counter */
18095         /* (The move number is still ignored)    */
18096     }
18097
18098     return TRUE;
18099 }
18100
18101 void
18102 EditPositionPasteFEN (char *fen)
18103 {
18104   if (fen != NULL) {
18105     Board initial_position;
18106
18107     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18108       DisplayError(_("Bad FEN position in clipboard"), 0);
18109       return ;
18110     } else {
18111       int savedBlackPlaysFirst = blackPlaysFirst;
18112       EditPositionEvent();
18113       blackPlaysFirst = savedBlackPlaysFirst;
18114       CopyBoard(boards[0], initial_position);
18115       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18116       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18117       DisplayBothClocks();
18118       DrawPosition(FALSE, boards[currentMove]);
18119     }
18120   }
18121 }
18122
18123 static char cseq[12] = "\\   ";
18124
18125 Boolean
18126 set_cont_sequence (char *new_seq)
18127 {
18128     int len;
18129     Boolean ret;
18130
18131     // handle bad attempts to set the sequence
18132         if (!new_seq)
18133                 return 0; // acceptable error - no debug
18134
18135     len = strlen(new_seq);
18136     ret = (len > 0) && (len < sizeof(cseq));
18137     if (ret)
18138       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18139     else if (appData.debugMode)
18140       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18141     return ret;
18142 }
18143
18144 /*
18145     reformat a source message so words don't cross the width boundary.  internal
18146     newlines are not removed.  returns the wrapped size (no null character unless
18147     included in source message).  If dest is NULL, only calculate the size required
18148     for the dest buffer.  lp argument indicats line position upon entry, and it's
18149     passed back upon exit.
18150 */
18151 int
18152 wrap (char *dest, char *src, int count, int width, int *lp)
18153 {
18154     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18155
18156     cseq_len = strlen(cseq);
18157     old_line = line = *lp;
18158     ansi = len = clen = 0;
18159
18160     for (i=0; i < count; i++)
18161     {
18162         if (src[i] == '\033')
18163             ansi = 1;
18164
18165         // if we hit the width, back up
18166         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18167         {
18168             // store i & len in case the word is too long
18169             old_i = i, old_len = len;
18170
18171             // find the end of the last word
18172             while (i && src[i] != ' ' && src[i] != '\n')
18173             {
18174                 i--;
18175                 len--;
18176             }
18177
18178             // word too long?  restore i & len before splitting it
18179             if ((old_i-i+clen) >= width)
18180             {
18181                 i = old_i;
18182                 len = old_len;
18183             }
18184
18185             // extra space?
18186             if (i && src[i-1] == ' ')
18187                 len--;
18188
18189             if (src[i] != ' ' && src[i] != '\n')
18190             {
18191                 i--;
18192                 if (len)
18193                     len--;
18194             }
18195
18196             // now append the newline and continuation sequence
18197             if (dest)
18198                 dest[len] = '\n';
18199             len++;
18200             if (dest)
18201                 strncpy(dest+len, cseq, cseq_len);
18202             len += cseq_len;
18203             line = cseq_len;
18204             clen = cseq_len;
18205             continue;
18206         }
18207
18208         if (dest)
18209             dest[len] = src[i];
18210         len++;
18211         if (!ansi)
18212             line++;
18213         if (src[i] == '\n')
18214             line = 0;
18215         if (src[i] == 'm')
18216             ansi = 0;
18217     }
18218     if (dest && appData.debugMode)
18219     {
18220         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18221             count, width, line, len, *lp);
18222         show_bytes(debugFP, src, count);
18223         fprintf(debugFP, "\ndest: ");
18224         show_bytes(debugFP, dest, len);
18225         fprintf(debugFP, "\n");
18226     }
18227     *lp = dest ? line : old_line;
18228
18229     return len;
18230 }
18231
18232 // [HGM] vari: routines for shelving variations
18233 Boolean modeRestore = FALSE;
18234
18235 void
18236 PushInner (int firstMove, int lastMove)
18237 {
18238         int i, j, nrMoves = lastMove - firstMove;
18239
18240         // push current tail of game on stack
18241         savedResult[storedGames] = gameInfo.result;
18242         savedDetails[storedGames] = gameInfo.resultDetails;
18243         gameInfo.resultDetails = NULL;
18244         savedFirst[storedGames] = firstMove;
18245         savedLast [storedGames] = lastMove;
18246         savedFramePtr[storedGames] = framePtr;
18247         framePtr -= nrMoves; // reserve space for the boards
18248         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18249             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18250             for(j=0; j<MOVE_LEN; j++)
18251                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18252             for(j=0; j<2*MOVE_LEN; j++)
18253                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18254             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18255             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18256             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18257             pvInfoList[firstMove+i-1].depth = 0;
18258             commentList[framePtr+i] = commentList[firstMove+i];
18259             commentList[firstMove+i] = NULL;
18260         }
18261
18262         storedGames++;
18263         forwardMostMove = firstMove; // truncate game so we can start variation
18264 }
18265
18266 void
18267 PushTail (int firstMove, int lastMove)
18268 {
18269         if(appData.icsActive) { // only in local mode
18270                 forwardMostMove = currentMove; // mimic old ICS behavior
18271                 return;
18272         }
18273         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18274
18275         PushInner(firstMove, lastMove);
18276         if(storedGames == 1) GreyRevert(FALSE);
18277         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18278 }
18279
18280 void
18281 PopInner (Boolean annotate)
18282 {
18283         int i, j, nrMoves;
18284         char buf[8000], moveBuf[20];
18285
18286         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18287         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18288         nrMoves = savedLast[storedGames] - currentMove;
18289         if(annotate) {
18290                 int cnt = 10;
18291                 if(!WhiteOnMove(currentMove))
18292                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18293                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18294                 for(i=currentMove; i<forwardMostMove; i++) {
18295                         if(WhiteOnMove(i))
18296                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18297                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18298                         strcat(buf, moveBuf);
18299                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18300                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18301                 }
18302                 strcat(buf, ")");
18303         }
18304         for(i=1; i<=nrMoves; i++) { // copy last variation back
18305             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18306             for(j=0; j<MOVE_LEN; j++)
18307                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18308             for(j=0; j<2*MOVE_LEN; j++)
18309                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18310             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18311             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18312             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18313             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18314             commentList[currentMove+i] = commentList[framePtr+i];
18315             commentList[framePtr+i] = NULL;
18316         }
18317         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18318         framePtr = savedFramePtr[storedGames];
18319         gameInfo.result = savedResult[storedGames];
18320         if(gameInfo.resultDetails != NULL) {
18321             free(gameInfo.resultDetails);
18322       }
18323         gameInfo.resultDetails = savedDetails[storedGames];
18324         forwardMostMove = currentMove + nrMoves;
18325 }
18326
18327 Boolean
18328 PopTail (Boolean annotate)
18329 {
18330         if(appData.icsActive) return FALSE; // only in local mode
18331         if(!storedGames) return FALSE; // sanity
18332         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18333
18334         PopInner(annotate);
18335         if(currentMove < forwardMostMove) ForwardEvent(); else
18336         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18337
18338         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18339         return TRUE;
18340 }
18341
18342 void
18343 CleanupTail ()
18344 {       // remove all shelved variations
18345         int i;
18346         for(i=0; i<storedGames; i++) {
18347             if(savedDetails[i])
18348                 free(savedDetails[i]);
18349             savedDetails[i] = NULL;
18350         }
18351         for(i=framePtr; i<MAX_MOVES; i++) {
18352                 if(commentList[i]) free(commentList[i]);
18353                 commentList[i] = NULL;
18354         }
18355         framePtr = MAX_MOVES-1;
18356         storedGames = 0;
18357 }
18358
18359 void
18360 LoadVariation (int index, char *text)
18361 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18362         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18363         int level = 0, move;
18364
18365         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18366         // first find outermost bracketing variation
18367         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18368             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18369                 if(*p == '{') wait = '}'; else
18370                 if(*p == '[') wait = ']'; else
18371                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18372                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18373             }
18374             if(*p == wait) wait = NULLCHAR; // closing ]} found
18375             p++;
18376         }
18377         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18378         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18379         end[1] = NULLCHAR; // clip off comment beyond variation
18380         ToNrEvent(currentMove-1);
18381         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18382         // kludge: use ParsePV() to append variation to game
18383         move = currentMove;
18384         ParsePV(start, TRUE, TRUE);
18385         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18386         ClearPremoveHighlights();
18387         CommentPopDown();
18388         ToNrEvent(currentMove+1);
18389 }
18390
18391 void
18392 LoadTheme ()
18393 {
18394     char *p, *q, buf[MSG_SIZ];
18395     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18396         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18397         ParseArgsFromString(buf);
18398         ActivateTheme(TRUE); // also redo colors
18399         return;
18400     }
18401     p = nickName;
18402     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18403     {
18404         int len;
18405         q = appData.themeNames;
18406         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18407       if(appData.useBitmaps) {
18408         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18409                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18410                 appData.liteBackTextureMode,
18411                 appData.darkBackTextureMode );
18412       } else {
18413         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18414                 Col2Text(2),   // lightSquareColor
18415                 Col2Text(3) ); // darkSquareColor
18416       }
18417       if(appData.useBorder) {
18418         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18419                 appData.border);
18420       } else {
18421         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18422       }
18423       if(appData.useFont) {
18424         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18425                 appData.renderPiecesWithFont,
18426                 appData.fontToPieceTable,
18427                 Col2Text(9),    // appData.fontBackColorWhite
18428                 Col2Text(10) ); // appData.fontForeColorBlack
18429       } else {
18430         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18431                 appData.pieceDirectory);
18432         if(!appData.pieceDirectory[0])
18433           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18434                 Col2Text(0),   // whitePieceColor
18435                 Col2Text(1) ); // blackPieceColor
18436       }
18437       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18438                 Col2Text(4),   // highlightSquareColor
18439                 Col2Text(5) ); // premoveHighlightColor
18440         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18441         if(insert != q) insert[-1] = NULLCHAR;
18442         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18443         if(q)   free(q);
18444     }
18445     ActivateTheme(FALSE);
18446 }