Allow adding played move to book
[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 && 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
8718                 if( score > 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 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8763           }
8764         }
8765 #endif
8766
8767         /* [AS] Clear stats for next move */
8768         ClearProgramStats();
8769         thinkOutput[0] = NULLCHAR;
8770         hiddenThinkOutputState = 0;
8771
8772         bookHit = NULL;
8773         if (gameMode == TwoMachinesPlay) {
8774             /* [HGM] relaying draw offers moved to after reception of move */
8775             /* and interpreting offer as claim if it brings draw condition */
8776             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8777                 SendToProgram("draw\n", cps->other);
8778             }
8779             if (cps->other->sendTime) {
8780                 SendTimeRemaining(cps->other,
8781                                   cps->other->twoMachinesColor[0] == 'w');
8782             }
8783             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8784             if (firstMove && !bookHit) {
8785                 firstMove = FALSE;
8786                 if (cps->other->useColors) {
8787                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8788                 }
8789                 SendToProgram("go\n", cps->other);
8790             }
8791             cps->other->maybeThinking = TRUE;
8792         }
8793
8794         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8795
8796         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8797
8798         if (!pausing && appData.ringBellAfterMoves) {
8799             if(!roar) RingBell();
8800         }
8801
8802         /*
8803          * Reenable menu items that were disabled while
8804          * machine was thinking
8805          */
8806         if (gameMode != TwoMachinesPlay)
8807             SetUserThinkingEnables();
8808
8809         // [HGM] book: after book hit opponent has received move and is now in force mode
8810         // force the book reply into it, and then fake that it outputted this move by jumping
8811         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8812         if(bookHit) {
8813                 static char bookMove[MSG_SIZ]; // a bit generous?
8814
8815                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8816                 strcat(bookMove, bookHit);
8817                 message = bookMove;
8818                 cps = cps->other;
8819                 programStats.nodes = programStats.depth = programStats.time =
8820                 programStats.score = programStats.got_only_move = 0;
8821                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8822
8823                 if(cps->lastPing != cps->lastPong) {
8824                     savedMessage = message; // args for deferred call
8825                     savedState = cps;
8826                     ScheduleDelayedEvent(DeferredBookMove, 10);
8827                     return;
8828                 }
8829                 goto FakeBookMove;
8830         }
8831
8832         return;
8833     }
8834
8835     /* Set special modes for chess engines.  Later something general
8836      *  could be added here; for now there is just one kludge feature,
8837      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8838      *  when "xboard" is given as an interactive command.
8839      */
8840     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8841         cps->useSigint = FALSE;
8842         cps->useSigterm = FALSE;
8843     }
8844     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8845       ParseFeatures(message+8, cps);
8846       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8847     }
8848
8849     if (!strncmp(message, "setup ", 6) && 
8850         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8851           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8852                                         ) { // [HGM] allow first engine to define opening position
8853       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8854       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8855       *buf = NULLCHAR;
8856       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8857       if(startedFromSetupPosition) return;
8858       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8859       if(dummy >= 3) {
8860         while(message[s] && message[s++] != ' ');
8861         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8862            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8863             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8864             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8865           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8866           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8867         }
8868       }
8869       ParseFEN(boards[0], &dummy, message+s, FALSE);
8870       DrawPosition(TRUE, boards[0]);
8871       startedFromSetupPosition = TRUE;
8872       return;
8873     }
8874     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8875      * want this, I was asked to put it in, and obliged.
8876      */
8877     if (!strncmp(message, "setboard ", 9)) {
8878         Board initial_position;
8879
8880         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8881
8882         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8883             DisplayError(_("Bad FEN received from engine"), 0);
8884             return ;
8885         } else {
8886            Reset(TRUE, FALSE);
8887            CopyBoard(boards[0], initial_position);
8888            initialRulePlies = FENrulePlies;
8889            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8890            else gameMode = MachinePlaysBlack;
8891            DrawPosition(FALSE, boards[currentMove]);
8892         }
8893         return;
8894     }
8895
8896     /*
8897      * Look for communication commands
8898      */
8899     if (!strncmp(message, "telluser ", 9)) {
8900         if(message[9] == '\\' && message[10] == '\\')
8901             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8902         PlayTellSound();
8903         DisplayNote(message + 9);
8904         return;
8905     }
8906     if (!strncmp(message, "tellusererror ", 14)) {
8907         cps->userError = 1;
8908         if(message[14] == '\\' && message[15] == '\\')
8909             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8910         PlayTellSound();
8911         DisplayError(message + 14, 0);
8912         return;
8913     }
8914     if (!strncmp(message, "tellopponent ", 13)) {
8915       if (appData.icsActive) {
8916         if (loggedOn) {
8917           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8918           SendToICS(buf1);
8919         }
8920       } else {
8921         DisplayNote(message + 13);
8922       }
8923       return;
8924     }
8925     if (!strncmp(message, "tellothers ", 11)) {
8926       if (appData.icsActive) {
8927         if (loggedOn) {
8928           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8929           SendToICS(buf1);
8930         }
8931       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8932       return;
8933     }
8934     if (!strncmp(message, "tellall ", 8)) {
8935       if (appData.icsActive) {
8936         if (loggedOn) {
8937           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8938           SendToICS(buf1);
8939         }
8940       } else {
8941         DisplayNote(message + 8);
8942       }
8943       return;
8944     }
8945     if (strncmp(message, "warning", 7) == 0) {
8946         /* Undocumented feature, use tellusererror in new code */
8947         DisplayError(message, 0);
8948         return;
8949     }
8950     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8951         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8952         strcat(realname, " query");
8953         AskQuestion(realname, buf2, buf1, cps->pr);
8954         return;
8955     }
8956     /* Commands from the engine directly to ICS.  We don't allow these to be
8957      *  sent until we are logged on. Crafty kibitzes have been known to
8958      *  interfere with the login process.
8959      */
8960     if (loggedOn) {
8961         if (!strncmp(message, "tellics ", 8)) {
8962             SendToICS(message + 8);
8963             SendToICS("\n");
8964             return;
8965         }
8966         if (!strncmp(message, "tellicsnoalias ", 15)) {
8967             SendToICS(ics_prefix);
8968             SendToICS(message + 15);
8969             SendToICS("\n");
8970             return;
8971         }
8972         /* The following are for backward compatibility only */
8973         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8974             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8975             SendToICS(ics_prefix);
8976             SendToICS(message);
8977             SendToICS("\n");
8978             return;
8979         }
8980     }
8981     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8982         if(initPing == cps->lastPong) {
8983             if(gameInfo.variant == VariantUnknown) {
8984                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8985                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8986                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8987             }
8988             initPing = -1;
8989         }
8990         return;
8991     }
8992     if(!strncmp(message, "highlight ", 10)) {
8993         if(appData.testLegality && appData.markers) return;
8994         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8995         return;
8996     }
8997     if(!strncmp(message, "click ", 6)) {
8998         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8999         if(appData.testLegality || !appData.oneClick) return;
9000         sscanf(message+6, "%c%d%c", &f, &y, &c);
9001         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9002         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9003         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9004         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9005         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9006         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9007             LeftClick(Release, lastLeftX, lastLeftY);
9008         controlKey  = (c == ',');
9009         LeftClick(Press, x, y);
9010         LeftClick(Release, x, y);
9011         first.highlight = f;
9012         return;
9013     }
9014     /*
9015      * If the move is illegal, cancel it and redraw the board.
9016      * Also deal with other error cases.  Matching is rather loose
9017      * here to accommodate engines written before the spec.
9018      */
9019     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9020         strncmp(message, "Error", 5) == 0) {
9021         if (StrStr(message, "name") ||
9022             StrStr(message, "rating") || StrStr(message, "?") ||
9023             StrStr(message, "result") || StrStr(message, "board") ||
9024             StrStr(message, "bk") || StrStr(message, "computer") ||
9025             StrStr(message, "variant") || StrStr(message, "hint") ||
9026             StrStr(message, "random") || StrStr(message, "depth") ||
9027             StrStr(message, "accepted")) {
9028             return;
9029         }
9030         if (StrStr(message, "protover")) {
9031           /* Program is responding to input, so it's apparently done
9032              initializing, and this error message indicates it is
9033              protocol version 1.  So we don't need to wait any longer
9034              for it to initialize and send feature commands. */
9035           FeatureDone(cps, 1);
9036           cps->protocolVersion = 1;
9037           return;
9038         }
9039         cps->maybeThinking = FALSE;
9040
9041         if (StrStr(message, "draw")) {
9042             /* Program doesn't have "draw" command */
9043             cps->sendDrawOffers = 0;
9044             return;
9045         }
9046         if (cps->sendTime != 1 &&
9047             (StrStr(message, "time") || StrStr(message, "otim"))) {
9048           /* Program apparently doesn't have "time" or "otim" command */
9049           cps->sendTime = 0;
9050           return;
9051         }
9052         if (StrStr(message, "analyze")) {
9053             cps->analysisSupport = FALSE;
9054             cps->analyzing = FALSE;
9055 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9056             EditGameEvent(); // [HGM] try to preserve loaded game
9057             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9058             DisplayError(buf2, 0);
9059             return;
9060         }
9061         if (StrStr(message, "(no matching move)st")) {
9062           /* Special kludge for GNU Chess 4 only */
9063           cps->stKludge = TRUE;
9064           SendTimeControl(cps, movesPerSession, timeControl,
9065                           timeIncrement, appData.searchDepth,
9066                           searchTime);
9067           return;
9068         }
9069         if (StrStr(message, "(no matching move)sd")) {
9070           /* Special kludge for GNU Chess 4 only */
9071           cps->sdKludge = TRUE;
9072           SendTimeControl(cps, movesPerSession, timeControl,
9073                           timeIncrement, appData.searchDepth,
9074                           searchTime);
9075           return;
9076         }
9077         if (!StrStr(message, "llegal")) {
9078             return;
9079         }
9080         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9081             gameMode == IcsIdle) return;
9082         if (forwardMostMove <= backwardMostMove) return;
9083         if (pausing) PauseEvent();
9084       if(appData.forceIllegal) {
9085             // [HGM] illegal: machine refused move; force position after move into it
9086           SendToProgram("force\n", cps);
9087           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9088                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9089                 // when black is to move, while there might be nothing on a2 or black
9090                 // might already have the move. So send the board as if white has the move.
9091                 // But first we must change the stm of the engine, as it refused the last move
9092                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9093                 if(WhiteOnMove(forwardMostMove)) {
9094                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9095                     SendBoard(cps, forwardMostMove); // kludgeless board
9096                 } else {
9097                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9098                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9099                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9100                 }
9101           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9102             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9103                  gameMode == TwoMachinesPlay)
9104               SendToProgram("go\n", cps);
9105             return;
9106       } else
9107         if (gameMode == PlayFromGameFile) {
9108             /* Stop reading this game file */
9109             gameMode = EditGame;
9110             ModeHighlight();
9111         }
9112         /* [HGM] illegal-move claim should forfeit game when Xboard */
9113         /* only passes fully legal moves                            */
9114         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9115             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9116                                 "False illegal-move claim", GE_XBOARD );
9117             return; // do not take back move we tested as valid
9118         }
9119         currentMove = forwardMostMove-1;
9120         DisplayMove(currentMove-1); /* before DisplayMoveError */
9121         SwitchClocks(forwardMostMove-1); // [HGM] race
9122         DisplayBothClocks();
9123         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9124                 parseList[currentMove], _(cps->which));
9125         DisplayMoveError(buf1);
9126         DrawPosition(FALSE, boards[currentMove]);
9127
9128         SetUserThinkingEnables();
9129         return;
9130     }
9131     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9132         /* Program has a broken "time" command that
9133            outputs a string not ending in newline.
9134            Don't use it. */
9135         cps->sendTime = 0;
9136     }
9137
9138     /*
9139      * If chess program startup fails, exit with an error message.
9140      * Attempts to recover here are futile. [HGM] Well, we try anyway
9141      */
9142     if ((StrStr(message, "unknown host") != NULL)
9143         || (StrStr(message, "No remote directory") != NULL)
9144         || (StrStr(message, "not found") != NULL)
9145         || (StrStr(message, "No such file") != NULL)
9146         || (StrStr(message, "can't alloc") != NULL)
9147         || (StrStr(message, "Permission denied") != NULL)) {
9148
9149         cps->maybeThinking = FALSE;
9150         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9151                 _(cps->which), cps->program, cps->host, message);
9152         RemoveInputSource(cps->isr);
9153         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9154             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9155             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9156         }
9157         return;
9158     }
9159
9160     /*
9161      * Look for hint output
9162      */
9163     if (sscanf(message, "Hint: %s", buf1) == 1) {
9164         if (cps == &first && hintRequested) {
9165             hintRequested = FALSE;
9166             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9167                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9168                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9169                                     PosFlags(forwardMostMove),
9170                                     fromY, fromX, toY, toX, promoChar, buf1);
9171                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9172                 DisplayInformation(buf2);
9173             } else {
9174                 /* Hint move could not be parsed!? */
9175               snprintf(buf2, sizeof(buf2),
9176                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9177                         buf1, _(cps->which));
9178                 DisplayError(buf2, 0);
9179             }
9180         } else {
9181           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9182         }
9183         return;
9184     }
9185
9186     /*
9187      * Ignore other messages if game is not in progress
9188      */
9189     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9190         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9191
9192     /*
9193      * look for win, lose, draw, or draw offer
9194      */
9195     if (strncmp(message, "1-0", 3) == 0) {
9196         char *p, *q, *r = "";
9197         p = strchr(message, '{');
9198         if (p) {
9199             q = strchr(p, '}');
9200             if (q) {
9201                 *q = NULLCHAR;
9202                 r = p + 1;
9203             }
9204         }
9205         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9206         return;
9207     } else if (strncmp(message, "0-1", 3) == 0) {
9208         char *p, *q, *r = "";
9209         p = strchr(message, '{');
9210         if (p) {
9211             q = strchr(p, '}');
9212             if (q) {
9213                 *q = NULLCHAR;
9214                 r = p + 1;
9215             }
9216         }
9217         /* Kludge for Arasan 4.1 bug */
9218         if (strcmp(r, "Black resigns") == 0) {
9219             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9220             return;
9221         }
9222         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9223         return;
9224     } else if (strncmp(message, "1/2", 3) == 0) {
9225         char *p, *q, *r = "";
9226         p = strchr(message, '{');
9227         if (p) {
9228             q = strchr(p, '}');
9229             if (q) {
9230                 *q = NULLCHAR;
9231                 r = p + 1;
9232             }
9233         }
9234
9235         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9236         return;
9237
9238     } else if (strncmp(message, "White resign", 12) == 0) {
9239         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9240         return;
9241     } else if (strncmp(message, "Black resign", 12) == 0) {
9242         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9243         return;
9244     } else if (strncmp(message, "White matches", 13) == 0 ||
9245                strncmp(message, "Black matches", 13) == 0   ) {
9246         /* [HGM] ignore GNUShogi noises */
9247         return;
9248     } else if (strncmp(message, "White", 5) == 0 &&
9249                message[5] != '(' &&
9250                StrStr(message, "Black") == NULL) {
9251         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9252         return;
9253     } else if (strncmp(message, "Black", 5) == 0 &&
9254                message[5] != '(') {
9255         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9256         return;
9257     } else if (strcmp(message, "resign") == 0 ||
9258                strcmp(message, "computer resigns") == 0) {
9259         switch (gameMode) {
9260           case MachinePlaysBlack:
9261           case IcsPlayingBlack:
9262             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9263             break;
9264           case MachinePlaysWhite:
9265           case IcsPlayingWhite:
9266             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9267             break;
9268           case TwoMachinesPlay:
9269             if (cps->twoMachinesColor[0] == 'w')
9270               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9271             else
9272               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9273             break;
9274           default:
9275             /* can't happen */
9276             break;
9277         }
9278         return;
9279     } else if (strncmp(message, "opponent mates", 14) == 0) {
9280         switch (gameMode) {
9281           case MachinePlaysBlack:
9282           case IcsPlayingBlack:
9283             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9284             break;
9285           case MachinePlaysWhite:
9286           case IcsPlayingWhite:
9287             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9288             break;
9289           case TwoMachinesPlay:
9290             if (cps->twoMachinesColor[0] == 'w')
9291               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9292             else
9293               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9294             break;
9295           default:
9296             /* can't happen */
9297             break;
9298         }
9299         return;
9300     } else if (strncmp(message, "computer mates", 14) == 0) {
9301         switch (gameMode) {
9302           case MachinePlaysBlack:
9303           case IcsPlayingBlack:
9304             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9305             break;
9306           case MachinePlaysWhite:
9307           case IcsPlayingWhite:
9308             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9309             break;
9310           case TwoMachinesPlay:
9311             if (cps->twoMachinesColor[0] == 'w')
9312               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9313             else
9314               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9315             break;
9316           default:
9317             /* can't happen */
9318             break;
9319         }
9320         return;
9321     } else if (strncmp(message, "checkmate", 9) == 0) {
9322         if (WhiteOnMove(forwardMostMove)) {
9323             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9324         } else {
9325             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9326         }
9327         return;
9328     } else if (strstr(message, "Draw") != NULL ||
9329                strstr(message, "game is a draw") != NULL) {
9330         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9331         return;
9332     } else if (strstr(message, "offer") != NULL &&
9333                strstr(message, "draw") != NULL) {
9334 #if ZIPPY
9335         if (appData.zippyPlay && first.initDone) {
9336             /* Relay offer to ICS */
9337             SendToICS(ics_prefix);
9338             SendToICS("draw\n");
9339         }
9340 #endif
9341         cps->offeredDraw = 2; /* valid until this engine moves twice */
9342         if (gameMode == TwoMachinesPlay) {
9343             if (cps->other->offeredDraw) {
9344                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9345             /* [HGM] in two-machine mode we delay relaying draw offer      */
9346             /* until after we also have move, to see if it is really claim */
9347             }
9348         } else if (gameMode == MachinePlaysWhite ||
9349                    gameMode == MachinePlaysBlack) {
9350           if (userOfferedDraw) {
9351             DisplayInformation(_("Machine accepts your draw offer"));
9352             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9353           } else {
9354             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9355           }
9356         }
9357     }
9358
9359
9360     /*
9361      * Look for thinking output
9362      */
9363     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9364           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9365                                 ) {
9366         int plylev, mvleft, mvtot, curscore, time;
9367         char mvname[MOVE_LEN];
9368         u64 nodes; // [DM]
9369         char plyext;
9370         int ignore = FALSE;
9371         int prefixHint = FALSE;
9372         mvname[0] = NULLCHAR;
9373
9374         switch (gameMode) {
9375           case MachinePlaysBlack:
9376           case IcsPlayingBlack:
9377             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9378             break;
9379           case MachinePlaysWhite:
9380           case IcsPlayingWhite:
9381             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9382             break;
9383           case AnalyzeMode:
9384           case AnalyzeFile:
9385             break;
9386           case IcsObserving: /* [DM] icsEngineAnalyze */
9387             if (!appData.icsEngineAnalyze) ignore = TRUE;
9388             break;
9389           case TwoMachinesPlay:
9390             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9391                 ignore = TRUE;
9392             }
9393             break;
9394           default:
9395             ignore = TRUE;
9396             break;
9397         }
9398
9399         if (!ignore) {
9400             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9401             buf1[0] = NULLCHAR;
9402             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9403                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9404
9405                 if (plyext != ' ' && plyext != '\t') {
9406                     time *= 100;
9407                 }
9408
9409                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9410                 if( cps->scoreIsAbsolute &&
9411                     ( gameMode == MachinePlaysBlack ||
9412                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9413                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9414                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9415                      !WhiteOnMove(currentMove)
9416                     ) )
9417                 {
9418                     curscore = -curscore;
9419                 }
9420
9421                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9422
9423                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9424                         char buf[MSG_SIZ];
9425                         FILE *f;
9426                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9427                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9428                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9429                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9430                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9431                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9432                                 fclose(f);
9433                         }
9434                         else
9435                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9436                           DisplayError(_("failed writing PV"), 0);
9437                 }
9438
9439                 tempStats.depth = plylev;
9440                 tempStats.nodes = nodes;
9441                 tempStats.time = time;
9442                 tempStats.score = curscore;
9443                 tempStats.got_only_move = 0;
9444
9445                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9446                         int ticklen;
9447
9448                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9449                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9450                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9451                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9452                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9453                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9454                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9455                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9456                 }
9457
9458                 /* Buffer overflow protection */
9459                 if (pv[0] != NULLCHAR) {
9460                     if (strlen(pv) >= sizeof(tempStats.movelist)
9461                         && appData.debugMode) {
9462                         fprintf(debugFP,
9463                                 "PV is too long; using the first %u bytes.\n",
9464                                 (unsigned) sizeof(tempStats.movelist) - 1);
9465                     }
9466
9467                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9468                 } else {
9469                     sprintf(tempStats.movelist, " no PV\n");
9470                 }
9471
9472                 if (tempStats.seen_stat) {
9473                     tempStats.ok_to_send = 1;
9474                 }
9475
9476                 if (strchr(tempStats.movelist, '(') != NULL) {
9477                     tempStats.line_is_book = 1;
9478                     tempStats.nr_moves = 0;
9479                     tempStats.moves_left = 0;
9480                 } else {
9481                     tempStats.line_is_book = 0;
9482                 }
9483
9484                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9485                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9486
9487                 SendProgramStatsToFrontend( cps, &tempStats );
9488
9489                 /*
9490                     [AS] Protect the thinkOutput buffer from overflow... this
9491                     is only useful if buf1 hasn't overflowed first!
9492                 */
9493                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9494                          plylev,
9495                          (gameMode == TwoMachinesPlay ?
9496                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9497                          ((double) curscore) / 100.0,
9498                          prefixHint ? lastHint : "",
9499                          prefixHint ? " " : "" );
9500
9501                 if( buf1[0] != NULLCHAR ) {
9502                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9503
9504                     if( strlen(pv) > max_len ) {
9505                         if( appData.debugMode) {
9506                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9507                         }
9508                         pv[max_len+1] = '\0';
9509                     }
9510
9511                     strcat( thinkOutput, pv);
9512                 }
9513
9514                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9515                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9516                     DisplayMove(currentMove - 1);
9517                 }
9518                 return;
9519
9520             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9521                 /* crafty (9.25+) says "(only move) <move>"
9522                  * if there is only 1 legal move
9523                  */
9524                 sscanf(p, "(only move) %s", buf1);
9525                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9526                 sprintf(programStats.movelist, "%s (only move)", buf1);
9527                 programStats.depth = 1;
9528                 programStats.nr_moves = 1;
9529                 programStats.moves_left = 1;
9530                 programStats.nodes = 1;
9531                 programStats.time = 1;
9532                 programStats.got_only_move = 1;
9533
9534                 /* Not really, but we also use this member to
9535                    mean "line isn't going to change" (Crafty
9536                    isn't searching, so stats won't change) */
9537                 programStats.line_is_book = 1;
9538
9539                 SendProgramStatsToFrontend( cps, &programStats );
9540
9541                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9542                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9543                     DisplayMove(currentMove - 1);
9544                 }
9545                 return;
9546             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9547                               &time, &nodes, &plylev, &mvleft,
9548                               &mvtot, mvname) >= 5) {
9549                 /* The stat01: line is from Crafty (9.29+) in response
9550                    to the "." command */
9551                 programStats.seen_stat = 1;
9552                 cps->maybeThinking = TRUE;
9553
9554                 if (programStats.got_only_move || !appData.periodicUpdates)
9555                   return;
9556
9557                 programStats.depth = plylev;
9558                 programStats.time = time;
9559                 programStats.nodes = nodes;
9560                 programStats.moves_left = mvleft;
9561                 programStats.nr_moves = mvtot;
9562                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9563                 programStats.ok_to_send = 1;
9564                 programStats.movelist[0] = '\0';
9565
9566                 SendProgramStatsToFrontend( cps, &programStats );
9567
9568                 return;
9569
9570             } else if (strncmp(message,"++",2) == 0) {
9571                 /* Crafty 9.29+ outputs this */
9572                 programStats.got_fail = 2;
9573                 return;
9574
9575             } else if (strncmp(message,"--",2) == 0) {
9576                 /* Crafty 9.29+ outputs this */
9577                 programStats.got_fail = 1;
9578                 return;
9579
9580             } else if (thinkOutput[0] != NULLCHAR &&
9581                        strncmp(message, "    ", 4) == 0) {
9582                 unsigned message_len;
9583
9584                 p = message;
9585                 while (*p && *p == ' ') p++;
9586
9587                 message_len = strlen( p );
9588
9589                 /* [AS] Avoid buffer overflow */
9590                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9591                     strcat(thinkOutput, " ");
9592                     strcat(thinkOutput, p);
9593                 }
9594
9595                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9596                     strcat(programStats.movelist, " ");
9597                     strcat(programStats.movelist, p);
9598                 }
9599
9600                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9601                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9602                     DisplayMove(currentMove - 1);
9603                 }
9604                 return;
9605             }
9606         }
9607         else {
9608             buf1[0] = NULLCHAR;
9609
9610             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9611                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9612             {
9613                 ChessProgramStats cpstats;
9614
9615                 if (plyext != ' ' && plyext != '\t') {
9616                     time *= 100;
9617                 }
9618
9619                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9620                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9621                     curscore = -curscore;
9622                 }
9623
9624                 cpstats.depth = plylev;
9625                 cpstats.nodes = nodes;
9626                 cpstats.time = time;
9627                 cpstats.score = curscore;
9628                 cpstats.got_only_move = 0;
9629                 cpstats.movelist[0] = '\0';
9630
9631                 if (buf1[0] != NULLCHAR) {
9632                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9633                 }
9634
9635                 cpstats.ok_to_send = 0;
9636                 cpstats.line_is_book = 0;
9637                 cpstats.nr_moves = 0;
9638                 cpstats.moves_left = 0;
9639
9640                 SendProgramStatsToFrontend( cps, &cpstats );
9641             }
9642         }
9643     }
9644 }
9645
9646
9647 /* Parse a game score from the character string "game", and
9648    record it as the history of the current game.  The game
9649    score is NOT assumed to start from the standard position.
9650    The display is not updated in any way.
9651    */
9652 void
9653 ParseGameHistory (char *game)
9654 {
9655     ChessMove moveType;
9656     int fromX, fromY, toX, toY, boardIndex;
9657     char promoChar;
9658     char *p, *q;
9659     char buf[MSG_SIZ];
9660
9661     if (appData.debugMode)
9662       fprintf(debugFP, "Parsing game history: %s\n", game);
9663
9664     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9665     gameInfo.site = StrSave(appData.icsHost);
9666     gameInfo.date = PGNDate();
9667     gameInfo.round = StrSave("-");
9668
9669     /* Parse out names of players */
9670     while (*game == ' ') game++;
9671     p = buf;
9672     while (*game != ' ') *p++ = *game++;
9673     *p = NULLCHAR;
9674     gameInfo.white = StrSave(buf);
9675     while (*game == ' ') game++;
9676     p = buf;
9677     while (*game != ' ' && *game != '\n') *p++ = *game++;
9678     *p = NULLCHAR;
9679     gameInfo.black = StrSave(buf);
9680
9681     /* Parse moves */
9682     boardIndex = blackPlaysFirst ? 1 : 0;
9683     yynewstr(game);
9684     for (;;) {
9685         yyboardindex = boardIndex;
9686         moveType = (ChessMove) Myylex();
9687         switch (moveType) {
9688           case IllegalMove:             /* maybe suicide chess, etc. */
9689   if (appData.debugMode) {
9690     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9691     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9692     setbuf(debugFP, NULL);
9693   }
9694           case WhitePromotion:
9695           case BlackPromotion:
9696           case WhiteNonPromotion:
9697           case BlackNonPromotion:
9698           case NormalMove:
9699           case FirstLeg:
9700           case WhiteCapturesEnPassant:
9701           case BlackCapturesEnPassant:
9702           case WhiteKingSideCastle:
9703           case WhiteQueenSideCastle:
9704           case BlackKingSideCastle:
9705           case BlackQueenSideCastle:
9706           case WhiteKingSideCastleWild:
9707           case WhiteQueenSideCastleWild:
9708           case BlackKingSideCastleWild:
9709           case BlackQueenSideCastleWild:
9710           /* PUSH Fabien */
9711           case WhiteHSideCastleFR:
9712           case WhiteASideCastleFR:
9713           case BlackHSideCastleFR:
9714           case BlackASideCastleFR:
9715           /* POP Fabien */
9716             fromX = currentMoveString[0] - AAA;
9717             fromY = currentMoveString[1] - ONE;
9718             toX = currentMoveString[2] - AAA;
9719             toY = currentMoveString[3] - ONE;
9720             promoChar = currentMoveString[4];
9721             break;
9722           case WhiteDrop:
9723           case BlackDrop:
9724             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9725             fromX = moveType == WhiteDrop ?
9726               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9727             (int) CharToPiece(ToLower(currentMoveString[0]));
9728             fromY = DROP_RANK;
9729             toX = currentMoveString[2] - AAA;
9730             toY = currentMoveString[3] - ONE;
9731             promoChar = NULLCHAR;
9732             break;
9733           case AmbiguousMove:
9734             /* bug? */
9735             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9736   if (appData.debugMode) {
9737     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9738     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9739     setbuf(debugFP, NULL);
9740   }
9741             DisplayError(buf, 0);
9742             return;
9743           case ImpossibleMove:
9744             /* bug? */
9745             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9746   if (appData.debugMode) {
9747     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9748     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9749     setbuf(debugFP, NULL);
9750   }
9751             DisplayError(buf, 0);
9752             return;
9753           case EndOfFile:
9754             if (boardIndex < backwardMostMove) {
9755                 /* Oops, gap.  How did that happen? */
9756                 DisplayError(_("Gap in move list"), 0);
9757                 return;
9758             }
9759             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9760             if (boardIndex > forwardMostMove) {
9761                 forwardMostMove = boardIndex;
9762             }
9763             return;
9764           case ElapsedTime:
9765             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9766                 strcat(parseList[boardIndex-1], " ");
9767                 strcat(parseList[boardIndex-1], yy_text);
9768             }
9769             continue;
9770           case Comment:
9771           case PGNTag:
9772           case NAG:
9773           default:
9774             /* ignore */
9775             continue;
9776           case WhiteWins:
9777           case BlackWins:
9778           case GameIsDrawn:
9779           case GameUnfinished:
9780             if (gameMode == IcsExamining) {
9781                 if (boardIndex < backwardMostMove) {
9782                     /* Oops, gap.  How did that happen? */
9783                     return;
9784                 }
9785                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9786                 return;
9787             }
9788             gameInfo.result = moveType;
9789             p = strchr(yy_text, '{');
9790             if (p == NULL) p = strchr(yy_text, '(');
9791             if (p == NULL) {
9792                 p = yy_text;
9793                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9794             } else {
9795                 q = strchr(p, *p == '{' ? '}' : ')');
9796                 if (q != NULL) *q = NULLCHAR;
9797                 p++;
9798             }
9799             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9800             gameInfo.resultDetails = StrSave(p);
9801             continue;
9802         }
9803         if (boardIndex >= forwardMostMove &&
9804             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9805             backwardMostMove = blackPlaysFirst ? 1 : 0;
9806             return;
9807         }
9808         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9809                                  fromY, fromX, toY, toX, promoChar,
9810                                  parseList[boardIndex]);
9811         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9812         /* currentMoveString is set as a side-effect of yylex */
9813         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9814         strcat(moveList[boardIndex], "\n");
9815         boardIndex++;
9816         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9817         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9818           case MT_NONE:
9819           case MT_STALEMATE:
9820           default:
9821             break;
9822           case MT_CHECK:
9823             if(!IS_SHOGI(gameInfo.variant))
9824                 strcat(parseList[boardIndex - 1], "+");
9825             break;
9826           case MT_CHECKMATE:
9827           case MT_STAINMATE:
9828             strcat(parseList[boardIndex - 1], "#");
9829             break;
9830         }
9831     }
9832 }
9833
9834
9835 /* Apply a move to the given board  */
9836 void
9837 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9838 {
9839   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9840   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9841
9842     /* [HGM] compute & store e.p. status and castling rights for new position */
9843     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9844
9845       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9846       oldEP = (signed char)board[EP_STATUS];
9847       board[EP_STATUS] = EP_NONE;
9848
9849   if (fromY == DROP_RANK) {
9850         /* must be first */
9851         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9852             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9853             return;
9854         }
9855         piece = board[toY][toX] = (ChessSquare) fromX;
9856   } else {
9857 //      ChessSquare victim;
9858       int i;
9859
9860       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9861 //           victim = board[killY][killX],
9862            board[killY][killX] = EmptySquare,
9863            board[EP_STATUS] = EP_CAPTURE;
9864
9865       if( board[toY][toX] != EmptySquare ) {
9866            board[EP_STATUS] = EP_CAPTURE;
9867            if( (fromX != toX || fromY != toY) && // not igui!
9868                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9869                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9870                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9871            }
9872       }
9873
9874       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9875            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9876                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9877       } else
9878       if( board[fromY][fromX] == WhitePawn ) {
9879            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9880                board[EP_STATUS] = EP_PAWN_MOVE;
9881            if( toY-fromY==2) {
9882                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9883                         gameInfo.variant != VariantBerolina || toX < fromX)
9884                       board[EP_STATUS] = toX | berolina;
9885                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9886                         gameInfo.variant != VariantBerolina || toX > fromX)
9887                       board[EP_STATUS] = toX;
9888            }
9889       } else
9890       if( board[fromY][fromX] == BlackPawn ) {
9891            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9892                board[EP_STATUS] = EP_PAWN_MOVE;
9893            if( toY-fromY== -2) {
9894                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9895                         gameInfo.variant != VariantBerolina || toX < fromX)
9896                       board[EP_STATUS] = toX | berolina;
9897                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9898                         gameInfo.variant != VariantBerolina || toX > fromX)
9899                       board[EP_STATUS] = toX;
9900            }
9901        }
9902
9903        for(i=0; i<nrCastlingRights; i++) {
9904            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9905               board[CASTLING][i] == toX   && castlingRank[i] == toY
9906              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9907        }
9908
9909        if(gameInfo.variant == VariantSChess) { // update virginity
9910            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9911            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9912            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9913            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9914        }
9915
9916      if (fromX == toX && fromY == toY) return;
9917
9918      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9919      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9920      if(gameInfo.variant == VariantKnightmate)
9921          king += (int) WhiteUnicorn - (int) WhiteKing;
9922
9923     /* Code added by Tord: */
9924     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9925     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9926         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9927       board[fromY][fromX] = EmptySquare;
9928       board[toY][toX] = EmptySquare;
9929       if((toX > fromX) != (piece == WhiteRook)) {
9930         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9931       } else {
9932         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9933       }
9934     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9935                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9936       board[fromY][fromX] = EmptySquare;
9937       board[toY][toX] = EmptySquare;
9938       if((toX > fromX) != (piece == BlackRook)) {
9939         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9940       } else {
9941         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9942       }
9943     /* End of code added by Tord */
9944
9945     } else if (board[fromY][fromX] == king
9946         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9947         && toY == fromY && toX > fromX+1) {
9948         board[fromY][fromX] = EmptySquare;
9949         board[toY][toX] = king;
9950         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9951         board[fromY][BOARD_RGHT-1] = EmptySquare;
9952     } else if (board[fromY][fromX] == king
9953         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9954                && toY == fromY && toX < fromX-1) {
9955         board[fromY][fromX] = EmptySquare;
9956         board[toY][toX] = king;
9957         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9958         board[fromY][BOARD_LEFT] = EmptySquare;
9959     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9960                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9961                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9962                ) {
9963         /* white pawn promotion */
9964         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9965         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9966             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9967         board[fromY][fromX] = EmptySquare;
9968     } else if ((fromY >= BOARD_HEIGHT>>1)
9969                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9970                && (toX != fromX)
9971                && gameInfo.variant != VariantXiangqi
9972                && gameInfo.variant != VariantBerolina
9973                && (board[fromY][fromX] == WhitePawn)
9974                && (board[toY][toX] == EmptySquare)) {
9975         board[fromY][fromX] = EmptySquare;
9976         board[toY][toX] = WhitePawn;
9977         captured = board[toY - 1][toX];
9978         board[toY - 1][toX] = EmptySquare;
9979     } else if ((fromY == BOARD_HEIGHT-4)
9980                && (toX == fromX)
9981                && gameInfo.variant == VariantBerolina
9982                && (board[fromY][fromX] == WhitePawn)
9983                && (board[toY][toX] == EmptySquare)) {
9984         board[fromY][fromX] = EmptySquare;
9985         board[toY][toX] = WhitePawn;
9986         if(oldEP & EP_BEROLIN_A) {
9987                 captured = board[fromY][fromX-1];
9988                 board[fromY][fromX-1] = EmptySquare;
9989         }else{  captured = board[fromY][fromX+1];
9990                 board[fromY][fromX+1] = EmptySquare;
9991         }
9992     } else if (board[fromY][fromX] == king
9993         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9994                && toY == fromY && toX > fromX+1) {
9995         board[fromY][fromX] = EmptySquare;
9996         board[toY][toX] = king;
9997         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9998         board[fromY][BOARD_RGHT-1] = EmptySquare;
9999     } else if (board[fromY][fromX] == king
10000         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10001                && toY == fromY && toX < fromX-1) {
10002         board[fromY][fromX] = EmptySquare;
10003         board[toY][toX] = king;
10004         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10005         board[fromY][BOARD_LEFT] = EmptySquare;
10006     } else if (fromY == 7 && fromX == 3
10007                && board[fromY][fromX] == BlackKing
10008                && toY == 7 && toX == 5) {
10009         board[fromY][fromX] = EmptySquare;
10010         board[toY][toX] = BlackKing;
10011         board[fromY][7] = EmptySquare;
10012         board[toY][4] = BlackRook;
10013     } else if (fromY == 7 && fromX == 3
10014                && board[fromY][fromX] == BlackKing
10015                && toY == 7 && toX == 1) {
10016         board[fromY][fromX] = EmptySquare;
10017         board[toY][toX] = BlackKing;
10018         board[fromY][0] = EmptySquare;
10019         board[toY][2] = BlackRook;
10020     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10021                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10022                && toY < promoRank && promoChar
10023                ) {
10024         /* black pawn promotion */
10025         board[toY][toX] = CharToPiece(ToLower(promoChar));
10026         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10027             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10028         board[fromY][fromX] = EmptySquare;
10029     } else if ((fromY < BOARD_HEIGHT>>1)
10030                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10031                && (toX != fromX)
10032                && gameInfo.variant != VariantXiangqi
10033                && gameInfo.variant != VariantBerolina
10034                && (board[fromY][fromX] == BlackPawn)
10035                && (board[toY][toX] == EmptySquare)) {
10036         board[fromY][fromX] = EmptySquare;
10037         board[toY][toX] = BlackPawn;
10038         captured = board[toY + 1][toX];
10039         board[toY + 1][toX] = EmptySquare;
10040     } else if ((fromY == 3)
10041                && (toX == fromX)
10042                && gameInfo.variant == VariantBerolina
10043                && (board[fromY][fromX] == BlackPawn)
10044                && (board[toY][toX] == EmptySquare)) {
10045         board[fromY][fromX] = EmptySquare;
10046         board[toY][toX] = BlackPawn;
10047         if(oldEP & EP_BEROLIN_A) {
10048                 captured = board[fromY][fromX-1];
10049                 board[fromY][fromX-1] = EmptySquare;
10050         }else{  captured = board[fromY][fromX+1];
10051                 board[fromY][fromX+1] = EmptySquare;
10052         }
10053     } else {
10054         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10055         board[fromY][fromX] = EmptySquare;
10056         board[toY][toX] = piece;
10057     }
10058   }
10059
10060     if (gameInfo.holdingsWidth != 0) {
10061
10062       /* !!A lot more code needs to be written to support holdings  */
10063       /* [HGM] OK, so I have written it. Holdings are stored in the */
10064       /* penultimate board files, so they are automaticlly stored   */
10065       /* in the game history.                                       */
10066       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10067                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10068         /* Delete from holdings, by decreasing count */
10069         /* and erasing image if necessary            */
10070         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10071         if(p < (int) BlackPawn) { /* white drop */
10072              p -= (int)WhitePawn;
10073                  p = PieceToNumber((ChessSquare)p);
10074              if(p >= gameInfo.holdingsSize) p = 0;
10075              if(--board[p][BOARD_WIDTH-2] <= 0)
10076                   board[p][BOARD_WIDTH-1] = EmptySquare;
10077              if((int)board[p][BOARD_WIDTH-2] < 0)
10078                         board[p][BOARD_WIDTH-2] = 0;
10079         } else {                  /* black drop */
10080              p -= (int)BlackPawn;
10081                  p = PieceToNumber((ChessSquare)p);
10082              if(p >= gameInfo.holdingsSize) p = 0;
10083              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10084                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10085              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10086                         board[BOARD_HEIGHT-1-p][1] = 0;
10087         }
10088       }
10089       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10090           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10091         /* [HGM] holdings: Add to holdings, if holdings exist */
10092         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10093                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10094                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10095         }
10096         p = (int) captured;
10097         if (p >= (int) BlackPawn) {
10098           p -= (int)BlackPawn;
10099           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10100                   /* in Shogi restore piece to its original  first */
10101                   captured = (ChessSquare) (DEMOTED captured);
10102                   p = DEMOTED p;
10103           }
10104           p = PieceToNumber((ChessSquare)p);
10105           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10106           board[p][BOARD_WIDTH-2]++;
10107           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10108         } else {
10109           p -= (int)WhitePawn;
10110           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10111                   captured = (ChessSquare) (DEMOTED captured);
10112                   p = DEMOTED p;
10113           }
10114           p = PieceToNumber((ChessSquare)p);
10115           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10116           board[BOARD_HEIGHT-1-p][1]++;
10117           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10118         }
10119       }
10120     } else if (gameInfo.variant == VariantAtomic) {
10121       if (captured != EmptySquare) {
10122         int y, x;
10123         for (y = toY-1; y <= toY+1; y++) {
10124           for (x = toX-1; x <= toX+1; x++) {
10125             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10126                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10127               board[y][x] = EmptySquare;
10128             }
10129           }
10130         }
10131         board[toY][toX] = EmptySquare;
10132       }
10133     }
10134
10135     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10136         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10137     } else
10138     if(promoChar == '+') {
10139         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10140         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10141         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10142           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10143     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10144         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10145         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10146            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10147         board[toY][toX] = newPiece;
10148     }
10149     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10150                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10151         // [HGM] superchess: take promotion piece out of holdings
10152         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10153         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10154             if(!--board[k][BOARD_WIDTH-2])
10155                 board[k][BOARD_WIDTH-1] = EmptySquare;
10156         } else {
10157             if(!--board[BOARD_HEIGHT-1-k][1])
10158                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10159         }
10160     }
10161 }
10162
10163 /* Updates forwardMostMove */
10164 void
10165 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10166 {
10167     int x = toX, y = toY;
10168     char *s = parseList[forwardMostMove];
10169     ChessSquare p = boards[forwardMostMove][toY][toX];
10170 //    forwardMostMove++; // [HGM] bare: moved downstream
10171
10172     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10173     (void) CoordsToAlgebraic(boards[forwardMostMove],
10174                              PosFlags(forwardMostMove),
10175                              fromY, fromX, y, x, promoChar,
10176                              s);
10177     if(killX >= 0 && killY >= 0)
10178         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10179
10180     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10181         int timeLeft; static int lastLoadFlag=0; int king, piece;
10182         piece = boards[forwardMostMove][fromY][fromX];
10183         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10184         if(gameInfo.variant == VariantKnightmate)
10185             king += (int) WhiteUnicorn - (int) WhiteKing;
10186         if(forwardMostMove == 0) {
10187             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10188                 fprintf(serverMoves, "%s;", UserName());
10189             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10190                 fprintf(serverMoves, "%s;", second.tidy);
10191             fprintf(serverMoves, "%s;", first.tidy);
10192             if(gameMode == MachinePlaysWhite)
10193                 fprintf(serverMoves, "%s;", UserName());
10194             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10195                 fprintf(serverMoves, "%s;", second.tidy);
10196         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10197         lastLoadFlag = loadFlag;
10198         // print base move
10199         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10200         // print castling suffix
10201         if( toY == fromY && piece == king ) {
10202             if(toX-fromX > 1)
10203                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10204             if(fromX-toX >1)
10205                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10206         }
10207         // e.p. suffix
10208         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10209              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10210              boards[forwardMostMove][toY][toX] == EmptySquare
10211              && fromX != toX && fromY != toY)
10212                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10213         // promotion suffix
10214         if(promoChar != NULLCHAR) {
10215             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10216                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10217                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10218             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10219         }
10220         if(!loadFlag) {
10221                 char buf[MOVE_LEN*2], *p; int len;
10222             fprintf(serverMoves, "/%d/%d",
10223                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10224             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10225             else                      timeLeft = blackTimeRemaining/1000;
10226             fprintf(serverMoves, "/%d", timeLeft);
10227                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10228                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10229                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10230                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10231             fprintf(serverMoves, "/%s", buf);
10232         }
10233         fflush(serverMoves);
10234     }
10235
10236     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10237         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10238       return;
10239     }
10240     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10241     if (commentList[forwardMostMove+1] != NULL) {
10242         free(commentList[forwardMostMove+1]);
10243         commentList[forwardMostMove+1] = NULL;
10244     }
10245     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10246     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10247     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10248     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10249     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10250     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10251     adjustedClock = FALSE;
10252     gameInfo.result = GameUnfinished;
10253     if (gameInfo.resultDetails != NULL) {
10254         free(gameInfo.resultDetails);
10255         gameInfo.resultDetails = NULL;
10256     }
10257     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10258                               moveList[forwardMostMove - 1]);
10259     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10260       case MT_NONE:
10261       case MT_STALEMATE:
10262       default:
10263         break;
10264       case MT_CHECK:
10265         if(!IS_SHOGI(gameInfo.variant))
10266             strcat(parseList[forwardMostMove - 1], "+");
10267         break;
10268       case MT_CHECKMATE:
10269       case MT_STAINMATE:
10270         strcat(parseList[forwardMostMove - 1], "#");
10271         break;
10272     }
10273 }
10274
10275 /* Updates currentMove if not pausing */
10276 void
10277 ShowMove (int fromX, int fromY, int toX, int toY)
10278 {
10279     int instant = (gameMode == PlayFromGameFile) ?
10280         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10281     if(appData.noGUI) return;
10282     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10283         if (!instant) {
10284             if (forwardMostMove == currentMove + 1) {
10285                 AnimateMove(boards[forwardMostMove - 1],
10286                             fromX, fromY, toX, toY);
10287             }
10288         }
10289         currentMove = forwardMostMove;
10290     }
10291
10292     killX = killY = -1; // [HGM] lion: used up
10293
10294     if (instant) return;
10295
10296     DisplayMove(currentMove - 1);
10297     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10298             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10299                 SetHighlights(fromX, fromY, toX, toY);
10300             }
10301     }
10302     DrawPosition(FALSE, boards[currentMove]);
10303     DisplayBothClocks();
10304     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10305 }
10306
10307 void
10308 SendEgtPath (ChessProgramState *cps)
10309 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10310         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10311
10312         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10313
10314         while(*p) {
10315             char c, *q = name+1, *r, *s;
10316
10317             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10318             while(*p && *p != ',') *q++ = *p++;
10319             *q++ = ':'; *q = 0;
10320             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10321                 strcmp(name, ",nalimov:") == 0 ) {
10322                 // take nalimov path from the menu-changeable option first, if it is defined
10323               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10324                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10325             } else
10326             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10327                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10328                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10329                 s = r = StrStr(s, ":") + 1; // beginning of path info
10330                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10331                 c = *r; *r = 0;             // temporarily null-terminate path info
10332                     *--q = 0;               // strip of trailig ':' from name
10333                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10334                 *r = c;
10335                 SendToProgram(buf,cps);     // send egtbpath command for this format
10336             }
10337             if(*p == ',') p++; // read away comma to position for next format name
10338         }
10339 }
10340
10341 static int
10342 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10343 {
10344       int width = 8, height = 8, holdings = 0;             // most common sizes
10345       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10346       // correct the deviations default for each variant
10347       if( v == VariantXiangqi ) width = 9,  height = 10;
10348       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10349       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10350       if( v == VariantCapablanca || v == VariantCapaRandom ||
10351           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10352                                 width = 10;
10353       if( v == VariantCourier ) width = 12;
10354       if( v == VariantSuper )                            holdings = 8;
10355       if( v == VariantGreat )   width = 10,              holdings = 8;
10356       if( v == VariantSChess )                           holdings = 7;
10357       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10358       if( v == VariantChuChess) width = 10, height = 10;
10359       if( v == VariantChu )     width = 12, height = 12;
10360       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10361              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10362              holdingsSize >= 0 && holdingsSize != holdings;
10363 }
10364
10365 char variantError[MSG_SIZ];
10366
10367 char *
10368 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10369 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10370       char *p, *variant = VariantName(v);
10371       static char b[MSG_SIZ];
10372       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10373            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10374                                                holdingsSize, variant); // cook up sized variant name
10375            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10376            if(StrStr(list, b) == NULL) {
10377                // specific sized variant not known, check if general sizing allowed
10378                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10379                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10380                             boardWidth, boardHeight, holdingsSize, engine);
10381                    return NULL;
10382                }
10383                /* [HGM] here we really should compare with the maximum supported board size */
10384            }
10385       } else snprintf(b, MSG_SIZ,"%s", variant);
10386       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10387       p = StrStr(list, b);
10388       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10389       if(p == NULL) {
10390           // occurs not at all in list, or only as sub-string
10391           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10392           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10393               int l = strlen(variantError);
10394               char *q;
10395               while(p != list && p[-1] != ',') p--;
10396               q = strchr(p, ',');
10397               if(q) *q = NULLCHAR;
10398               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10399               if(q) *q= ',';
10400           }
10401           return NULL;
10402       }
10403       return b;
10404 }
10405
10406 void
10407 InitChessProgram (ChessProgramState *cps, int setup)
10408 /* setup needed to setup FRC opening position */
10409 {
10410     char buf[MSG_SIZ], *b;
10411     if (appData.noChessProgram) return;
10412     hintRequested = FALSE;
10413     bookRequested = FALSE;
10414
10415     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10416     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10417     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10418     if(cps->memSize) { /* [HGM] memory */
10419       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10420         SendToProgram(buf, cps);
10421     }
10422     SendEgtPath(cps); /* [HGM] EGT */
10423     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10424       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10425         SendToProgram(buf, cps);
10426     }
10427
10428     SendToProgram(cps->initString, cps);
10429     if (gameInfo.variant != VariantNormal &&
10430         gameInfo.variant != VariantLoadable
10431         /* [HGM] also send variant if board size non-standard */
10432         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10433
10434       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10435                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10436       if (b == NULL) {
10437         DisplayFatalError(variantError, 0, 1);
10438         return;
10439       }
10440
10441       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10442       SendToProgram(buf, cps);
10443     }
10444     currentlyInitializedVariant = gameInfo.variant;
10445
10446     /* [HGM] send opening position in FRC to first engine */
10447     if(setup) {
10448           SendToProgram("force\n", cps);
10449           SendBoard(cps, 0);
10450           /* engine is now in force mode! Set flag to wake it up after first move. */
10451           setboardSpoiledMachineBlack = 1;
10452     }
10453
10454     if (cps->sendICS) {
10455       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10456       SendToProgram(buf, cps);
10457     }
10458     cps->maybeThinking = FALSE;
10459     cps->offeredDraw = 0;
10460     if (!appData.icsActive) {
10461         SendTimeControl(cps, movesPerSession, timeControl,
10462                         timeIncrement, appData.searchDepth,
10463                         searchTime);
10464     }
10465     if (appData.showThinking
10466         // [HGM] thinking: four options require thinking output to be sent
10467         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10468                                 ) {
10469         SendToProgram("post\n", cps);
10470     }
10471     SendToProgram("hard\n", cps);
10472     if (!appData.ponderNextMove) {
10473         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10474            it without being sure what state we are in first.  "hard"
10475            is not a toggle, so that one is OK.
10476          */
10477         SendToProgram("easy\n", cps);
10478     }
10479     if (cps->usePing) {
10480       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10481       SendToProgram(buf, cps);
10482     }
10483     cps->initDone = TRUE;
10484     ClearEngineOutputPane(cps == &second);
10485 }
10486
10487
10488 void
10489 ResendOptions (ChessProgramState *cps)
10490 { // send the stored value of the options
10491   int i;
10492   char buf[MSG_SIZ];
10493   Option *opt = cps->option;
10494   for(i=0; i<cps->nrOptions; i++, opt++) {
10495       switch(opt->type) {
10496         case Spin:
10497         case Slider:
10498         case CheckBox:
10499             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10500           break;
10501         case ComboBox:
10502           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10503           break;
10504         default:
10505             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10506           break;
10507         case Button:
10508         case SaveButton:
10509           continue;
10510       }
10511       SendToProgram(buf, cps);
10512   }
10513 }
10514
10515 void
10516 StartChessProgram (ChessProgramState *cps)
10517 {
10518     char buf[MSG_SIZ];
10519     int err;
10520
10521     if (appData.noChessProgram) return;
10522     cps->initDone = FALSE;
10523
10524     if (strcmp(cps->host, "localhost") == 0) {
10525         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10526     } else if (*appData.remoteShell == NULLCHAR) {
10527         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10528     } else {
10529         if (*appData.remoteUser == NULLCHAR) {
10530           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10531                     cps->program);
10532         } else {
10533           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10534                     cps->host, appData.remoteUser, cps->program);
10535         }
10536         err = StartChildProcess(buf, "", &cps->pr);
10537     }
10538
10539     if (err != 0) {
10540       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10541         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10542         if(cps != &first) return;
10543         appData.noChessProgram = TRUE;
10544         ThawUI();
10545         SetNCPMode();
10546 //      DisplayFatalError(buf, err, 1);
10547 //      cps->pr = NoProc;
10548 //      cps->isr = NULL;
10549         return;
10550     }
10551
10552     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10553     if (cps->protocolVersion > 1) {
10554       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10555       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10556         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10557         cps->comboCnt = 0;  //                and values of combo boxes
10558       }
10559       SendToProgram(buf, cps);
10560       if(cps->reload) ResendOptions(cps);
10561     } else {
10562       SendToProgram("xboard\n", cps);
10563     }
10564 }
10565
10566 void
10567 TwoMachinesEventIfReady P((void))
10568 {
10569   static int curMess = 0;
10570   if (first.lastPing != first.lastPong) {
10571     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10572     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10573     return;
10574   }
10575   if (second.lastPing != second.lastPong) {
10576     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10577     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10578     return;
10579   }
10580   DisplayMessage("", ""); curMess = 0;
10581   TwoMachinesEvent();
10582 }
10583
10584 char *
10585 MakeName (char *template)
10586 {
10587     time_t clock;
10588     struct tm *tm;
10589     static char buf[MSG_SIZ];
10590     char *p = buf;
10591     int i;
10592
10593     clock = time((time_t *)NULL);
10594     tm = localtime(&clock);
10595
10596     while(*p++ = *template++) if(p[-1] == '%') {
10597         switch(*template++) {
10598           case 0:   *p = 0; return buf;
10599           case 'Y': i = tm->tm_year+1900; break;
10600           case 'y': i = tm->tm_year-100; break;
10601           case 'M': i = tm->tm_mon+1; break;
10602           case 'd': i = tm->tm_mday; break;
10603           case 'h': i = tm->tm_hour; break;
10604           case 'm': i = tm->tm_min; break;
10605           case 's': i = tm->tm_sec; break;
10606           default:  i = 0;
10607         }
10608         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10609     }
10610     return buf;
10611 }
10612
10613 int
10614 CountPlayers (char *p)
10615 {
10616     int n = 0;
10617     while(p = strchr(p, '\n')) p++, n++; // count participants
10618     return n;
10619 }
10620
10621 FILE *
10622 WriteTourneyFile (char *results, FILE *f)
10623 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10624     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10625     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10626         // create a file with tournament description
10627         fprintf(f, "-participants {%s}\n", appData.participants);
10628         fprintf(f, "-seedBase %d\n", appData.seedBase);
10629         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10630         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10631         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10632         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10633         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10634         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10635         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10636         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10637         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10638         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10639         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10640         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10641         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10642         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10643         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10644         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10645         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10646         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10647         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10648         fprintf(f, "-smpCores %d\n", appData.smpCores);
10649         if(searchTime > 0)
10650                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10651         else {
10652                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10653                 fprintf(f, "-tc %s\n", appData.timeControl);
10654                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10655         }
10656         fprintf(f, "-results \"%s\"\n", results);
10657     }
10658     return f;
10659 }
10660
10661 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10662
10663 void
10664 Substitute (char *participants, int expunge)
10665 {
10666     int i, changed, changes=0, nPlayers=0;
10667     char *p, *q, *r, buf[MSG_SIZ];
10668     if(participants == NULL) return;
10669     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10670     r = p = participants; q = appData.participants;
10671     while(*p && *p == *q) {
10672         if(*p == '\n') r = p+1, nPlayers++;
10673         p++; q++;
10674     }
10675     if(*p) { // difference
10676         while(*p && *p++ != '\n');
10677         while(*q && *q++ != '\n');
10678       changed = nPlayers;
10679         changes = 1 + (strcmp(p, q) != 0);
10680     }
10681     if(changes == 1) { // a single engine mnemonic was changed
10682         q = r; while(*q) nPlayers += (*q++ == '\n');
10683         p = buf; while(*r && (*p = *r++) != '\n') p++;
10684         *p = NULLCHAR;
10685         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10686         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10687         if(mnemonic[i]) { // The substitute is valid
10688             FILE *f;
10689             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10690                 flock(fileno(f), LOCK_EX);
10691                 ParseArgsFromFile(f);
10692                 fseek(f, 0, SEEK_SET);
10693                 FREE(appData.participants); appData.participants = participants;
10694                 if(expunge) { // erase results of replaced engine
10695                     int len = strlen(appData.results), w, b, dummy;
10696                     for(i=0; i<len; i++) {
10697                         Pairing(i, nPlayers, &w, &b, &dummy);
10698                         if((w == changed || b == changed) && appData.results[i] == '*') {
10699                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10700                             fclose(f);
10701                             return;
10702                         }
10703                     }
10704                     for(i=0; i<len; i++) {
10705                         Pairing(i, nPlayers, &w, &b, &dummy);
10706                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10707                     }
10708                 }
10709                 WriteTourneyFile(appData.results, f);
10710                 fclose(f); // release lock
10711                 return;
10712             }
10713         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10714     }
10715     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10716     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10717     free(participants);
10718     return;
10719 }
10720
10721 int
10722 CheckPlayers (char *participants)
10723 {
10724         int i;
10725         char buf[MSG_SIZ], *p;
10726         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10727         while(p = strchr(participants, '\n')) {
10728             *p = NULLCHAR;
10729             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10730             if(!mnemonic[i]) {
10731                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10732                 *p = '\n';
10733                 DisplayError(buf, 0);
10734                 return 1;
10735             }
10736             *p = '\n';
10737             participants = p + 1;
10738         }
10739         return 0;
10740 }
10741
10742 int
10743 CreateTourney (char *name)
10744 {
10745         FILE *f;
10746         if(matchMode && strcmp(name, appData.tourneyFile)) {
10747              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10748         }
10749         if(name[0] == NULLCHAR) {
10750             if(appData.participants[0])
10751                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10752             return 0;
10753         }
10754         f = fopen(name, "r");
10755         if(f) { // file exists
10756             ASSIGN(appData.tourneyFile, name);
10757             ParseArgsFromFile(f); // parse it
10758         } else {
10759             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10760             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10761                 DisplayError(_("Not enough participants"), 0);
10762                 return 0;
10763             }
10764             if(CheckPlayers(appData.participants)) return 0;
10765             ASSIGN(appData.tourneyFile, name);
10766             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10767             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10768         }
10769         fclose(f);
10770         appData.noChessProgram = FALSE;
10771         appData.clockMode = TRUE;
10772         SetGNUMode();
10773         return 1;
10774 }
10775
10776 int
10777 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10778 {
10779     char buf[MSG_SIZ], *p, *q;
10780     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10781     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10782     skip = !all && group[0]; // if group requested, we start in skip mode
10783     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10784         p = names; q = buf; header = 0;
10785         while(*p && *p != '\n') *q++ = *p++;
10786         *q = 0;
10787         if(*p == '\n') p++;
10788         if(buf[0] == '#') {
10789             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10790             depth++; // we must be entering a new group
10791             if(all) continue; // suppress printing group headers when complete list requested
10792             header = 1;
10793             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10794         }
10795         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10796         if(engineList[i]) free(engineList[i]);
10797         engineList[i] = strdup(buf);
10798         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10799         if(engineMnemonic[i]) free(engineMnemonic[i]);
10800         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10801             strcat(buf, " (");
10802             sscanf(q + 8, "%s", buf + strlen(buf));
10803             strcat(buf, ")");
10804         }
10805         engineMnemonic[i] = strdup(buf);
10806         i++;
10807     }
10808     engineList[i] = engineMnemonic[i] = NULL;
10809     return i;
10810 }
10811
10812 // following implemented as macro to avoid type limitations
10813 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10814
10815 void
10816 SwapEngines (int n)
10817 {   // swap settings for first engine and other engine (so far only some selected options)
10818     int h;
10819     char *p;
10820     if(n == 0) return;
10821     SWAP(directory, p)
10822     SWAP(chessProgram, p)
10823     SWAP(isUCI, h)
10824     SWAP(hasOwnBookUCI, h)
10825     SWAP(protocolVersion, h)
10826     SWAP(reuse, h)
10827     SWAP(scoreIsAbsolute, h)
10828     SWAP(timeOdds, h)
10829     SWAP(logo, p)
10830     SWAP(pgnName, p)
10831     SWAP(pvSAN, h)
10832     SWAP(engOptions, p)
10833     SWAP(engInitString, p)
10834     SWAP(computerString, p)
10835     SWAP(features, p)
10836     SWAP(fenOverride, p)
10837     SWAP(NPS, h)
10838     SWAP(accumulateTC, h)
10839     SWAP(drawDepth, h)
10840     SWAP(host, p)
10841 }
10842
10843 int
10844 GetEngineLine (char *s, int n)
10845 {
10846     int i;
10847     char buf[MSG_SIZ];
10848     extern char *icsNames;
10849     if(!s || !*s) return 0;
10850     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10851     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10852     if(!mnemonic[i]) return 0;
10853     if(n == 11) return 1; // just testing if there was a match
10854     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10855     if(n == 1) SwapEngines(n);
10856     ParseArgsFromString(buf);
10857     if(n == 1) SwapEngines(n);
10858     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10859         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10860         ParseArgsFromString(buf);
10861     }
10862     return 1;
10863 }
10864
10865 int
10866 SetPlayer (int player, char *p)
10867 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10868     int i;
10869     char buf[MSG_SIZ], *engineName;
10870     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10871     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10872     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10873     if(mnemonic[i]) {
10874         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10875         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10876         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10877         ParseArgsFromString(buf);
10878     } else { // no engine with this nickname is installed!
10879         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10880         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10881         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10882         ModeHighlight();
10883         DisplayError(buf, 0);
10884         return 0;
10885     }
10886     free(engineName);
10887     return i;
10888 }
10889
10890 char *recentEngines;
10891
10892 void
10893 RecentEngineEvent (int nr)
10894 {
10895     int n;
10896 //    SwapEngines(1); // bump first to second
10897 //    ReplaceEngine(&second, 1); // and load it there
10898     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10899     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10900     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10901         ReplaceEngine(&first, 0);
10902         FloatToFront(&appData.recentEngineList, command[n]);
10903     }
10904 }
10905
10906 int
10907 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10908 {   // determine players from game number
10909     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10910
10911     if(appData.tourneyType == 0) {
10912         roundsPerCycle = (nPlayers - 1) | 1;
10913         pairingsPerRound = nPlayers / 2;
10914     } else if(appData.tourneyType > 0) {
10915         roundsPerCycle = nPlayers - appData.tourneyType;
10916         pairingsPerRound = appData.tourneyType;
10917     }
10918     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10919     gamesPerCycle = gamesPerRound * roundsPerCycle;
10920     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10921     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10922     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10923     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10924     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10925     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10926
10927     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10928     if(appData.roundSync) *syncInterval = gamesPerRound;
10929
10930     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10931
10932     if(appData.tourneyType == 0) {
10933         if(curPairing == (nPlayers-1)/2 ) {
10934             *whitePlayer = curRound;
10935             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10936         } else {
10937             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10938             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10939             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10940             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10941         }
10942     } else if(appData.tourneyType > 1) {
10943         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10944         *whitePlayer = curRound + appData.tourneyType;
10945     } else if(appData.tourneyType > 0) {
10946         *whitePlayer = curPairing;
10947         *blackPlayer = curRound + appData.tourneyType;
10948     }
10949
10950     // take care of white/black alternation per round.
10951     // For cycles and games this is already taken care of by default, derived from matchGame!
10952     return curRound & 1;
10953 }
10954
10955 int
10956 NextTourneyGame (int nr, int *swapColors)
10957 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10958     char *p, *q;
10959     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10960     FILE *tf;
10961     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10962     tf = fopen(appData.tourneyFile, "r");
10963     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10964     ParseArgsFromFile(tf); fclose(tf);
10965     InitTimeControls(); // TC might be altered from tourney file
10966
10967     nPlayers = CountPlayers(appData.participants); // count participants
10968     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10969     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10970
10971     if(syncInterval) {
10972         p = q = appData.results;
10973         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10974         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10975             DisplayMessage(_("Waiting for other game(s)"),"");
10976             waitingForGame = TRUE;
10977             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10978             return 0;
10979         }
10980         waitingForGame = FALSE;
10981     }
10982
10983     if(appData.tourneyType < 0) {
10984         if(nr>=0 && !pairingReceived) {
10985             char buf[1<<16];
10986             if(pairing.pr == NoProc) {
10987                 if(!appData.pairingEngine[0]) {
10988                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10989                     return 0;
10990                 }
10991                 StartChessProgram(&pairing); // starts the pairing engine
10992             }
10993             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10994             SendToProgram(buf, &pairing);
10995             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10996             SendToProgram(buf, &pairing);
10997             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10998         }
10999         pairingReceived = 0;                              // ... so we continue here
11000         *swapColors = 0;
11001         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11002         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11003         matchGame = 1; roundNr = nr / syncInterval + 1;
11004     }
11005
11006     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11007
11008     // redefine engines, engine dir, etc.
11009     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11010     if(first.pr == NoProc) {
11011       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11012       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11013     }
11014     if(second.pr == NoProc) {
11015       SwapEngines(1);
11016       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11017       SwapEngines(1);         // and make that valid for second engine by swapping
11018       InitEngine(&second, 1);
11019     }
11020     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11021     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11022     return OK;
11023 }
11024
11025 void
11026 NextMatchGame ()
11027 {   // performs game initialization that does not invoke engines, and then tries to start the game
11028     int res, firstWhite, swapColors = 0;
11029     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11030     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
11031         char buf[MSG_SIZ];
11032         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11033         if(strcmp(buf, currentDebugFile)) { // name has changed
11034             FILE *f = fopen(buf, "w");
11035             if(f) { // if opening the new file failed, just keep using the old one
11036                 ASSIGN(currentDebugFile, buf);
11037                 fclose(debugFP);
11038                 debugFP = f;
11039             }
11040             if(appData.serverFileName) {
11041                 if(serverFP) fclose(serverFP);
11042                 serverFP = fopen(appData.serverFileName, "w");
11043                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11044                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11045             }
11046         }
11047     }
11048     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11049     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11050     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11051     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11052     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11053     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11054     Reset(FALSE, first.pr != NoProc);
11055     res = LoadGameOrPosition(matchGame); // setup game
11056     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11057     if(!res) return; // abort when bad game/pos file
11058     TwoMachinesEvent();
11059 }
11060
11061 void
11062 UserAdjudicationEvent (int result)
11063 {
11064     ChessMove gameResult = GameIsDrawn;
11065
11066     if( result > 0 ) {
11067         gameResult = WhiteWins;
11068     }
11069     else if( result < 0 ) {
11070         gameResult = BlackWins;
11071     }
11072
11073     if( gameMode == TwoMachinesPlay ) {
11074         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11075     }
11076 }
11077
11078
11079 // [HGM] save: calculate checksum of game to make games easily identifiable
11080 int
11081 StringCheckSum (char *s)
11082 {
11083         int i = 0;
11084         if(s==NULL) return 0;
11085         while(*s) i = i*259 + *s++;
11086         return i;
11087 }
11088
11089 int
11090 GameCheckSum ()
11091 {
11092         int i, sum=0;
11093         for(i=backwardMostMove; i<forwardMostMove; i++) {
11094                 sum += pvInfoList[i].depth;
11095                 sum += StringCheckSum(parseList[i]);
11096                 sum += StringCheckSum(commentList[i]);
11097                 sum *= 261;
11098         }
11099         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11100         return sum + StringCheckSum(commentList[i]);
11101 } // end of save patch
11102
11103 void
11104 GameEnds (ChessMove result, char *resultDetails, int whosays)
11105 {
11106     GameMode nextGameMode;
11107     int isIcsGame;
11108     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11109
11110     if(endingGame) return; /* [HGM] crash: forbid recursion */
11111     endingGame = 1;
11112     if(twoBoards) { // [HGM] dual: switch back to one board
11113         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11114         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11115     }
11116     if (appData.debugMode) {
11117       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11118               result, resultDetails ? resultDetails : "(null)", whosays);
11119     }
11120
11121     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11122
11123     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11124
11125     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11126         /* If we are playing on ICS, the server decides when the
11127            game is over, but the engine can offer to draw, claim
11128            a draw, or resign.
11129          */
11130 #if ZIPPY
11131         if (appData.zippyPlay && first.initDone) {
11132             if (result == GameIsDrawn) {
11133                 /* In case draw still needs to be claimed */
11134                 SendToICS(ics_prefix);
11135                 SendToICS("draw\n");
11136             } else if (StrCaseStr(resultDetails, "resign")) {
11137                 SendToICS(ics_prefix);
11138                 SendToICS("resign\n");
11139             }
11140         }
11141 #endif
11142         endingGame = 0; /* [HGM] crash */
11143         return;
11144     }
11145
11146     /* If we're loading the game from a file, stop */
11147     if (whosays == GE_FILE) {
11148       (void) StopLoadGameTimer();
11149       gameFileFP = NULL;
11150     }
11151
11152     /* Cancel draw offers */
11153     first.offeredDraw = second.offeredDraw = 0;
11154
11155     /* If this is an ICS game, only ICS can really say it's done;
11156        if not, anyone can. */
11157     isIcsGame = (gameMode == IcsPlayingWhite ||
11158                  gameMode == IcsPlayingBlack ||
11159                  gameMode == IcsObserving    ||
11160                  gameMode == IcsExamining);
11161
11162     if (!isIcsGame || whosays == GE_ICS) {
11163         /* OK -- not an ICS game, or ICS said it was done */
11164         StopClocks();
11165         if (!isIcsGame && !appData.noChessProgram)
11166           SetUserThinkingEnables();
11167
11168         /* [HGM] if a machine claims the game end we verify this claim */
11169         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11170             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11171                 char claimer;
11172                 ChessMove trueResult = (ChessMove) -1;
11173
11174                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11175                                             first.twoMachinesColor[0] :
11176                                             second.twoMachinesColor[0] ;
11177
11178                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11179                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11180                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11181                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11182                 } else
11183                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11184                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11185                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11186                 } else
11187                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11188                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11189                 }
11190
11191                 // now verify win claims, but not in drop games, as we don't understand those yet
11192                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11193                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11194                     (result == WhiteWins && claimer == 'w' ||
11195                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11196                       if (appData.debugMode) {
11197                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11198                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11199                       }
11200                       if(result != trueResult) {
11201                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11202                               result = claimer == 'w' ? BlackWins : WhiteWins;
11203                               resultDetails = buf;
11204                       }
11205                 } else
11206                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11207                     && (forwardMostMove <= backwardMostMove ||
11208                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11209                         (claimer=='b')==(forwardMostMove&1))
11210                                                                                   ) {
11211                       /* [HGM] verify: draws that were not flagged are false claims */
11212                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11213                       result = claimer == 'w' ? BlackWins : WhiteWins;
11214                       resultDetails = buf;
11215                 }
11216                 /* (Claiming a loss is accepted no questions asked!) */
11217             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11218                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11219                 result = GameUnfinished;
11220                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11221             }
11222             /* [HGM] bare: don't allow bare King to win */
11223             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11224                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11225                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11226                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11227                && result != GameIsDrawn)
11228             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11229                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11230                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11231                         if(p >= 0 && p <= (int)WhiteKing) k++;
11232                 }
11233                 if (appData.debugMode) {
11234                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11235                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11236                 }
11237                 if(k <= 1) {
11238                         result = GameIsDrawn;
11239                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11240                         resultDetails = buf;
11241                 }
11242             }
11243         }
11244
11245
11246         if(serverMoves != NULL && !loadFlag) { char c = '=';
11247             if(result==WhiteWins) c = '+';
11248             if(result==BlackWins) c = '-';
11249             if(resultDetails != NULL)
11250                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11251         }
11252         if (resultDetails != NULL) {
11253             gameInfo.result = result;
11254             gameInfo.resultDetails = StrSave(resultDetails);
11255
11256             /* display last move only if game was not loaded from file */
11257             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11258                 DisplayMove(currentMove - 1);
11259
11260             if (forwardMostMove != 0) {
11261                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11262                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11263                                                                 ) {
11264                     if (*appData.saveGameFile != NULLCHAR) {
11265                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11266                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11267                         else
11268                         SaveGameToFile(appData.saveGameFile, TRUE);
11269                     } else if (appData.autoSaveGames) {
11270                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11271                     }
11272                     if (*appData.savePositionFile != NULLCHAR) {
11273                         SavePositionToFile(appData.savePositionFile);
11274                     }
11275                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11276                 }
11277             }
11278
11279             /* Tell program how game ended in case it is learning */
11280             /* [HGM] Moved this to after saving the PGN, just in case */
11281             /* engine died and we got here through time loss. In that */
11282             /* case we will get a fatal error writing the pipe, which */
11283             /* would otherwise lose us the PGN.                       */
11284             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11285             /* output during GameEnds should never be fatal anymore   */
11286             if (gameMode == MachinePlaysWhite ||
11287                 gameMode == MachinePlaysBlack ||
11288                 gameMode == TwoMachinesPlay ||
11289                 gameMode == IcsPlayingWhite ||
11290                 gameMode == IcsPlayingBlack ||
11291                 gameMode == BeginningOfGame) {
11292                 char buf[MSG_SIZ];
11293                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11294                         resultDetails);
11295                 if (first.pr != NoProc) {
11296                     SendToProgram(buf, &first);
11297                 }
11298                 if (second.pr != NoProc &&
11299                     gameMode == TwoMachinesPlay) {
11300                     SendToProgram(buf, &second);
11301                 }
11302             }
11303         }
11304
11305         if (appData.icsActive) {
11306             if (appData.quietPlay &&
11307                 (gameMode == IcsPlayingWhite ||
11308                  gameMode == IcsPlayingBlack)) {
11309                 SendToICS(ics_prefix);
11310                 SendToICS("set shout 1\n");
11311             }
11312             nextGameMode = IcsIdle;
11313             ics_user_moved = FALSE;
11314             /* clean up premove.  It's ugly when the game has ended and the
11315              * premove highlights are still on the board.
11316              */
11317             if (gotPremove) {
11318               gotPremove = FALSE;
11319               ClearPremoveHighlights();
11320               DrawPosition(FALSE, boards[currentMove]);
11321             }
11322             if (whosays == GE_ICS) {
11323                 switch (result) {
11324                 case WhiteWins:
11325                     if (gameMode == IcsPlayingWhite)
11326                         PlayIcsWinSound();
11327                     else if(gameMode == IcsPlayingBlack)
11328                         PlayIcsLossSound();
11329                     break;
11330                 case BlackWins:
11331                     if (gameMode == IcsPlayingBlack)
11332                         PlayIcsWinSound();
11333                     else if(gameMode == IcsPlayingWhite)
11334                         PlayIcsLossSound();
11335                     break;
11336                 case GameIsDrawn:
11337                     PlayIcsDrawSound();
11338                     break;
11339                 default:
11340                     PlayIcsUnfinishedSound();
11341                 }
11342             }
11343             if(appData.quitNext) { ExitEvent(0); return; }
11344         } else if (gameMode == EditGame ||
11345                    gameMode == PlayFromGameFile ||
11346                    gameMode == AnalyzeMode ||
11347                    gameMode == AnalyzeFile) {
11348             nextGameMode = gameMode;
11349         } else {
11350             nextGameMode = EndOfGame;
11351         }
11352         pausing = FALSE;
11353         ModeHighlight();
11354     } else {
11355         nextGameMode = gameMode;
11356     }
11357
11358     if (appData.noChessProgram) {
11359         gameMode = nextGameMode;
11360         ModeHighlight();
11361         endingGame = 0; /* [HGM] crash */
11362         return;
11363     }
11364
11365     if (first.reuse) {
11366         /* Put first chess program into idle state */
11367         if (first.pr != NoProc &&
11368             (gameMode == MachinePlaysWhite ||
11369              gameMode == MachinePlaysBlack ||
11370              gameMode == TwoMachinesPlay ||
11371              gameMode == IcsPlayingWhite ||
11372              gameMode == IcsPlayingBlack ||
11373              gameMode == BeginningOfGame)) {
11374             SendToProgram("force\n", &first);
11375             if (first.usePing) {
11376               char buf[MSG_SIZ];
11377               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11378               SendToProgram(buf, &first);
11379             }
11380         }
11381     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11382         /* Kill off first chess program */
11383         if (first.isr != NULL)
11384           RemoveInputSource(first.isr);
11385         first.isr = NULL;
11386
11387         if (first.pr != NoProc) {
11388             ExitAnalyzeMode();
11389             DoSleep( appData.delayBeforeQuit );
11390             SendToProgram("quit\n", &first);
11391             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11392             first.reload = TRUE;
11393         }
11394         first.pr = NoProc;
11395     }
11396     if (second.reuse) {
11397         /* Put second chess program into idle state */
11398         if (second.pr != NoProc &&
11399             gameMode == TwoMachinesPlay) {
11400             SendToProgram("force\n", &second);
11401             if (second.usePing) {
11402               char buf[MSG_SIZ];
11403               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11404               SendToProgram(buf, &second);
11405             }
11406         }
11407     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11408         /* Kill off second chess program */
11409         if (second.isr != NULL)
11410           RemoveInputSource(second.isr);
11411         second.isr = NULL;
11412
11413         if (second.pr != NoProc) {
11414             DoSleep( appData.delayBeforeQuit );
11415             SendToProgram("quit\n", &second);
11416             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11417             second.reload = TRUE;
11418         }
11419         second.pr = NoProc;
11420     }
11421
11422     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11423         char resChar = '=';
11424         switch (result) {
11425         case WhiteWins:
11426           resChar = '+';
11427           if (first.twoMachinesColor[0] == 'w') {
11428             first.matchWins++;
11429           } else {
11430             second.matchWins++;
11431           }
11432           break;
11433         case BlackWins:
11434           resChar = '-';
11435           if (first.twoMachinesColor[0] == 'b') {
11436             first.matchWins++;
11437           } else {
11438             second.matchWins++;
11439           }
11440           break;
11441         case GameUnfinished:
11442           resChar = ' ';
11443         default:
11444           break;
11445         }
11446
11447         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11448         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11449             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11450             ReserveGame(nextGame, resChar); // sets nextGame
11451             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11452             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11453         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11454
11455         if (nextGame <= appData.matchGames && !abortMatch) {
11456             gameMode = nextGameMode;
11457             matchGame = nextGame; // this will be overruled in tourney mode!
11458             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11459             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11460             endingGame = 0; /* [HGM] crash */
11461             return;
11462         } else {
11463             gameMode = nextGameMode;
11464             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11465                      first.tidy, second.tidy,
11466                      first.matchWins, second.matchWins,
11467                      appData.matchGames - (first.matchWins + second.matchWins));
11468             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11469             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11470             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11471             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11472                 first.twoMachinesColor = "black\n";
11473                 second.twoMachinesColor = "white\n";
11474             } else {
11475                 first.twoMachinesColor = "white\n";
11476                 second.twoMachinesColor = "black\n";
11477             }
11478         }
11479     }
11480     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11481         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11482       ExitAnalyzeMode();
11483     gameMode = nextGameMode;
11484     ModeHighlight();
11485     endingGame = 0;  /* [HGM] crash */
11486     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11487         if(matchMode == TRUE) { // match through command line: exit with or without popup
11488             if(ranking) {
11489                 ToNrEvent(forwardMostMove);
11490                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11491                 else ExitEvent(0);
11492             } else DisplayFatalError(buf, 0, 0);
11493         } else { // match through menu; just stop, with or without popup
11494             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11495             ModeHighlight();
11496             if(ranking){
11497                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11498             } else DisplayNote(buf);
11499       }
11500       if(ranking) free(ranking);
11501     }
11502 }
11503
11504 /* Assumes program was just initialized (initString sent).
11505    Leaves program in force mode. */
11506 void
11507 FeedMovesToProgram (ChessProgramState *cps, int upto)
11508 {
11509     int i;
11510
11511     if (appData.debugMode)
11512       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11513               startedFromSetupPosition ? "position and " : "",
11514               backwardMostMove, upto, cps->which);
11515     if(currentlyInitializedVariant != gameInfo.variant) {
11516       char buf[MSG_SIZ];
11517         // [HGM] variantswitch: make engine aware of new variant
11518         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11519                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11520                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11521         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11522         SendToProgram(buf, cps);
11523         currentlyInitializedVariant = gameInfo.variant;
11524     }
11525     SendToProgram("force\n", cps);
11526     if (startedFromSetupPosition) {
11527         SendBoard(cps, backwardMostMove);
11528     if (appData.debugMode) {
11529         fprintf(debugFP, "feedMoves\n");
11530     }
11531     }
11532     for (i = backwardMostMove; i < upto; i++) {
11533         SendMoveToProgram(i, cps);
11534     }
11535 }
11536
11537
11538 int
11539 ResurrectChessProgram ()
11540 {
11541      /* The chess program may have exited.
11542         If so, restart it and feed it all the moves made so far. */
11543     static int doInit = 0;
11544
11545     if (appData.noChessProgram) return 1;
11546
11547     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11548         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11549         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11550         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11551     } else {
11552         if (first.pr != NoProc) return 1;
11553         StartChessProgram(&first);
11554     }
11555     InitChessProgram(&first, FALSE);
11556     FeedMovesToProgram(&first, currentMove);
11557
11558     if (!first.sendTime) {
11559         /* can't tell gnuchess what its clock should read,
11560            so we bow to its notion. */
11561         ResetClocks();
11562         timeRemaining[0][currentMove] = whiteTimeRemaining;
11563         timeRemaining[1][currentMove] = blackTimeRemaining;
11564     }
11565
11566     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11567                 appData.icsEngineAnalyze) && first.analysisSupport) {
11568       SendToProgram("analyze\n", &first);
11569       first.analyzing = TRUE;
11570     }
11571     return 1;
11572 }
11573
11574 /*
11575  * Button procedures
11576  */
11577 void
11578 Reset (int redraw, int init)
11579 {
11580     int i;
11581
11582     if (appData.debugMode) {
11583         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11584                 redraw, init, gameMode);
11585     }
11586     CleanupTail(); // [HGM] vari: delete any stored variations
11587     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11588     pausing = pauseExamInvalid = FALSE;
11589     startedFromSetupPosition = blackPlaysFirst = FALSE;
11590     firstMove = TRUE;
11591     whiteFlag = blackFlag = FALSE;
11592     userOfferedDraw = FALSE;
11593     hintRequested = bookRequested = FALSE;
11594     first.maybeThinking = FALSE;
11595     second.maybeThinking = FALSE;
11596     first.bookSuspend = FALSE; // [HGM] book
11597     second.bookSuspend = FALSE;
11598     thinkOutput[0] = NULLCHAR;
11599     lastHint[0] = NULLCHAR;
11600     ClearGameInfo(&gameInfo);
11601     gameInfo.variant = StringToVariant(appData.variant);
11602     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11603     ics_user_moved = ics_clock_paused = FALSE;
11604     ics_getting_history = H_FALSE;
11605     ics_gamenum = -1;
11606     white_holding[0] = black_holding[0] = NULLCHAR;
11607     ClearProgramStats();
11608     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11609
11610     ResetFrontEnd();
11611     ClearHighlights();
11612     flipView = appData.flipView;
11613     ClearPremoveHighlights();
11614     gotPremove = FALSE;
11615     alarmSounded = FALSE;
11616     killX = killY = -1; // [HGM] lion
11617
11618     GameEnds(EndOfFile, NULL, GE_PLAYER);
11619     if(appData.serverMovesName != NULL) {
11620         /* [HGM] prepare to make moves file for broadcasting */
11621         clock_t t = clock();
11622         if(serverMoves != NULL) fclose(serverMoves);
11623         serverMoves = fopen(appData.serverMovesName, "r");
11624         if(serverMoves != NULL) {
11625             fclose(serverMoves);
11626             /* delay 15 sec before overwriting, so all clients can see end */
11627             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11628         }
11629         serverMoves = fopen(appData.serverMovesName, "w");
11630     }
11631
11632     ExitAnalyzeMode();
11633     gameMode = BeginningOfGame;
11634     ModeHighlight();
11635     if(appData.icsActive) gameInfo.variant = VariantNormal;
11636     currentMove = forwardMostMove = backwardMostMove = 0;
11637     MarkTargetSquares(1);
11638     InitPosition(redraw);
11639     for (i = 0; i < MAX_MOVES; i++) {
11640         if (commentList[i] != NULL) {
11641             free(commentList[i]);
11642             commentList[i] = NULL;
11643         }
11644     }
11645     ResetClocks();
11646     timeRemaining[0][0] = whiteTimeRemaining;
11647     timeRemaining[1][0] = blackTimeRemaining;
11648
11649     if (first.pr == NoProc) {
11650         StartChessProgram(&first);
11651     }
11652     if (init) {
11653             InitChessProgram(&first, startedFromSetupPosition);
11654     }
11655     DisplayTitle("");
11656     DisplayMessage("", "");
11657     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11658     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11659     ClearMap();        // [HGM] exclude: invalidate map
11660 }
11661
11662 void
11663 AutoPlayGameLoop ()
11664 {
11665     for (;;) {
11666         if (!AutoPlayOneMove())
11667           return;
11668         if (matchMode || appData.timeDelay == 0)
11669           continue;
11670         if (appData.timeDelay < 0)
11671           return;
11672         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11673         break;
11674     }
11675 }
11676
11677 void
11678 AnalyzeNextGame()
11679 {
11680     ReloadGame(1); // next game
11681 }
11682
11683 int
11684 AutoPlayOneMove ()
11685 {
11686     int fromX, fromY, toX, toY;
11687
11688     if (appData.debugMode) {
11689       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11690     }
11691
11692     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11693       return FALSE;
11694
11695     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11696       pvInfoList[currentMove].depth = programStats.depth;
11697       pvInfoList[currentMove].score = programStats.score;
11698       pvInfoList[currentMove].time  = 0;
11699       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11700       else { // append analysis of final position as comment
11701         char buf[MSG_SIZ];
11702         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11703         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11704       }
11705       programStats.depth = 0;
11706     }
11707
11708     if (currentMove >= forwardMostMove) {
11709       if(gameMode == AnalyzeFile) {
11710           if(appData.loadGameIndex == -1) {
11711             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11712           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11713           } else {
11714           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11715         }
11716       }
11717 //      gameMode = EndOfGame;
11718 //      ModeHighlight();
11719
11720       /* [AS] Clear current move marker at the end of a game */
11721       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11722
11723       return FALSE;
11724     }
11725
11726     toX = moveList[currentMove][2] - AAA;
11727     toY = moveList[currentMove][3] - ONE;
11728
11729     if (moveList[currentMove][1] == '@') {
11730         if (appData.highlightLastMove) {
11731             SetHighlights(-1, -1, toX, toY);
11732         }
11733     } else {
11734         fromX = moveList[currentMove][0] - AAA;
11735         fromY = moveList[currentMove][1] - ONE;
11736
11737         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11738
11739         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11740
11741         if (appData.highlightLastMove) {
11742             SetHighlights(fromX, fromY, toX, toY);
11743         }
11744     }
11745     DisplayMove(currentMove);
11746     SendMoveToProgram(currentMove++, &first);
11747     DisplayBothClocks();
11748     DrawPosition(FALSE, boards[currentMove]);
11749     // [HGM] PV info: always display, routine tests if empty
11750     DisplayComment(currentMove - 1, commentList[currentMove]);
11751     return TRUE;
11752 }
11753
11754
11755 int
11756 LoadGameOneMove (ChessMove readAhead)
11757 {
11758     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11759     char promoChar = NULLCHAR;
11760     ChessMove moveType;
11761     char move[MSG_SIZ];
11762     char *p, *q;
11763
11764     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11765         gameMode != AnalyzeMode && gameMode != Training) {
11766         gameFileFP = NULL;
11767         return FALSE;
11768     }
11769
11770     yyboardindex = forwardMostMove;
11771     if (readAhead != EndOfFile) {
11772       moveType = readAhead;
11773     } else {
11774       if (gameFileFP == NULL)
11775           return FALSE;
11776       moveType = (ChessMove) Myylex();
11777     }
11778
11779     done = FALSE;
11780     switch (moveType) {
11781       case Comment:
11782         if (appData.debugMode)
11783           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11784         p = yy_text;
11785
11786         /* append the comment but don't display it */
11787         AppendComment(currentMove, p, FALSE);
11788         return TRUE;
11789
11790       case WhiteCapturesEnPassant:
11791       case BlackCapturesEnPassant:
11792       case WhitePromotion:
11793       case BlackPromotion:
11794       case WhiteNonPromotion:
11795       case BlackNonPromotion:
11796       case NormalMove:
11797       case FirstLeg:
11798       case WhiteKingSideCastle:
11799       case WhiteQueenSideCastle:
11800       case BlackKingSideCastle:
11801       case BlackQueenSideCastle:
11802       case WhiteKingSideCastleWild:
11803       case WhiteQueenSideCastleWild:
11804       case BlackKingSideCastleWild:
11805       case BlackQueenSideCastleWild:
11806       /* PUSH Fabien */
11807       case WhiteHSideCastleFR:
11808       case WhiteASideCastleFR:
11809       case BlackHSideCastleFR:
11810       case BlackASideCastleFR:
11811       /* POP Fabien */
11812         if (appData.debugMode)
11813           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11814         fromX = currentMoveString[0] - AAA;
11815         fromY = currentMoveString[1] - ONE;
11816         toX = currentMoveString[2] - AAA;
11817         toY = currentMoveString[3] - ONE;
11818         promoChar = currentMoveString[4];
11819         if(promoChar == ';') promoChar = NULLCHAR;
11820         break;
11821
11822       case WhiteDrop:
11823       case BlackDrop:
11824         if (appData.debugMode)
11825           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11826         fromX = moveType == WhiteDrop ?
11827           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11828         (int) CharToPiece(ToLower(currentMoveString[0]));
11829         fromY = DROP_RANK;
11830         toX = currentMoveString[2] - AAA;
11831         toY = currentMoveString[3] - ONE;
11832         break;
11833
11834       case WhiteWins:
11835       case BlackWins:
11836       case GameIsDrawn:
11837       case GameUnfinished:
11838         if (appData.debugMode)
11839           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11840         p = strchr(yy_text, '{');
11841         if (p == NULL) p = strchr(yy_text, '(');
11842         if (p == NULL) {
11843             p = yy_text;
11844             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11845         } else {
11846             q = strchr(p, *p == '{' ? '}' : ')');
11847             if (q != NULL) *q = NULLCHAR;
11848             p++;
11849         }
11850         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11851         GameEnds(moveType, p, GE_FILE);
11852         done = TRUE;
11853         if (cmailMsgLoaded) {
11854             ClearHighlights();
11855             flipView = WhiteOnMove(currentMove);
11856             if (moveType == GameUnfinished) flipView = !flipView;
11857             if (appData.debugMode)
11858               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11859         }
11860         break;
11861
11862       case EndOfFile:
11863         if (appData.debugMode)
11864           fprintf(debugFP, "Parser hit end of file\n");
11865         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11866           case MT_NONE:
11867           case MT_CHECK:
11868             break;
11869           case MT_CHECKMATE:
11870           case MT_STAINMATE:
11871             if (WhiteOnMove(currentMove)) {
11872                 GameEnds(BlackWins, "Black mates", GE_FILE);
11873             } else {
11874                 GameEnds(WhiteWins, "White mates", GE_FILE);
11875             }
11876             break;
11877           case MT_STALEMATE:
11878             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11879             break;
11880         }
11881         done = TRUE;
11882         break;
11883
11884       case MoveNumberOne:
11885         if (lastLoadGameStart == GNUChessGame) {
11886             /* GNUChessGames have numbers, but they aren't move numbers */
11887             if (appData.debugMode)
11888               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11889                       yy_text, (int) moveType);
11890             return LoadGameOneMove(EndOfFile); /* tail recursion */
11891         }
11892         /* else fall thru */
11893
11894       case XBoardGame:
11895       case GNUChessGame:
11896       case PGNTag:
11897         /* Reached start of next game in file */
11898         if (appData.debugMode)
11899           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11900         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11901           case MT_NONE:
11902           case MT_CHECK:
11903             break;
11904           case MT_CHECKMATE:
11905           case MT_STAINMATE:
11906             if (WhiteOnMove(currentMove)) {
11907                 GameEnds(BlackWins, "Black mates", GE_FILE);
11908             } else {
11909                 GameEnds(WhiteWins, "White mates", GE_FILE);
11910             }
11911             break;
11912           case MT_STALEMATE:
11913             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11914             break;
11915         }
11916         done = TRUE;
11917         break;
11918
11919       case PositionDiagram:     /* should not happen; ignore */
11920       case ElapsedTime:         /* ignore */
11921       case NAG:                 /* ignore */
11922         if (appData.debugMode)
11923           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11924                   yy_text, (int) moveType);
11925         return LoadGameOneMove(EndOfFile); /* tail recursion */
11926
11927       case IllegalMove:
11928         if (appData.testLegality) {
11929             if (appData.debugMode)
11930               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11931             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11932                     (forwardMostMove / 2) + 1,
11933                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11934             DisplayError(move, 0);
11935             done = TRUE;
11936         } else {
11937             if (appData.debugMode)
11938               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11939                       yy_text, currentMoveString);
11940             fromX = currentMoveString[0] - AAA;
11941             fromY = currentMoveString[1] - ONE;
11942             toX = currentMoveString[2] - AAA;
11943             toY = currentMoveString[3] - ONE;
11944             promoChar = currentMoveString[4];
11945         }
11946         break;
11947
11948       case AmbiguousMove:
11949         if (appData.debugMode)
11950           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11951         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11952                 (forwardMostMove / 2) + 1,
11953                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11954         DisplayError(move, 0);
11955         done = TRUE;
11956         break;
11957
11958       default:
11959       case ImpossibleMove:
11960         if (appData.debugMode)
11961           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11962         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11963                 (forwardMostMove / 2) + 1,
11964                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11965         DisplayError(move, 0);
11966         done = TRUE;
11967         break;
11968     }
11969
11970     if (done) {
11971         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11972             DrawPosition(FALSE, boards[currentMove]);
11973             DisplayBothClocks();
11974             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11975               DisplayComment(currentMove - 1, commentList[currentMove]);
11976         }
11977         (void) StopLoadGameTimer();
11978         gameFileFP = NULL;
11979         cmailOldMove = forwardMostMove;
11980         return FALSE;
11981     } else {
11982         /* currentMoveString is set as a side-effect of yylex */
11983
11984         thinkOutput[0] = NULLCHAR;
11985         MakeMove(fromX, fromY, toX, toY, promoChar);
11986         killX = killY = -1; // [HGM] lion: used up
11987         currentMove = forwardMostMove;
11988         return TRUE;
11989     }
11990 }
11991
11992 /* Load the nth game from the given file */
11993 int
11994 LoadGameFromFile (char *filename, int n, char *title, int useList)
11995 {
11996     FILE *f;
11997     char buf[MSG_SIZ];
11998
11999     if (strcmp(filename, "-") == 0) {
12000         f = stdin;
12001         title = "stdin";
12002     } else {
12003         f = fopen(filename, "rb");
12004         if (f == NULL) {
12005           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12006             DisplayError(buf, errno);
12007             return FALSE;
12008         }
12009     }
12010     if (fseek(f, 0, 0) == -1) {
12011         /* f is not seekable; probably a pipe */
12012         useList = FALSE;
12013     }
12014     if (useList && n == 0) {
12015         int error = GameListBuild(f);
12016         if (error) {
12017             DisplayError(_("Cannot build game list"), error);
12018         } else if (!ListEmpty(&gameList) &&
12019                    ((ListGame *) gameList.tailPred)->number > 1) {
12020             GameListPopUp(f, title);
12021             return TRUE;
12022         }
12023         GameListDestroy();
12024         n = 1;
12025     }
12026     if (n == 0) n = 1;
12027     return LoadGame(f, n, title, FALSE);
12028 }
12029
12030
12031 void
12032 MakeRegisteredMove ()
12033 {
12034     int fromX, fromY, toX, toY;
12035     char promoChar;
12036     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12037         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12038           case CMAIL_MOVE:
12039           case CMAIL_DRAW:
12040             if (appData.debugMode)
12041               fprintf(debugFP, "Restoring %s for game %d\n",
12042                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12043
12044             thinkOutput[0] = NULLCHAR;
12045             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12046             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12047             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12048             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12049             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12050             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12051             MakeMove(fromX, fromY, toX, toY, promoChar);
12052             ShowMove(fromX, fromY, toX, toY);
12053
12054             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12055               case MT_NONE:
12056               case MT_CHECK:
12057                 break;
12058
12059               case MT_CHECKMATE:
12060               case MT_STAINMATE:
12061                 if (WhiteOnMove(currentMove)) {
12062                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12063                 } else {
12064                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12065                 }
12066                 break;
12067
12068               case MT_STALEMATE:
12069                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12070                 break;
12071             }
12072
12073             break;
12074
12075           case CMAIL_RESIGN:
12076             if (WhiteOnMove(currentMove)) {
12077                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12078             } else {
12079                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12080             }
12081             break;
12082
12083           case CMAIL_ACCEPT:
12084             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12085             break;
12086
12087           default:
12088             break;
12089         }
12090     }
12091
12092     return;
12093 }
12094
12095 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12096 int
12097 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12098 {
12099     int retVal;
12100
12101     if (gameNumber > nCmailGames) {
12102         DisplayError(_("No more games in this message"), 0);
12103         return FALSE;
12104     }
12105     if (f == lastLoadGameFP) {
12106         int offset = gameNumber - lastLoadGameNumber;
12107         if (offset == 0) {
12108             cmailMsg[0] = NULLCHAR;
12109             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12110                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12111                 nCmailMovesRegistered--;
12112             }
12113             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12114             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12115                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12116             }
12117         } else {
12118             if (! RegisterMove()) return FALSE;
12119         }
12120     }
12121
12122     retVal = LoadGame(f, gameNumber, title, useList);
12123
12124     /* Make move registered during previous look at this game, if any */
12125     MakeRegisteredMove();
12126
12127     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12128         commentList[currentMove]
12129           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12130         DisplayComment(currentMove - 1, commentList[currentMove]);
12131     }
12132
12133     return retVal;
12134 }
12135
12136 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12137 int
12138 ReloadGame (int offset)
12139 {
12140     int gameNumber = lastLoadGameNumber + offset;
12141     if (lastLoadGameFP == NULL) {
12142         DisplayError(_("No game has been loaded yet"), 0);
12143         return FALSE;
12144     }
12145     if (gameNumber <= 0) {
12146         DisplayError(_("Can't back up any further"), 0);
12147         return FALSE;
12148     }
12149     if (cmailMsgLoaded) {
12150         return CmailLoadGame(lastLoadGameFP, gameNumber,
12151                              lastLoadGameTitle, lastLoadGameUseList);
12152     } else {
12153         return LoadGame(lastLoadGameFP, gameNumber,
12154                         lastLoadGameTitle, lastLoadGameUseList);
12155     }
12156 }
12157
12158 int keys[EmptySquare+1];
12159
12160 int
12161 PositionMatches (Board b1, Board b2)
12162 {
12163     int r, f, sum=0;
12164     switch(appData.searchMode) {
12165         case 1: return CompareWithRights(b1, b2);
12166         case 2:
12167             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12168                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12169             }
12170             return TRUE;
12171         case 3:
12172             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12173               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12174                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12175             }
12176             return sum==0;
12177         case 4:
12178             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12179                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12180             }
12181             return sum==0;
12182     }
12183     return TRUE;
12184 }
12185
12186 #define Q_PROMO  4
12187 #define Q_EP     3
12188 #define Q_BCASTL 2
12189 #define Q_WCASTL 1
12190
12191 int pieceList[256], quickBoard[256];
12192 ChessSquare pieceType[256] = { EmptySquare };
12193 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12194 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12195 int soughtTotal, turn;
12196 Boolean epOK, flipSearch;
12197
12198 typedef struct {
12199     unsigned char piece, to;
12200 } Move;
12201
12202 #define DSIZE (250000)
12203
12204 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12205 Move *moveDatabase = initialSpace;
12206 unsigned int movePtr, dataSize = DSIZE;
12207
12208 int
12209 MakePieceList (Board board, int *counts)
12210 {
12211     int r, f, n=Q_PROMO, total=0;
12212     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12213     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12214         int sq = f + (r<<4);
12215         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12216             quickBoard[sq] = ++n;
12217             pieceList[n] = sq;
12218             pieceType[n] = board[r][f];
12219             counts[board[r][f]]++;
12220             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12221             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12222             total++;
12223         }
12224     }
12225     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12226     return total;
12227 }
12228
12229 void
12230 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12231 {
12232     int sq = fromX + (fromY<<4);
12233     int piece = quickBoard[sq], rook;
12234     quickBoard[sq] = 0;
12235     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12236     if(piece == pieceList[1] && fromY == toY) {
12237       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12238         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12239         moveDatabase[movePtr++].piece = Q_WCASTL;
12240         quickBoard[sq] = piece;
12241         piece = quickBoard[from]; quickBoard[from] = 0;
12242         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12243       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12244         quickBoard[sq] = 0; // remove Rook
12245         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12246         moveDatabase[movePtr++].piece = Q_WCASTL;
12247         quickBoard[sq] = pieceList[1]; // put King
12248         piece = rook;
12249         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12250       }
12251     } else
12252     if(piece == pieceList[2] && fromY == toY) {
12253       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12254         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12255         moveDatabase[movePtr++].piece = Q_BCASTL;
12256         quickBoard[sq] = piece;
12257         piece = quickBoard[from]; quickBoard[from] = 0;
12258         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12259       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12260         quickBoard[sq] = 0; // remove Rook
12261         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12262         moveDatabase[movePtr++].piece = Q_BCASTL;
12263         quickBoard[sq] = pieceList[2]; // put King
12264         piece = rook;
12265         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12266       }
12267     } else
12268     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12269         quickBoard[(fromY<<4)+toX] = 0;
12270         moveDatabase[movePtr].piece = Q_EP;
12271         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12272         moveDatabase[movePtr].to = sq;
12273     } else
12274     if(promoPiece != pieceType[piece]) {
12275         moveDatabase[movePtr++].piece = Q_PROMO;
12276         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12277     }
12278     moveDatabase[movePtr].piece = piece;
12279     quickBoard[sq] = piece;
12280     movePtr++;
12281 }
12282
12283 int
12284 PackGame (Board board)
12285 {
12286     Move *newSpace = NULL;
12287     moveDatabase[movePtr].piece = 0; // terminate previous game
12288     if(movePtr > dataSize) {
12289         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12290         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12291         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12292         if(newSpace) {
12293             int i;
12294             Move *p = moveDatabase, *q = newSpace;
12295             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12296             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12297             moveDatabase = newSpace;
12298         } else { // calloc failed, we must be out of memory. Too bad...
12299             dataSize = 0; // prevent calloc events for all subsequent games
12300             return 0;     // and signal this one isn't cached
12301         }
12302     }
12303     movePtr++;
12304     MakePieceList(board, counts);
12305     return movePtr;
12306 }
12307
12308 int
12309 QuickCompare (Board board, int *minCounts, int *maxCounts)
12310 {   // compare according to search mode
12311     int r, f;
12312     switch(appData.searchMode)
12313     {
12314       case 1: // exact position match
12315         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12316         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12317             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12318         }
12319         break;
12320       case 2: // can have extra material on empty squares
12321         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12322             if(board[r][f] == EmptySquare) continue;
12323             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12324         }
12325         break;
12326       case 3: // material with exact Pawn structure
12327         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12328             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12329             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12330         } // fall through to material comparison
12331       case 4: // exact material
12332         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12333         break;
12334       case 6: // material range with given imbalance
12335         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12336         // fall through to range comparison
12337       case 5: // material range
12338         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12339     }
12340     return TRUE;
12341 }
12342
12343 int
12344 QuickScan (Board board, Move *move)
12345 {   // reconstruct game,and compare all positions in it
12346     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12347     do {
12348         int piece = move->piece;
12349         int to = move->to, from = pieceList[piece];
12350         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12351           if(!piece) return -1;
12352           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12353             piece = (++move)->piece;
12354             from = pieceList[piece];
12355             counts[pieceType[piece]]--;
12356             pieceType[piece] = (ChessSquare) move->to;
12357             counts[move->to]++;
12358           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12359             counts[pieceType[quickBoard[to]]]--;
12360             quickBoard[to] = 0; total--;
12361             move++;
12362             continue;
12363           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12364             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12365             from  = pieceList[piece]; // so this must be King
12366             quickBoard[from] = 0;
12367             pieceList[piece] = to;
12368             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12369             quickBoard[from] = 0; // rook
12370             quickBoard[to] = piece;
12371             to = move->to; piece = move->piece;
12372             goto aftercastle;
12373           }
12374         }
12375         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12376         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12377         quickBoard[from] = 0;
12378       aftercastle:
12379         quickBoard[to] = piece;
12380         pieceList[piece] = to;
12381         cnt++; turn ^= 3;
12382         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12383            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12384            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12385                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12386           ) {
12387             static int lastCounts[EmptySquare+1];
12388             int i;
12389             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12390             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12391         } else stretch = 0;
12392         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12393         move++;
12394     } while(1);
12395 }
12396
12397 void
12398 InitSearch ()
12399 {
12400     int r, f;
12401     flipSearch = FALSE;
12402     CopyBoard(soughtBoard, boards[currentMove]);
12403     soughtTotal = MakePieceList(soughtBoard, maxSought);
12404     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12405     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12406     CopyBoard(reverseBoard, boards[currentMove]);
12407     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12408         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12409         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12410         reverseBoard[r][f] = piece;
12411     }
12412     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12413     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12414     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12415                  || (boards[currentMove][CASTLING][2] == NoRights ||
12416                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12417                  && (boards[currentMove][CASTLING][5] == NoRights ||
12418                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12419       ) {
12420         flipSearch = TRUE;
12421         CopyBoard(flipBoard, soughtBoard);
12422         CopyBoard(rotateBoard, reverseBoard);
12423         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12424             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12425             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12426         }
12427     }
12428     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12429     if(appData.searchMode >= 5) {
12430         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12431         MakePieceList(soughtBoard, minSought);
12432         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12433     }
12434     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12435         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12436 }
12437
12438 GameInfo dummyInfo;
12439 static int creatingBook;
12440
12441 int
12442 GameContainsPosition (FILE *f, ListGame *lg)
12443 {
12444     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12445     int fromX, fromY, toX, toY;
12446     char promoChar;
12447     static int initDone=FALSE;
12448
12449     // weed out games based on numerical tag comparison
12450     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12451     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12452     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12453     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12454     if(!initDone) {
12455         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12456         initDone = TRUE;
12457     }
12458     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12459     else CopyBoard(boards[scratch], initialPosition); // default start position
12460     if(lg->moves) {
12461         turn = btm + 1;
12462         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12463         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12464     }
12465     if(btm) plyNr++;
12466     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12467     fseek(f, lg->offset, 0);
12468     yynewfile(f);
12469     while(1) {
12470         yyboardindex = scratch;
12471         quickFlag = plyNr+1;
12472         next = Myylex();
12473         quickFlag = 0;
12474         switch(next) {
12475             case PGNTag:
12476                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12477             default:
12478                 continue;
12479
12480             case XBoardGame:
12481             case GNUChessGame:
12482                 if(plyNr) return -1; // after we have seen moves, this is for new game
12483               continue;
12484
12485             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12486             case ImpossibleMove:
12487             case WhiteWins: // game ends here with these four
12488             case BlackWins:
12489             case GameIsDrawn:
12490             case GameUnfinished:
12491                 return -1;
12492
12493             case IllegalMove:
12494                 if(appData.testLegality) return -1;
12495             case WhiteCapturesEnPassant:
12496             case BlackCapturesEnPassant:
12497             case WhitePromotion:
12498             case BlackPromotion:
12499             case WhiteNonPromotion:
12500             case BlackNonPromotion:
12501             case NormalMove:
12502             case FirstLeg:
12503             case WhiteKingSideCastle:
12504             case WhiteQueenSideCastle:
12505             case BlackKingSideCastle:
12506             case BlackQueenSideCastle:
12507             case WhiteKingSideCastleWild:
12508             case WhiteQueenSideCastleWild:
12509             case BlackKingSideCastleWild:
12510             case BlackQueenSideCastleWild:
12511             case WhiteHSideCastleFR:
12512             case WhiteASideCastleFR:
12513             case BlackHSideCastleFR:
12514             case BlackASideCastleFR:
12515                 fromX = currentMoveString[0] - AAA;
12516                 fromY = currentMoveString[1] - ONE;
12517                 toX = currentMoveString[2] - AAA;
12518                 toY = currentMoveString[3] - ONE;
12519                 promoChar = currentMoveString[4];
12520                 break;
12521             case WhiteDrop:
12522             case BlackDrop:
12523                 fromX = next == WhiteDrop ?
12524                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12525                   (int) CharToPiece(ToLower(currentMoveString[0]));
12526                 fromY = DROP_RANK;
12527                 toX = currentMoveString[2] - AAA;
12528                 toY = currentMoveString[3] - ONE;
12529                 promoChar = 0;
12530                 break;
12531         }
12532         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12533         plyNr++;
12534         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12535         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12536         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12537         if(appData.findMirror) {
12538             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12539             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12540         }
12541     }
12542 }
12543
12544 /* Load the nth game from open file f */
12545 int
12546 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12547 {
12548     ChessMove cm;
12549     char buf[MSG_SIZ];
12550     int gn = gameNumber;
12551     ListGame *lg = NULL;
12552     int numPGNTags = 0;
12553     int err, pos = -1;
12554     GameMode oldGameMode;
12555     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12556
12557     if (appData.debugMode)
12558         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12559
12560     if (gameMode == Training )
12561         SetTrainingModeOff();
12562
12563     oldGameMode = gameMode;
12564     if (gameMode != BeginningOfGame) {
12565       Reset(FALSE, TRUE);
12566     }
12567     killX = killY = -1; // [HGM] lion: in case we did not Reset
12568
12569     gameFileFP = f;
12570     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12571         fclose(lastLoadGameFP);
12572     }
12573
12574     if (useList) {
12575         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12576
12577         if (lg) {
12578             fseek(f, lg->offset, 0);
12579             GameListHighlight(gameNumber);
12580             pos = lg->position;
12581             gn = 1;
12582         }
12583         else {
12584             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12585               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12586             else
12587             DisplayError(_("Game number out of range"), 0);
12588             return FALSE;
12589         }
12590     } else {
12591         GameListDestroy();
12592         if (fseek(f, 0, 0) == -1) {
12593             if (f == lastLoadGameFP ?
12594                 gameNumber == lastLoadGameNumber + 1 :
12595                 gameNumber == 1) {
12596                 gn = 1;
12597             } else {
12598                 DisplayError(_("Can't seek on game file"), 0);
12599                 return FALSE;
12600             }
12601         }
12602     }
12603     lastLoadGameFP = f;
12604     lastLoadGameNumber = gameNumber;
12605     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12606     lastLoadGameUseList = useList;
12607
12608     yynewfile(f);
12609
12610     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12611       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12612                 lg->gameInfo.black);
12613             DisplayTitle(buf);
12614     } else if (*title != NULLCHAR) {
12615         if (gameNumber > 1) {
12616           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12617             DisplayTitle(buf);
12618         } else {
12619             DisplayTitle(title);
12620         }
12621     }
12622
12623     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12624         gameMode = PlayFromGameFile;
12625         ModeHighlight();
12626     }
12627
12628     currentMove = forwardMostMove = backwardMostMove = 0;
12629     CopyBoard(boards[0], initialPosition);
12630     StopClocks();
12631
12632     /*
12633      * Skip the first gn-1 games in the file.
12634      * Also skip over anything that precedes an identifiable
12635      * start of game marker, to avoid being confused by
12636      * garbage at the start of the file.  Currently
12637      * recognized start of game markers are the move number "1",
12638      * the pattern "gnuchess .* game", the pattern
12639      * "^[#;%] [^ ]* game file", and a PGN tag block.
12640      * A game that starts with one of the latter two patterns
12641      * will also have a move number 1, possibly
12642      * following a position diagram.
12643      * 5-4-02: Let's try being more lenient and allowing a game to
12644      * start with an unnumbered move.  Does that break anything?
12645      */
12646     cm = lastLoadGameStart = EndOfFile;
12647     while (gn > 0) {
12648         yyboardindex = forwardMostMove;
12649         cm = (ChessMove) Myylex();
12650         switch (cm) {
12651           case EndOfFile:
12652             if (cmailMsgLoaded) {
12653                 nCmailGames = CMAIL_MAX_GAMES - gn;
12654             } else {
12655                 Reset(TRUE, TRUE);
12656                 DisplayError(_("Game not found in file"), 0);
12657             }
12658             return FALSE;
12659
12660           case GNUChessGame:
12661           case XBoardGame:
12662             gn--;
12663             lastLoadGameStart = cm;
12664             break;
12665
12666           case MoveNumberOne:
12667             switch (lastLoadGameStart) {
12668               case GNUChessGame:
12669               case XBoardGame:
12670               case PGNTag:
12671                 break;
12672               case MoveNumberOne:
12673               case EndOfFile:
12674                 gn--;           /* count this game */
12675                 lastLoadGameStart = cm;
12676                 break;
12677               default:
12678                 /* impossible */
12679                 break;
12680             }
12681             break;
12682
12683           case PGNTag:
12684             switch (lastLoadGameStart) {
12685               case GNUChessGame:
12686               case PGNTag:
12687               case MoveNumberOne:
12688               case EndOfFile:
12689                 gn--;           /* count this game */
12690                 lastLoadGameStart = cm;
12691                 break;
12692               case XBoardGame:
12693                 lastLoadGameStart = cm; /* game counted already */
12694                 break;
12695               default:
12696                 /* impossible */
12697                 break;
12698             }
12699             if (gn > 0) {
12700                 do {
12701                     yyboardindex = forwardMostMove;
12702                     cm = (ChessMove) Myylex();
12703                 } while (cm == PGNTag || cm == Comment);
12704             }
12705             break;
12706
12707           case WhiteWins:
12708           case BlackWins:
12709           case GameIsDrawn:
12710             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12711                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12712                     != CMAIL_OLD_RESULT) {
12713                     nCmailResults ++ ;
12714                     cmailResult[  CMAIL_MAX_GAMES
12715                                 - gn - 1] = CMAIL_OLD_RESULT;
12716                 }
12717             }
12718             break;
12719
12720           case NormalMove:
12721           case FirstLeg:
12722             /* Only a NormalMove can be at the start of a game
12723              * without a position diagram. */
12724             if (lastLoadGameStart == EndOfFile ) {
12725               gn--;
12726               lastLoadGameStart = MoveNumberOne;
12727             }
12728             break;
12729
12730           default:
12731             break;
12732         }
12733     }
12734
12735     if (appData.debugMode)
12736       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12737
12738     if (cm == XBoardGame) {
12739         /* Skip any header junk before position diagram and/or move 1 */
12740         for (;;) {
12741             yyboardindex = forwardMostMove;
12742             cm = (ChessMove) Myylex();
12743
12744             if (cm == EndOfFile ||
12745                 cm == GNUChessGame || cm == XBoardGame) {
12746                 /* Empty game; pretend end-of-file and handle later */
12747                 cm = EndOfFile;
12748                 break;
12749             }
12750
12751             if (cm == MoveNumberOne || cm == PositionDiagram ||
12752                 cm == PGNTag || cm == Comment)
12753               break;
12754         }
12755     } else if (cm == GNUChessGame) {
12756         if (gameInfo.event != NULL) {
12757             free(gameInfo.event);
12758         }
12759         gameInfo.event = StrSave(yy_text);
12760     }
12761
12762     startedFromSetupPosition = FALSE;
12763     while (cm == PGNTag) {
12764         if (appData.debugMode)
12765           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12766         err = ParsePGNTag(yy_text, &gameInfo);
12767         if (!err) numPGNTags++;
12768
12769         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12770         if(gameInfo.variant != oldVariant) {
12771             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12772             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12773             InitPosition(TRUE);
12774             oldVariant = gameInfo.variant;
12775             if (appData.debugMode)
12776               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12777         }
12778
12779
12780         if (gameInfo.fen != NULL) {
12781           Board initial_position;
12782           startedFromSetupPosition = TRUE;
12783           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12784             Reset(TRUE, TRUE);
12785             DisplayError(_("Bad FEN position in file"), 0);
12786             return FALSE;
12787           }
12788           CopyBoard(boards[0], initial_position);
12789           if (blackPlaysFirst) {
12790             currentMove = forwardMostMove = backwardMostMove = 1;
12791             CopyBoard(boards[1], initial_position);
12792             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12793             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12794             timeRemaining[0][1] = whiteTimeRemaining;
12795             timeRemaining[1][1] = blackTimeRemaining;
12796             if (commentList[0] != NULL) {
12797               commentList[1] = commentList[0];
12798               commentList[0] = NULL;
12799             }
12800           } else {
12801             currentMove = forwardMostMove = backwardMostMove = 0;
12802           }
12803           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12804           {   int i;
12805               initialRulePlies = FENrulePlies;
12806               for( i=0; i< nrCastlingRights; i++ )
12807                   initialRights[i] = initial_position[CASTLING][i];
12808           }
12809           yyboardindex = forwardMostMove;
12810           free(gameInfo.fen);
12811           gameInfo.fen = NULL;
12812         }
12813
12814         yyboardindex = forwardMostMove;
12815         cm = (ChessMove) Myylex();
12816
12817         /* Handle comments interspersed among the tags */
12818         while (cm == Comment) {
12819             char *p;
12820             if (appData.debugMode)
12821               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12822             p = yy_text;
12823             AppendComment(currentMove, p, FALSE);
12824             yyboardindex = forwardMostMove;
12825             cm = (ChessMove) Myylex();
12826         }
12827     }
12828
12829     /* don't rely on existence of Event tag since if game was
12830      * pasted from clipboard the Event tag may not exist
12831      */
12832     if (numPGNTags > 0){
12833         char *tags;
12834         if (gameInfo.variant == VariantNormal) {
12835           VariantClass v = StringToVariant(gameInfo.event);
12836           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12837           if(v < VariantShogi) gameInfo.variant = v;
12838         }
12839         if (!matchMode) {
12840           if( appData.autoDisplayTags ) {
12841             tags = PGNTags(&gameInfo);
12842             TagsPopUp(tags, CmailMsg());
12843             free(tags);
12844           }
12845         }
12846     } else {
12847         /* Make something up, but don't display it now */
12848         SetGameInfo();
12849         TagsPopDown();
12850     }
12851
12852     if (cm == PositionDiagram) {
12853         int i, j;
12854         char *p;
12855         Board initial_position;
12856
12857         if (appData.debugMode)
12858           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12859
12860         if (!startedFromSetupPosition) {
12861             p = yy_text;
12862             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12863               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12864                 switch (*p) {
12865                   case '{':
12866                   case '[':
12867                   case '-':
12868                   case ' ':
12869                   case '\t':
12870                   case '\n':
12871                   case '\r':
12872                     break;
12873                   default:
12874                     initial_position[i][j++] = CharToPiece(*p);
12875                     break;
12876                 }
12877             while (*p == ' ' || *p == '\t' ||
12878                    *p == '\n' || *p == '\r') p++;
12879
12880             if (strncmp(p, "black", strlen("black"))==0)
12881               blackPlaysFirst = TRUE;
12882             else
12883               blackPlaysFirst = FALSE;
12884             startedFromSetupPosition = TRUE;
12885
12886             CopyBoard(boards[0], initial_position);
12887             if (blackPlaysFirst) {
12888                 currentMove = forwardMostMove = backwardMostMove = 1;
12889                 CopyBoard(boards[1], initial_position);
12890                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12891                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12892                 timeRemaining[0][1] = whiteTimeRemaining;
12893                 timeRemaining[1][1] = blackTimeRemaining;
12894                 if (commentList[0] != NULL) {
12895                     commentList[1] = commentList[0];
12896                     commentList[0] = NULL;
12897                 }
12898             } else {
12899                 currentMove = forwardMostMove = backwardMostMove = 0;
12900             }
12901         }
12902         yyboardindex = forwardMostMove;
12903         cm = (ChessMove) Myylex();
12904     }
12905
12906   if(!creatingBook) {
12907     if (first.pr == NoProc) {
12908         StartChessProgram(&first);
12909     }
12910     InitChessProgram(&first, FALSE);
12911     SendToProgram("force\n", &first);
12912     if (startedFromSetupPosition) {
12913         SendBoard(&first, forwardMostMove);
12914     if (appData.debugMode) {
12915         fprintf(debugFP, "Load Game\n");
12916     }
12917         DisplayBothClocks();
12918     }
12919   }
12920
12921     /* [HGM] server: flag to write setup moves in broadcast file as one */
12922     loadFlag = appData.suppressLoadMoves;
12923
12924     while (cm == Comment) {
12925         char *p;
12926         if (appData.debugMode)
12927           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12928         p = yy_text;
12929         AppendComment(currentMove, p, FALSE);
12930         yyboardindex = forwardMostMove;
12931         cm = (ChessMove) Myylex();
12932     }
12933
12934     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12935         cm == WhiteWins || cm == BlackWins ||
12936         cm == GameIsDrawn || cm == GameUnfinished) {
12937         DisplayMessage("", _("No moves in game"));
12938         if (cmailMsgLoaded) {
12939             if (appData.debugMode)
12940               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12941             ClearHighlights();
12942             flipView = FALSE;
12943         }
12944         DrawPosition(FALSE, boards[currentMove]);
12945         DisplayBothClocks();
12946         gameMode = EditGame;
12947         ModeHighlight();
12948         gameFileFP = NULL;
12949         cmailOldMove = 0;
12950         return TRUE;
12951     }
12952
12953     // [HGM] PV info: routine tests if comment empty
12954     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12955         DisplayComment(currentMove - 1, commentList[currentMove]);
12956     }
12957     if (!matchMode && appData.timeDelay != 0)
12958       DrawPosition(FALSE, boards[currentMove]);
12959
12960     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12961       programStats.ok_to_send = 1;
12962     }
12963
12964     /* if the first token after the PGN tags is a move
12965      * and not move number 1, retrieve it from the parser
12966      */
12967     if (cm != MoveNumberOne)
12968         LoadGameOneMove(cm);
12969
12970     /* load the remaining moves from the file */
12971     while (LoadGameOneMove(EndOfFile)) {
12972       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12973       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12974     }
12975
12976     /* rewind to the start of the game */
12977     currentMove = backwardMostMove;
12978
12979     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12980
12981     if (oldGameMode == AnalyzeFile) {
12982       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12983       AnalyzeFileEvent();
12984     } else
12985     if (oldGameMode == AnalyzeMode) {
12986       AnalyzeFileEvent();
12987     }
12988
12989     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12990         long int w, b; // [HGM] adjourn: restore saved clock times
12991         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12992         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12993             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12994             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12995         }
12996     }
12997
12998     if(creatingBook) return TRUE;
12999     if (!matchMode && pos > 0) {
13000         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13001     } else
13002     if (matchMode || appData.timeDelay == 0) {
13003       ToEndEvent();
13004     } else if (appData.timeDelay > 0) {
13005       AutoPlayGameLoop();
13006     }
13007
13008     if (appData.debugMode)
13009         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13010
13011     loadFlag = 0; /* [HGM] true game starts */
13012     return TRUE;
13013 }
13014
13015 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13016 int
13017 ReloadPosition (int offset)
13018 {
13019     int positionNumber = lastLoadPositionNumber + offset;
13020     if (lastLoadPositionFP == NULL) {
13021         DisplayError(_("No position has been loaded yet"), 0);
13022         return FALSE;
13023     }
13024     if (positionNumber <= 0) {
13025         DisplayError(_("Can't back up any further"), 0);
13026         return FALSE;
13027     }
13028     return LoadPosition(lastLoadPositionFP, positionNumber,
13029                         lastLoadPositionTitle);
13030 }
13031
13032 /* Load the nth position from the given file */
13033 int
13034 LoadPositionFromFile (char *filename, int n, char *title)
13035 {
13036     FILE *f;
13037     char buf[MSG_SIZ];
13038
13039     if (strcmp(filename, "-") == 0) {
13040         return LoadPosition(stdin, n, "stdin");
13041     } else {
13042         f = fopen(filename, "rb");
13043         if (f == NULL) {
13044             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13045             DisplayError(buf, errno);
13046             return FALSE;
13047         } else {
13048             return LoadPosition(f, n, title);
13049         }
13050     }
13051 }
13052
13053 /* Load the nth position from the given open file, and close it */
13054 int
13055 LoadPosition (FILE *f, int positionNumber, char *title)
13056 {
13057     char *p, line[MSG_SIZ];
13058     Board initial_position;
13059     int i, j, fenMode, pn;
13060
13061     if (gameMode == Training )
13062         SetTrainingModeOff();
13063
13064     if (gameMode != BeginningOfGame) {
13065         Reset(FALSE, TRUE);
13066     }
13067     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13068         fclose(lastLoadPositionFP);
13069     }
13070     if (positionNumber == 0) positionNumber = 1;
13071     lastLoadPositionFP = f;
13072     lastLoadPositionNumber = positionNumber;
13073     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13074     if (first.pr == NoProc && !appData.noChessProgram) {
13075       StartChessProgram(&first);
13076       InitChessProgram(&first, FALSE);
13077     }
13078     pn = positionNumber;
13079     if (positionNumber < 0) {
13080         /* Negative position number means to seek to that byte offset */
13081         if (fseek(f, -positionNumber, 0) == -1) {
13082             DisplayError(_("Can't seek on position file"), 0);
13083             return FALSE;
13084         };
13085         pn = 1;
13086     } else {
13087         if (fseek(f, 0, 0) == -1) {
13088             if (f == lastLoadPositionFP ?
13089                 positionNumber == lastLoadPositionNumber + 1 :
13090                 positionNumber == 1) {
13091                 pn = 1;
13092             } else {
13093                 DisplayError(_("Can't seek on position file"), 0);
13094                 return FALSE;
13095             }
13096         }
13097     }
13098     /* See if this file is FEN or old-style xboard */
13099     if (fgets(line, MSG_SIZ, f) == NULL) {
13100         DisplayError(_("Position not found in file"), 0);
13101         return FALSE;
13102     }
13103     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13104     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13105
13106     if (pn >= 2) {
13107         if (fenMode || line[0] == '#') pn--;
13108         while (pn > 0) {
13109             /* skip positions before number pn */
13110             if (fgets(line, MSG_SIZ, f) == NULL) {
13111                 Reset(TRUE, TRUE);
13112                 DisplayError(_("Position not found in file"), 0);
13113                 return FALSE;
13114             }
13115             if (fenMode || line[0] == '#') pn--;
13116         }
13117     }
13118
13119     if (fenMode) {
13120         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13121             DisplayError(_("Bad FEN position in file"), 0);
13122             return FALSE;
13123         }
13124     } else {
13125         (void) fgets(line, MSG_SIZ, f);
13126         (void) fgets(line, MSG_SIZ, f);
13127
13128         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13129             (void) fgets(line, MSG_SIZ, f);
13130             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13131                 if (*p == ' ')
13132                   continue;
13133                 initial_position[i][j++] = CharToPiece(*p);
13134             }
13135         }
13136
13137         blackPlaysFirst = FALSE;
13138         if (!feof(f)) {
13139             (void) fgets(line, MSG_SIZ, f);
13140             if (strncmp(line, "black", strlen("black"))==0)
13141               blackPlaysFirst = TRUE;
13142         }
13143     }
13144     startedFromSetupPosition = TRUE;
13145
13146     CopyBoard(boards[0], initial_position);
13147     if (blackPlaysFirst) {
13148         currentMove = forwardMostMove = backwardMostMove = 1;
13149         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13150         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13151         CopyBoard(boards[1], initial_position);
13152         DisplayMessage("", _("Black to play"));
13153     } else {
13154         currentMove = forwardMostMove = backwardMostMove = 0;
13155         DisplayMessage("", _("White to play"));
13156     }
13157     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13158     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13159         SendToProgram("force\n", &first);
13160         SendBoard(&first, forwardMostMove);
13161     }
13162     if (appData.debugMode) {
13163 int i, j;
13164   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13165   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13166         fprintf(debugFP, "Load Position\n");
13167     }
13168
13169     if (positionNumber > 1) {
13170       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13171         DisplayTitle(line);
13172     } else {
13173         DisplayTitle(title);
13174     }
13175     gameMode = EditGame;
13176     ModeHighlight();
13177     ResetClocks();
13178     timeRemaining[0][1] = whiteTimeRemaining;
13179     timeRemaining[1][1] = blackTimeRemaining;
13180     DrawPosition(FALSE, boards[currentMove]);
13181
13182     return TRUE;
13183 }
13184
13185
13186 void
13187 CopyPlayerNameIntoFileName (char **dest, char *src)
13188 {
13189     while (*src != NULLCHAR && *src != ',') {
13190         if (*src == ' ') {
13191             *(*dest)++ = '_';
13192             src++;
13193         } else {
13194             *(*dest)++ = *src++;
13195         }
13196     }
13197 }
13198
13199 char *
13200 DefaultFileName (char *ext)
13201 {
13202     static char def[MSG_SIZ];
13203     char *p;
13204
13205     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13206         p = def;
13207         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13208         *p++ = '-';
13209         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13210         *p++ = '.';
13211         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13212     } else {
13213         def[0] = NULLCHAR;
13214     }
13215     return def;
13216 }
13217
13218 /* Save the current game to the given file */
13219 int
13220 SaveGameToFile (char *filename, int append)
13221 {
13222     FILE *f;
13223     char buf[MSG_SIZ];
13224     int result, i, t,tot=0;
13225
13226     if (strcmp(filename, "-") == 0) {
13227         return SaveGame(stdout, 0, NULL);
13228     } else {
13229         for(i=0; i<10; i++) { // upto 10 tries
13230              f = fopen(filename, append ? "a" : "w");
13231              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13232              if(f || errno != 13) break;
13233              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13234              tot += t;
13235         }
13236         if (f == NULL) {
13237             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13238             DisplayError(buf, errno);
13239             return FALSE;
13240         } else {
13241             safeStrCpy(buf, lastMsg, MSG_SIZ);
13242             DisplayMessage(_("Waiting for access to save file"), "");
13243             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13244             DisplayMessage(_("Saving game"), "");
13245             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13246             result = SaveGame(f, 0, NULL);
13247             DisplayMessage(buf, "");
13248             return result;
13249         }
13250     }
13251 }
13252
13253 char *
13254 SavePart (char *str)
13255 {
13256     static char buf[MSG_SIZ];
13257     char *p;
13258
13259     p = strchr(str, ' ');
13260     if (p == NULL) return str;
13261     strncpy(buf, str, p - str);
13262     buf[p - str] = NULLCHAR;
13263     return buf;
13264 }
13265
13266 #define PGN_MAX_LINE 75
13267
13268 #define PGN_SIDE_WHITE  0
13269 #define PGN_SIDE_BLACK  1
13270
13271 static int
13272 FindFirstMoveOutOfBook (int side)
13273 {
13274     int result = -1;
13275
13276     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13277         int index = backwardMostMove;
13278         int has_book_hit = 0;
13279
13280         if( (index % 2) != side ) {
13281             index++;
13282         }
13283
13284         while( index < forwardMostMove ) {
13285             /* Check to see if engine is in book */
13286             int depth = pvInfoList[index].depth;
13287             int score = pvInfoList[index].score;
13288             int in_book = 0;
13289
13290             if( depth <= 2 ) {
13291                 in_book = 1;
13292             }
13293             else if( score == 0 && depth == 63 ) {
13294                 in_book = 1; /* Zappa */
13295             }
13296             else if( score == 2 && depth == 99 ) {
13297                 in_book = 1; /* Abrok */
13298             }
13299
13300             has_book_hit += in_book;
13301
13302             if( ! in_book ) {
13303                 result = index;
13304
13305                 break;
13306             }
13307
13308             index += 2;
13309         }
13310     }
13311
13312     return result;
13313 }
13314
13315 void
13316 GetOutOfBookInfo (char * buf)
13317 {
13318     int oob[2];
13319     int i;
13320     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13321
13322     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13323     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13324
13325     *buf = '\0';
13326
13327     if( oob[0] >= 0 || oob[1] >= 0 ) {
13328         for( i=0; i<2; i++ ) {
13329             int idx = oob[i];
13330
13331             if( idx >= 0 ) {
13332                 if( i > 0 && oob[0] >= 0 ) {
13333                     strcat( buf, "   " );
13334                 }
13335
13336                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13337                 sprintf( buf+strlen(buf), "%s%.2f",
13338                     pvInfoList[idx].score >= 0 ? "+" : "",
13339                     pvInfoList[idx].score / 100.0 );
13340             }
13341         }
13342     }
13343 }
13344
13345 /* Save game in PGN style and close the file */
13346 int
13347 SaveGamePGN (FILE *f)
13348 {
13349     int i, offset, linelen, newblock;
13350 //    char *movetext;
13351     char numtext[32];
13352     int movelen, numlen, blank;
13353     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13354
13355     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13356
13357     PrintPGNTags(f, &gameInfo);
13358
13359     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13360
13361     if (backwardMostMove > 0 || startedFromSetupPosition) {
13362         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13363         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13364         fprintf(f, "\n{--------------\n");
13365         PrintPosition(f, backwardMostMove);
13366         fprintf(f, "--------------}\n");
13367         free(fen);
13368     }
13369     else {
13370         /* [AS] Out of book annotation */
13371         if( appData.saveOutOfBookInfo ) {
13372             char buf[64];
13373
13374             GetOutOfBookInfo( buf );
13375
13376             if( buf[0] != '\0' ) {
13377                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13378             }
13379         }
13380
13381         fprintf(f, "\n");
13382     }
13383
13384     i = backwardMostMove;
13385     linelen = 0;
13386     newblock = TRUE;
13387
13388     while (i < forwardMostMove) {
13389         /* Print comments preceding this move */
13390         if (commentList[i] != NULL) {
13391             if (linelen > 0) fprintf(f, "\n");
13392             fprintf(f, "%s", commentList[i]);
13393             linelen = 0;
13394             newblock = TRUE;
13395         }
13396
13397         /* Format move number */
13398         if ((i % 2) == 0)
13399           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13400         else
13401           if (newblock)
13402             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13403           else
13404             numtext[0] = NULLCHAR;
13405
13406         numlen = strlen(numtext);
13407         newblock = FALSE;
13408
13409         /* Print move number */
13410         blank = linelen > 0 && numlen > 0;
13411         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13412             fprintf(f, "\n");
13413             linelen = 0;
13414             blank = 0;
13415         }
13416         if (blank) {
13417             fprintf(f, " ");
13418             linelen++;
13419         }
13420         fprintf(f, "%s", numtext);
13421         linelen += numlen;
13422
13423         /* Get move */
13424         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13425         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13426
13427         /* Print move */
13428         blank = linelen > 0 && movelen > 0;
13429         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13430             fprintf(f, "\n");
13431             linelen = 0;
13432             blank = 0;
13433         }
13434         if (blank) {
13435             fprintf(f, " ");
13436             linelen++;
13437         }
13438         fprintf(f, "%s", move_buffer);
13439         linelen += movelen;
13440
13441         /* [AS] Add PV info if present */
13442         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13443             /* [HGM] add time */
13444             char buf[MSG_SIZ]; int seconds;
13445
13446             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13447
13448             if( seconds <= 0)
13449               buf[0] = 0;
13450             else
13451               if( seconds < 30 )
13452                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13453               else
13454                 {
13455                   seconds = (seconds + 4)/10; // round to full seconds
13456                   if( seconds < 60 )
13457                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13458                   else
13459                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13460                 }
13461
13462             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13463                       pvInfoList[i].score >= 0 ? "+" : "",
13464                       pvInfoList[i].score / 100.0,
13465                       pvInfoList[i].depth,
13466                       buf );
13467
13468             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13469
13470             /* Print score/depth */
13471             blank = linelen > 0 && movelen > 0;
13472             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13473                 fprintf(f, "\n");
13474                 linelen = 0;
13475                 blank = 0;
13476             }
13477             if (blank) {
13478                 fprintf(f, " ");
13479                 linelen++;
13480             }
13481             fprintf(f, "%s", move_buffer);
13482             linelen += movelen;
13483         }
13484
13485         i++;
13486     }
13487
13488     /* Start a new line */
13489     if (linelen > 0) fprintf(f, "\n");
13490
13491     /* Print comments after last move */
13492     if (commentList[i] != NULL) {
13493         fprintf(f, "%s\n", commentList[i]);
13494     }
13495
13496     /* Print result */
13497     if (gameInfo.resultDetails != NULL &&
13498         gameInfo.resultDetails[0] != NULLCHAR) {
13499         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13500         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13501            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13502             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13503         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13504     } else {
13505         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13506     }
13507
13508     fclose(f);
13509     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13510     return TRUE;
13511 }
13512
13513 /* Save game in old style and close the file */
13514 int
13515 SaveGameOldStyle (FILE *f)
13516 {
13517     int i, offset;
13518     time_t tm;
13519
13520     tm = time((time_t *) NULL);
13521
13522     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13523     PrintOpponents(f);
13524
13525     if (backwardMostMove > 0 || startedFromSetupPosition) {
13526         fprintf(f, "\n[--------------\n");
13527         PrintPosition(f, backwardMostMove);
13528         fprintf(f, "--------------]\n");
13529     } else {
13530         fprintf(f, "\n");
13531     }
13532
13533     i = backwardMostMove;
13534     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13535
13536     while (i < forwardMostMove) {
13537         if (commentList[i] != NULL) {
13538             fprintf(f, "[%s]\n", commentList[i]);
13539         }
13540
13541         if ((i % 2) == 1) {
13542             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13543             i++;
13544         } else {
13545             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13546             i++;
13547             if (commentList[i] != NULL) {
13548                 fprintf(f, "\n");
13549                 continue;
13550             }
13551             if (i >= forwardMostMove) {
13552                 fprintf(f, "\n");
13553                 break;
13554             }
13555             fprintf(f, "%s\n", parseList[i]);
13556             i++;
13557         }
13558     }
13559
13560     if (commentList[i] != NULL) {
13561         fprintf(f, "[%s]\n", commentList[i]);
13562     }
13563
13564     /* This isn't really the old style, but it's close enough */
13565     if (gameInfo.resultDetails != NULL &&
13566         gameInfo.resultDetails[0] != NULLCHAR) {
13567         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13568                 gameInfo.resultDetails);
13569     } else {
13570         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13571     }
13572
13573     fclose(f);
13574     return TRUE;
13575 }
13576
13577 /* Save the current game to open file f and close the file */
13578 int
13579 SaveGame (FILE *f, int dummy, char *dummy2)
13580 {
13581     if (gameMode == EditPosition) EditPositionDone(TRUE);
13582     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13583     if (appData.oldSaveStyle)
13584       return SaveGameOldStyle(f);
13585     else
13586       return SaveGamePGN(f);
13587 }
13588
13589 /* Save the current position to the given file */
13590 int
13591 SavePositionToFile (char *filename)
13592 {
13593     FILE *f;
13594     char buf[MSG_SIZ];
13595
13596     if (strcmp(filename, "-") == 0) {
13597         return SavePosition(stdout, 0, NULL);
13598     } else {
13599         f = fopen(filename, "a");
13600         if (f == NULL) {
13601             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13602             DisplayError(buf, errno);
13603             return FALSE;
13604         } else {
13605             safeStrCpy(buf, lastMsg, MSG_SIZ);
13606             DisplayMessage(_("Waiting for access to save file"), "");
13607             flock(fileno(f), LOCK_EX); // [HGM] lock
13608             DisplayMessage(_("Saving position"), "");
13609             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13610             SavePosition(f, 0, NULL);
13611             DisplayMessage(buf, "");
13612             return TRUE;
13613         }
13614     }
13615 }
13616
13617 /* Save the current position to the given open file and close the file */
13618 int
13619 SavePosition (FILE *f, int dummy, char *dummy2)
13620 {
13621     time_t tm;
13622     char *fen;
13623
13624     if (gameMode == EditPosition) EditPositionDone(TRUE);
13625     if (appData.oldSaveStyle) {
13626         tm = time((time_t *) NULL);
13627
13628         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13629         PrintOpponents(f);
13630         fprintf(f, "[--------------\n");
13631         PrintPosition(f, currentMove);
13632         fprintf(f, "--------------]\n");
13633     } else {
13634         fen = PositionToFEN(currentMove, NULL, 1);
13635         fprintf(f, "%s\n", fen);
13636         free(fen);
13637     }
13638     fclose(f);
13639     return TRUE;
13640 }
13641
13642 void
13643 ReloadCmailMsgEvent (int unregister)
13644 {
13645 #if !WIN32
13646     static char *inFilename = NULL;
13647     static char *outFilename;
13648     int i;
13649     struct stat inbuf, outbuf;
13650     int status;
13651
13652     /* Any registered moves are unregistered if unregister is set, */
13653     /* i.e. invoked by the signal handler */
13654     if (unregister) {
13655         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13656             cmailMoveRegistered[i] = FALSE;
13657             if (cmailCommentList[i] != NULL) {
13658                 free(cmailCommentList[i]);
13659                 cmailCommentList[i] = NULL;
13660             }
13661         }
13662         nCmailMovesRegistered = 0;
13663     }
13664
13665     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13666         cmailResult[i] = CMAIL_NOT_RESULT;
13667     }
13668     nCmailResults = 0;
13669
13670     if (inFilename == NULL) {
13671         /* Because the filenames are static they only get malloced once  */
13672         /* and they never get freed                                      */
13673         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13674         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13675
13676         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13677         sprintf(outFilename, "%s.out", appData.cmailGameName);
13678     }
13679
13680     status = stat(outFilename, &outbuf);
13681     if (status < 0) {
13682         cmailMailedMove = FALSE;
13683     } else {
13684         status = stat(inFilename, &inbuf);
13685         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13686     }
13687
13688     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13689        counts the games, notes how each one terminated, etc.
13690
13691        It would be nice to remove this kludge and instead gather all
13692        the information while building the game list.  (And to keep it
13693        in the game list nodes instead of having a bunch of fixed-size
13694        parallel arrays.)  Note this will require getting each game's
13695        termination from the PGN tags, as the game list builder does
13696        not process the game moves.  --mann
13697        */
13698     cmailMsgLoaded = TRUE;
13699     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13700
13701     /* Load first game in the file or popup game menu */
13702     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13703
13704 #endif /* !WIN32 */
13705     return;
13706 }
13707
13708 int
13709 RegisterMove ()
13710 {
13711     FILE *f;
13712     char string[MSG_SIZ];
13713
13714     if (   cmailMailedMove
13715         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13716         return TRUE;            /* Allow free viewing  */
13717     }
13718
13719     /* Unregister move to ensure that we don't leave RegisterMove        */
13720     /* with the move registered when the conditions for registering no   */
13721     /* longer hold                                                       */
13722     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13723         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13724         nCmailMovesRegistered --;
13725
13726         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13727           {
13728               free(cmailCommentList[lastLoadGameNumber - 1]);
13729               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13730           }
13731     }
13732
13733     if (cmailOldMove == -1) {
13734         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13735         return FALSE;
13736     }
13737
13738     if (currentMove > cmailOldMove + 1) {
13739         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13740         return FALSE;
13741     }
13742
13743     if (currentMove < cmailOldMove) {
13744         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13745         return FALSE;
13746     }
13747
13748     if (forwardMostMove > currentMove) {
13749         /* Silently truncate extra moves */
13750         TruncateGame();
13751     }
13752
13753     if (   (currentMove == cmailOldMove + 1)
13754         || (   (currentMove == cmailOldMove)
13755             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13756                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13757         if (gameInfo.result != GameUnfinished) {
13758             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13759         }
13760
13761         if (commentList[currentMove] != NULL) {
13762             cmailCommentList[lastLoadGameNumber - 1]
13763               = StrSave(commentList[currentMove]);
13764         }
13765         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13766
13767         if (appData.debugMode)
13768           fprintf(debugFP, "Saving %s for game %d\n",
13769                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13770
13771         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13772
13773         f = fopen(string, "w");
13774         if (appData.oldSaveStyle) {
13775             SaveGameOldStyle(f); /* also closes the file */
13776
13777             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13778             f = fopen(string, "w");
13779             SavePosition(f, 0, NULL); /* also closes the file */
13780         } else {
13781             fprintf(f, "{--------------\n");
13782             PrintPosition(f, currentMove);
13783             fprintf(f, "--------------}\n\n");
13784
13785             SaveGame(f, 0, NULL); /* also closes the file*/
13786         }
13787
13788         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13789         nCmailMovesRegistered ++;
13790     } else if (nCmailGames == 1) {
13791         DisplayError(_("You have not made a move yet"), 0);
13792         return FALSE;
13793     }
13794
13795     return TRUE;
13796 }
13797
13798 void
13799 MailMoveEvent ()
13800 {
13801 #if !WIN32
13802     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13803     FILE *commandOutput;
13804     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13805     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13806     int nBuffers;
13807     int i;
13808     int archived;
13809     char *arcDir;
13810
13811     if (! cmailMsgLoaded) {
13812         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13813         return;
13814     }
13815
13816     if (nCmailGames == nCmailResults) {
13817         DisplayError(_("No unfinished games"), 0);
13818         return;
13819     }
13820
13821 #if CMAIL_PROHIBIT_REMAIL
13822     if (cmailMailedMove) {
13823       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);
13824         DisplayError(msg, 0);
13825         return;
13826     }
13827 #endif
13828
13829     if (! (cmailMailedMove || RegisterMove())) return;
13830
13831     if (   cmailMailedMove
13832         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13833       snprintf(string, MSG_SIZ, partCommandString,
13834                appData.debugMode ? " -v" : "", appData.cmailGameName);
13835         commandOutput = popen(string, "r");
13836
13837         if (commandOutput == NULL) {
13838             DisplayError(_("Failed to invoke cmail"), 0);
13839         } else {
13840             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13841                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13842             }
13843             if (nBuffers > 1) {
13844                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13845                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13846                 nBytes = MSG_SIZ - 1;
13847             } else {
13848                 (void) memcpy(msg, buffer, nBytes);
13849             }
13850             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13851
13852             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13853                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13854
13855                 archived = TRUE;
13856                 for (i = 0; i < nCmailGames; i ++) {
13857                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13858                         archived = FALSE;
13859                     }
13860                 }
13861                 if (   archived
13862                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13863                         != NULL)) {
13864                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13865                            arcDir,
13866                            appData.cmailGameName,
13867                            gameInfo.date);
13868                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13869                     cmailMsgLoaded = FALSE;
13870                 }
13871             }
13872
13873             DisplayInformation(msg);
13874             pclose(commandOutput);
13875         }
13876     } else {
13877         if ((*cmailMsg) != '\0') {
13878             DisplayInformation(cmailMsg);
13879         }
13880     }
13881
13882     return;
13883 #endif /* !WIN32 */
13884 }
13885
13886 char *
13887 CmailMsg ()
13888 {
13889 #if WIN32
13890     return NULL;
13891 #else
13892     int  prependComma = 0;
13893     char number[5];
13894     char string[MSG_SIZ];       /* Space for game-list */
13895     int  i;
13896
13897     if (!cmailMsgLoaded) return "";
13898
13899     if (cmailMailedMove) {
13900       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13901     } else {
13902         /* Create a list of games left */
13903       snprintf(string, MSG_SIZ, "[");
13904         for (i = 0; i < nCmailGames; i ++) {
13905             if (! (   cmailMoveRegistered[i]
13906                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13907                 if (prependComma) {
13908                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13909                 } else {
13910                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13911                     prependComma = 1;
13912                 }
13913
13914                 strcat(string, number);
13915             }
13916         }
13917         strcat(string, "]");
13918
13919         if (nCmailMovesRegistered + nCmailResults == 0) {
13920             switch (nCmailGames) {
13921               case 1:
13922                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13923                 break;
13924
13925               case 2:
13926                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13927                 break;
13928
13929               default:
13930                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13931                          nCmailGames);
13932                 break;
13933             }
13934         } else {
13935             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13936               case 1:
13937                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13938                          string);
13939                 break;
13940
13941               case 0:
13942                 if (nCmailResults == nCmailGames) {
13943                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13944                 } else {
13945                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13946                 }
13947                 break;
13948
13949               default:
13950                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13951                          string);
13952             }
13953         }
13954     }
13955     return cmailMsg;
13956 #endif /* WIN32 */
13957 }
13958
13959 void
13960 ResetGameEvent ()
13961 {
13962     if (gameMode == Training)
13963       SetTrainingModeOff();
13964
13965     Reset(TRUE, TRUE);
13966     cmailMsgLoaded = FALSE;
13967     if (appData.icsActive) {
13968       SendToICS(ics_prefix);
13969       SendToICS("refresh\n");
13970     }
13971 }
13972
13973 void
13974 ExitEvent (int status)
13975 {
13976     exiting++;
13977     if (exiting > 2) {
13978       /* Give up on clean exit */
13979       exit(status);
13980     }
13981     if (exiting > 1) {
13982       /* Keep trying for clean exit */
13983       return;
13984     }
13985
13986     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
13987     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13988
13989     if (telnetISR != NULL) {
13990       RemoveInputSource(telnetISR);
13991     }
13992     if (icsPR != NoProc) {
13993       DestroyChildProcess(icsPR, TRUE);
13994     }
13995
13996     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13997     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13998
13999     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14000     /* make sure this other one finishes before killing it!                  */
14001     if(endingGame) { int count = 0;
14002         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14003         while(endingGame && count++ < 10) DoSleep(1);
14004         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14005     }
14006
14007     /* Kill off chess programs */
14008     if (first.pr != NoProc) {
14009         ExitAnalyzeMode();
14010
14011         DoSleep( appData.delayBeforeQuit );
14012         SendToProgram("quit\n", &first);
14013         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14014     }
14015     if (second.pr != NoProc) {
14016         DoSleep( appData.delayBeforeQuit );
14017         SendToProgram("quit\n", &second);
14018         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14019     }
14020     if (first.isr != NULL) {
14021         RemoveInputSource(first.isr);
14022     }
14023     if (second.isr != NULL) {
14024         RemoveInputSource(second.isr);
14025     }
14026
14027     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14028     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14029
14030     ShutDownFrontEnd();
14031     exit(status);
14032 }
14033
14034 void
14035 PauseEngine (ChessProgramState *cps)
14036 {
14037     SendToProgram("pause\n", cps);
14038     cps->pause = 2;
14039 }
14040
14041 void
14042 UnPauseEngine (ChessProgramState *cps)
14043 {
14044     SendToProgram("resume\n", cps);
14045     cps->pause = 1;
14046 }
14047
14048 void
14049 PauseEvent ()
14050 {
14051     if (appData.debugMode)
14052         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14053     if (pausing) {
14054         pausing = FALSE;
14055         ModeHighlight();
14056         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14057             StartClocks();
14058             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14059                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14060                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14061             }
14062             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14063             HandleMachineMove(stashedInputMove, stalledEngine);
14064             stalledEngine = NULL;
14065             return;
14066         }
14067         if (gameMode == MachinePlaysWhite ||
14068             gameMode == TwoMachinesPlay   ||
14069             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14070             if(first.pause)  UnPauseEngine(&first);
14071             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14072             if(second.pause) UnPauseEngine(&second);
14073             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14074             StartClocks();
14075         } else {
14076             DisplayBothClocks();
14077         }
14078         if (gameMode == PlayFromGameFile) {
14079             if (appData.timeDelay >= 0)
14080                 AutoPlayGameLoop();
14081         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14082             Reset(FALSE, TRUE);
14083             SendToICS(ics_prefix);
14084             SendToICS("refresh\n");
14085         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14086             ForwardInner(forwardMostMove);
14087         }
14088         pauseExamInvalid = FALSE;
14089     } else {
14090         switch (gameMode) {
14091           default:
14092             return;
14093           case IcsExamining:
14094             pauseExamForwardMostMove = forwardMostMove;
14095             pauseExamInvalid = FALSE;
14096             /* fall through */
14097           case IcsObserving:
14098           case IcsPlayingWhite:
14099           case IcsPlayingBlack:
14100             pausing = TRUE;
14101             ModeHighlight();
14102             return;
14103           case PlayFromGameFile:
14104             (void) StopLoadGameTimer();
14105             pausing = TRUE;
14106             ModeHighlight();
14107             break;
14108           case BeginningOfGame:
14109             if (appData.icsActive) return;
14110             /* else fall through */
14111           case MachinePlaysWhite:
14112           case MachinePlaysBlack:
14113           case TwoMachinesPlay:
14114             if (forwardMostMove == 0)
14115               return;           /* don't pause if no one has moved */
14116             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14117                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14118                 if(onMove->pause) {           // thinking engine can be paused
14119                     PauseEngine(onMove);      // do it
14120                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14121                         PauseEngine(onMove->other);
14122                     else
14123                         SendToProgram("easy\n", onMove->other);
14124                     StopClocks();
14125                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14126             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14127                 if(first.pause) {
14128                     PauseEngine(&first);
14129                     StopClocks();
14130                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14131             } else { // human on move, pause pondering by either method
14132                 if(first.pause)
14133                     PauseEngine(&first);
14134                 else if(appData.ponderNextMove)
14135                     SendToProgram("easy\n", &first);
14136                 StopClocks();
14137             }
14138             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14139           case AnalyzeMode:
14140             pausing = TRUE;
14141             ModeHighlight();
14142             break;
14143         }
14144     }
14145 }
14146
14147 void
14148 EditCommentEvent ()
14149 {
14150     char title[MSG_SIZ];
14151
14152     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14153       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14154     } else {
14155       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14156                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14157                parseList[currentMove - 1]);
14158     }
14159
14160     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14161 }
14162
14163
14164 void
14165 EditTagsEvent ()
14166 {
14167     char *tags = PGNTags(&gameInfo);
14168     bookUp = FALSE;
14169     EditTagsPopUp(tags, NULL);
14170     free(tags);
14171 }
14172
14173 void
14174 ToggleSecond ()
14175 {
14176   if(second.analyzing) {
14177     SendToProgram("exit\n", &second);
14178     second.analyzing = FALSE;
14179   } else {
14180     if (second.pr == NoProc) StartChessProgram(&second);
14181     InitChessProgram(&second, FALSE);
14182     FeedMovesToProgram(&second, currentMove);
14183
14184     SendToProgram("analyze\n", &second);
14185     second.analyzing = TRUE;
14186   }
14187 }
14188
14189 /* Toggle ShowThinking */
14190 void
14191 ToggleShowThinking()
14192 {
14193   appData.showThinking = !appData.showThinking;
14194   ShowThinkingEvent();
14195 }
14196
14197 int
14198 AnalyzeModeEvent ()
14199 {
14200     char buf[MSG_SIZ];
14201
14202     if (!first.analysisSupport) {
14203       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14204       DisplayError(buf, 0);
14205       return 0;
14206     }
14207     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14208     if (appData.icsActive) {
14209         if (gameMode != IcsObserving) {
14210           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14211             DisplayError(buf, 0);
14212             /* secure check */
14213             if (appData.icsEngineAnalyze) {
14214                 if (appData.debugMode)
14215                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14216                 ExitAnalyzeMode();
14217                 ModeHighlight();
14218             }
14219             return 0;
14220         }
14221         /* if enable, user wants to disable icsEngineAnalyze */
14222         if (appData.icsEngineAnalyze) {
14223                 ExitAnalyzeMode();
14224                 ModeHighlight();
14225                 return 0;
14226         }
14227         appData.icsEngineAnalyze = TRUE;
14228         if (appData.debugMode)
14229             fprintf(debugFP, "ICS engine analyze starting... \n");
14230     }
14231
14232     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14233     if (appData.noChessProgram || gameMode == AnalyzeMode)
14234       return 0;
14235
14236     if (gameMode != AnalyzeFile) {
14237         if (!appData.icsEngineAnalyze) {
14238                EditGameEvent();
14239                if (gameMode != EditGame) return 0;
14240         }
14241         if (!appData.showThinking) ToggleShowThinking();
14242         ResurrectChessProgram();
14243         SendToProgram("analyze\n", &first);
14244         first.analyzing = TRUE;
14245         /*first.maybeThinking = TRUE;*/
14246         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14247         EngineOutputPopUp();
14248     }
14249     if (!appData.icsEngineAnalyze) {
14250         gameMode = AnalyzeMode;
14251         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14252     }
14253     pausing = FALSE;
14254     ModeHighlight();
14255     SetGameInfo();
14256
14257     StartAnalysisClock();
14258     GetTimeMark(&lastNodeCountTime);
14259     lastNodeCount = 0;
14260     return 1;
14261 }
14262
14263 void
14264 AnalyzeFileEvent ()
14265 {
14266     if (appData.noChessProgram || gameMode == AnalyzeFile)
14267       return;
14268
14269     if (!first.analysisSupport) {
14270       char buf[MSG_SIZ];
14271       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14272       DisplayError(buf, 0);
14273       return;
14274     }
14275
14276     if (gameMode != AnalyzeMode) {
14277         keepInfo = 1; // mere annotating should not alter PGN tags
14278         EditGameEvent();
14279         keepInfo = 0;
14280         if (gameMode != EditGame) return;
14281         if (!appData.showThinking) ToggleShowThinking();
14282         ResurrectChessProgram();
14283         SendToProgram("analyze\n", &first);
14284         first.analyzing = TRUE;
14285         /*first.maybeThinking = TRUE;*/
14286         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14287         EngineOutputPopUp();
14288     }
14289     gameMode = AnalyzeFile;
14290     pausing = FALSE;
14291     ModeHighlight();
14292
14293     StartAnalysisClock();
14294     GetTimeMark(&lastNodeCountTime);
14295     lastNodeCount = 0;
14296     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14297     AnalysisPeriodicEvent(1);
14298 }
14299
14300 void
14301 MachineWhiteEvent ()
14302 {
14303     char buf[MSG_SIZ];
14304     char *bookHit = NULL;
14305
14306     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14307       return;
14308
14309
14310     if (gameMode == PlayFromGameFile ||
14311         gameMode == TwoMachinesPlay  ||
14312         gameMode == Training         ||
14313         gameMode == AnalyzeMode      ||
14314         gameMode == EndOfGame)
14315         EditGameEvent();
14316
14317     if (gameMode == EditPosition)
14318         EditPositionDone(TRUE);
14319
14320     if (!WhiteOnMove(currentMove)) {
14321         DisplayError(_("It is not White's turn"), 0);
14322         return;
14323     }
14324
14325     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14326       ExitAnalyzeMode();
14327
14328     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14329         gameMode == AnalyzeFile)
14330         TruncateGame();
14331
14332     ResurrectChessProgram();    /* in case it isn't running */
14333     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14334         gameMode = MachinePlaysWhite;
14335         ResetClocks();
14336     } else
14337     gameMode = MachinePlaysWhite;
14338     pausing = FALSE;
14339     ModeHighlight();
14340     SetGameInfo();
14341     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14342     DisplayTitle(buf);
14343     if (first.sendName) {
14344       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14345       SendToProgram(buf, &first);
14346     }
14347     if (first.sendTime) {
14348       if (first.useColors) {
14349         SendToProgram("black\n", &first); /*gnu kludge*/
14350       }
14351       SendTimeRemaining(&first, TRUE);
14352     }
14353     if (first.useColors) {
14354       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14355     }
14356     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14357     SetMachineThinkingEnables();
14358     first.maybeThinking = TRUE;
14359     StartClocks();
14360     firstMove = FALSE;
14361
14362     if (appData.autoFlipView && !flipView) {
14363       flipView = !flipView;
14364       DrawPosition(FALSE, NULL);
14365       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14366     }
14367
14368     if(bookHit) { // [HGM] book: simulate book reply
14369         static char bookMove[MSG_SIZ]; // a bit generous?
14370
14371         programStats.nodes = programStats.depth = programStats.time =
14372         programStats.score = programStats.got_only_move = 0;
14373         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14374
14375         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14376         strcat(bookMove, bookHit);
14377         HandleMachineMove(bookMove, &first);
14378     }
14379 }
14380
14381 void
14382 MachineBlackEvent ()
14383 {
14384   char buf[MSG_SIZ];
14385   char *bookHit = NULL;
14386
14387     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14388         return;
14389
14390
14391     if (gameMode == PlayFromGameFile ||
14392         gameMode == TwoMachinesPlay  ||
14393         gameMode == Training         ||
14394         gameMode == AnalyzeMode      ||
14395         gameMode == EndOfGame)
14396         EditGameEvent();
14397
14398     if (gameMode == EditPosition)
14399         EditPositionDone(TRUE);
14400
14401     if (WhiteOnMove(currentMove)) {
14402         DisplayError(_("It is not Black's turn"), 0);
14403         return;
14404     }
14405
14406     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14407       ExitAnalyzeMode();
14408
14409     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14410         gameMode == AnalyzeFile)
14411         TruncateGame();
14412
14413     ResurrectChessProgram();    /* in case it isn't running */
14414     gameMode = MachinePlaysBlack;
14415     pausing = FALSE;
14416     ModeHighlight();
14417     SetGameInfo();
14418     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14419     DisplayTitle(buf);
14420     if (first.sendName) {
14421       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14422       SendToProgram(buf, &first);
14423     }
14424     if (first.sendTime) {
14425       if (first.useColors) {
14426         SendToProgram("white\n", &first); /*gnu kludge*/
14427       }
14428       SendTimeRemaining(&first, FALSE);
14429     }
14430     if (first.useColors) {
14431       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14432     }
14433     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14434     SetMachineThinkingEnables();
14435     first.maybeThinking = TRUE;
14436     StartClocks();
14437
14438     if (appData.autoFlipView && flipView) {
14439       flipView = !flipView;
14440       DrawPosition(FALSE, NULL);
14441       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14442     }
14443     if(bookHit) { // [HGM] book: simulate book reply
14444         static char bookMove[MSG_SIZ]; // a bit generous?
14445
14446         programStats.nodes = programStats.depth = programStats.time =
14447         programStats.score = programStats.got_only_move = 0;
14448         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14449
14450         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14451         strcat(bookMove, bookHit);
14452         HandleMachineMove(bookMove, &first);
14453     }
14454 }
14455
14456
14457 void
14458 DisplayTwoMachinesTitle ()
14459 {
14460     char buf[MSG_SIZ];
14461     if (appData.matchGames > 0) {
14462         if(appData.tourneyFile[0]) {
14463           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14464                    gameInfo.white, _("vs."), gameInfo.black,
14465                    nextGame+1, appData.matchGames+1,
14466                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14467         } else
14468         if (first.twoMachinesColor[0] == 'w') {
14469           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14470                    gameInfo.white, _("vs."),  gameInfo.black,
14471                    first.matchWins, second.matchWins,
14472                    matchGame - 1 - (first.matchWins + second.matchWins));
14473         } else {
14474           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14475                    gameInfo.white, _("vs."), gameInfo.black,
14476                    second.matchWins, first.matchWins,
14477                    matchGame - 1 - (first.matchWins + second.matchWins));
14478         }
14479     } else {
14480       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14481     }
14482     DisplayTitle(buf);
14483 }
14484
14485 void
14486 SettingsMenuIfReady ()
14487 {
14488   if (second.lastPing != second.lastPong) {
14489     DisplayMessage("", _("Waiting for second chess program"));
14490     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14491     return;
14492   }
14493   ThawUI();
14494   DisplayMessage("", "");
14495   SettingsPopUp(&second);
14496 }
14497
14498 int
14499 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14500 {
14501     char buf[MSG_SIZ];
14502     if (cps->pr == NoProc) {
14503         StartChessProgram(cps);
14504         if (cps->protocolVersion == 1) {
14505           retry();
14506           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14507         } else {
14508           /* kludge: allow timeout for initial "feature" command */
14509           if(retry != TwoMachinesEventIfReady) FreezeUI();
14510           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14511           DisplayMessage("", buf);
14512           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14513         }
14514         return 1;
14515     }
14516     return 0;
14517 }
14518
14519 void
14520 TwoMachinesEvent P((void))
14521 {
14522     int i;
14523     char buf[MSG_SIZ];
14524     ChessProgramState *onmove;
14525     char *bookHit = NULL;
14526     static int stalling = 0;
14527     TimeMark now;
14528     long wait;
14529
14530     if (appData.noChessProgram) return;
14531
14532     switch (gameMode) {
14533       case TwoMachinesPlay:
14534         return;
14535       case MachinePlaysWhite:
14536       case MachinePlaysBlack:
14537         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14538             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14539             return;
14540         }
14541         /* fall through */
14542       case BeginningOfGame:
14543       case PlayFromGameFile:
14544       case EndOfGame:
14545         EditGameEvent();
14546         if (gameMode != EditGame) return;
14547         break;
14548       case EditPosition:
14549         EditPositionDone(TRUE);
14550         break;
14551       case AnalyzeMode:
14552       case AnalyzeFile:
14553         ExitAnalyzeMode();
14554         break;
14555       case EditGame:
14556       default:
14557         break;
14558     }
14559
14560 //    forwardMostMove = currentMove;
14561     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14562     startingEngine = TRUE;
14563
14564     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14565
14566     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14567     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14568       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14569       return;
14570     }
14571     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14572
14573     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14574                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14575         startingEngine = FALSE;
14576         DisplayError("second engine does not play this", 0);
14577         return;
14578     }
14579
14580     if(!stalling) {
14581       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14582       SendToProgram("force\n", &second);
14583       stalling = 1;
14584       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14585       return;
14586     }
14587     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14588     if(appData.matchPause>10000 || appData.matchPause<10)
14589                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14590     wait = SubtractTimeMarks(&now, &pauseStart);
14591     if(wait < appData.matchPause) {
14592         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14593         return;
14594     }
14595     // we are now committed to starting the game
14596     stalling = 0;
14597     DisplayMessage("", "");
14598     if (startedFromSetupPosition) {
14599         SendBoard(&second, backwardMostMove);
14600     if (appData.debugMode) {
14601         fprintf(debugFP, "Two Machines\n");
14602     }
14603     }
14604     for (i = backwardMostMove; i < forwardMostMove; i++) {
14605         SendMoveToProgram(i, &second);
14606     }
14607
14608     gameMode = TwoMachinesPlay;
14609     pausing = startingEngine = FALSE;
14610     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14611     SetGameInfo();
14612     DisplayTwoMachinesTitle();
14613     firstMove = TRUE;
14614     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14615         onmove = &first;
14616     } else {
14617         onmove = &second;
14618     }
14619     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14620     SendToProgram(first.computerString, &first);
14621     if (first.sendName) {
14622       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14623       SendToProgram(buf, &first);
14624     }
14625     SendToProgram(second.computerString, &second);
14626     if (second.sendName) {
14627       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14628       SendToProgram(buf, &second);
14629     }
14630
14631     ResetClocks();
14632     if (!first.sendTime || !second.sendTime) {
14633         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14634         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14635     }
14636     if (onmove->sendTime) {
14637       if (onmove->useColors) {
14638         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14639       }
14640       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14641     }
14642     if (onmove->useColors) {
14643       SendToProgram(onmove->twoMachinesColor, onmove);
14644     }
14645     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14646 //    SendToProgram("go\n", onmove);
14647     onmove->maybeThinking = TRUE;
14648     SetMachineThinkingEnables();
14649
14650     StartClocks();
14651
14652     if(bookHit) { // [HGM] book: simulate book reply
14653         static char bookMove[MSG_SIZ]; // a bit generous?
14654
14655         programStats.nodes = programStats.depth = programStats.time =
14656         programStats.score = programStats.got_only_move = 0;
14657         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14658
14659         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14660         strcat(bookMove, bookHit);
14661         savedMessage = bookMove; // args for deferred call
14662         savedState = onmove;
14663         ScheduleDelayedEvent(DeferredBookMove, 1);
14664     }
14665 }
14666
14667 void
14668 TrainingEvent ()
14669 {
14670     if (gameMode == Training) {
14671       SetTrainingModeOff();
14672       gameMode = PlayFromGameFile;
14673       DisplayMessage("", _("Training mode off"));
14674     } else {
14675       gameMode = Training;
14676       animateTraining = appData.animate;
14677
14678       /* make sure we are not already at the end of the game */
14679       if (currentMove < forwardMostMove) {
14680         SetTrainingModeOn();
14681         DisplayMessage("", _("Training mode on"));
14682       } else {
14683         gameMode = PlayFromGameFile;
14684         DisplayError(_("Already at end of game"), 0);
14685       }
14686     }
14687     ModeHighlight();
14688 }
14689
14690 void
14691 IcsClientEvent ()
14692 {
14693     if (!appData.icsActive) return;
14694     switch (gameMode) {
14695       case IcsPlayingWhite:
14696       case IcsPlayingBlack:
14697       case IcsObserving:
14698       case IcsIdle:
14699       case BeginningOfGame:
14700       case IcsExamining:
14701         return;
14702
14703       case EditGame:
14704         break;
14705
14706       case EditPosition:
14707         EditPositionDone(TRUE);
14708         break;
14709
14710       case AnalyzeMode:
14711       case AnalyzeFile:
14712         ExitAnalyzeMode();
14713         break;
14714
14715       default:
14716         EditGameEvent();
14717         break;
14718     }
14719
14720     gameMode = IcsIdle;
14721     ModeHighlight();
14722     return;
14723 }
14724
14725 void
14726 EditGameEvent ()
14727 {
14728     int i;
14729
14730     switch (gameMode) {
14731       case Training:
14732         SetTrainingModeOff();
14733         break;
14734       case MachinePlaysWhite:
14735       case MachinePlaysBlack:
14736       case BeginningOfGame:
14737         SendToProgram("force\n", &first);
14738         SetUserThinkingEnables();
14739         break;
14740       case PlayFromGameFile:
14741         (void) StopLoadGameTimer();
14742         if (gameFileFP != NULL) {
14743             gameFileFP = NULL;
14744         }
14745         break;
14746       case EditPosition:
14747         EditPositionDone(TRUE);
14748         break;
14749       case AnalyzeMode:
14750       case AnalyzeFile:
14751         ExitAnalyzeMode();
14752         SendToProgram("force\n", &first);
14753         break;
14754       case TwoMachinesPlay:
14755         GameEnds(EndOfFile, NULL, GE_PLAYER);
14756         ResurrectChessProgram();
14757         SetUserThinkingEnables();
14758         break;
14759       case EndOfGame:
14760         ResurrectChessProgram();
14761         break;
14762       case IcsPlayingBlack:
14763       case IcsPlayingWhite:
14764         DisplayError(_("Warning: You are still playing a game"), 0);
14765         break;
14766       case IcsObserving:
14767         DisplayError(_("Warning: You are still observing a game"), 0);
14768         break;
14769       case IcsExamining:
14770         DisplayError(_("Warning: You are still examining a game"), 0);
14771         break;
14772       case IcsIdle:
14773         break;
14774       case EditGame:
14775       default:
14776         return;
14777     }
14778
14779     pausing = FALSE;
14780     StopClocks();
14781     first.offeredDraw = second.offeredDraw = 0;
14782
14783     if (gameMode == PlayFromGameFile) {
14784         whiteTimeRemaining = timeRemaining[0][currentMove];
14785         blackTimeRemaining = timeRemaining[1][currentMove];
14786         DisplayTitle("");
14787     }
14788
14789     if (gameMode == MachinePlaysWhite ||
14790         gameMode == MachinePlaysBlack ||
14791         gameMode == TwoMachinesPlay ||
14792         gameMode == EndOfGame) {
14793         i = forwardMostMove;
14794         while (i > currentMove) {
14795             SendToProgram("undo\n", &first);
14796             i--;
14797         }
14798         if(!adjustedClock) {
14799         whiteTimeRemaining = timeRemaining[0][currentMove];
14800         blackTimeRemaining = timeRemaining[1][currentMove];
14801         DisplayBothClocks();
14802         }
14803         if (whiteFlag || blackFlag) {
14804             whiteFlag = blackFlag = 0;
14805         }
14806         DisplayTitle("");
14807     }
14808
14809     gameMode = EditGame;
14810     ModeHighlight();
14811     SetGameInfo();
14812 }
14813
14814
14815 void
14816 EditPositionEvent ()
14817 {
14818     if (gameMode == EditPosition) {
14819         EditGameEvent();
14820         return;
14821     }
14822
14823     EditGameEvent();
14824     if (gameMode != EditGame) return;
14825
14826     gameMode = EditPosition;
14827     ModeHighlight();
14828     SetGameInfo();
14829     if (currentMove > 0)
14830       CopyBoard(boards[0], boards[currentMove]);
14831
14832     blackPlaysFirst = !WhiteOnMove(currentMove);
14833     ResetClocks();
14834     currentMove = forwardMostMove = backwardMostMove = 0;
14835     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14836     DisplayMove(-1);
14837     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14838 }
14839
14840 void
14841 ExitAnalyzeMode ()
14842 {
14843     /* [DM] icsEngineAnalyze - possible call from other functions */
14844     if (appData.icsEngineAnalyze) {
14845         appData.icsEngineAnalyze = FALSE;
14846
14847         DisplayMessage("",_("Close ICS engine analyze..."));
14848     }
14849     if (first.analysisSupport && first.analyzing) {
14850       SendToBoth("exit\n");
14851       first.analyzing = second.analyzing = FALSE;
14852     }
14853     thinkOutput[0] = NULLCHAR;
14854 }
14855
14856 void
14857 EditPositionDone (Boolean fakeRights)
14858 {
14859     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14860
14861     startedFromSetupPosition = TRUE;
14862     InitChessProgram(&first, FALSE);
14863     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14864       boards[0][EP_STATUS] = EP_NONE;
14865       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14866       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14867         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14868         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14869       } else boards[0][CASTLING][2] = NoRights;
14870       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14871         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14872         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14873       } else boards[0][CASTLING][5] = NoRights;
14874       if(gameInfo.variant == VariantSChess) {
14875         int i;
14876         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14877           boards[0][VIRGIN][i] = 0;
14878           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14879           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14880         }
14881       }
14882     }
14883     SendToProgram("force\n", &first);
14884     if (blackPlaysFirst) {
14885         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14886         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14887         currentMove = forwardMostMove = backwardMostMove = 1;
14888         CopyBoard(boards[1], boards[0]);
14889     } else {
14890         currentMove = forwardMostMove = backwardMostMove = 0;
14891     }
14892     SendBoard(&first, forwardMostMove);
14893     if (appData.debugMode) {
14894         fprintf(debugFP, "EditPosDone\n");
14895     }
14896     DisplayTitle("");
14897     DisplayMessage("", "");
14898     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14899     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14900     gameMode = EditGame;
14901     ModeHighlight();
14902     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14903     ClearHighlights(); /* [AS] */
14904 }
14905
14906 /* Pause for `ms' milliseconds */
14907 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14908 void
14909 TimeDelay (long ms)
14910 {
14911     TimeMark m1, m2;
14912
14913     GetTimeMark(&m1);
14914     do {
14915         GetTimeMark(&m2);
14916     } while (SubtractTimeMarks(&m2, &m1) < ms);
14917 }
14918
14919 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14920 void
14921 SendMultiLineToICS (char *buf)
14922 {
14923     char temp[MSG_SIZ+1], *p;
14924     int len;
14925
14926     len = strlen(buf);
14927     if (len > MSG_SIZ)
14928       len = MSG_SIZ;
14929
14930     strncpy(temp, buf, len);
14931     temp[len] = 0;
14932
14933     p = temp;
14934     while (*p) {
14935         if (*p == '\n' || *p == '\r')
14936           *p = ' ';
14937         ++p;
14938     }
14939
14940     strcat(temp, "\n");
14941     SendToICS(temp);
14942     SendToPlayer(temp, strlen(temp));
14943 }
14944
14945 void
14946 SetWhiteToPlayEvent ()
14947 {
14948     if (gameMode == EditPosition) {
14949         blackPlaysFirst = FALSE;
14950         DisplayBothClocks();    /* works because currentMove is 0 */
14951     } else if (gameMode == IcsExamining) {
14952         SendToICS(ics_prefix);
14953         SendToICS("tomove white\n");
14954     }
14955 }
14956
14957 void
14958 SetBlackToPlayEvent ()
14959 {
14960     if (gameMode == EditPosition) {
14961         blackPlaysFirst = TRUE;
14962         currentMove = 1;        /* kludge */
14963         DisplayBothClocks();
14964         currentMove = 0;
14965     } else if (gameMode == IcsExamining) {
14966         SendToICS(ics_prefix);
14967         SendToICS("tomove black\n");
14968     }
14969 }
14970
14971 void
14972 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14973 {
14974     char buf[MSG_SIZ];
14975     ChessSquare piece = boards[0][y][x];
14976     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14977     static int lastVariant;
14978
14979     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14980
14981     switch (selection) {
14982       case ClearBoard:
14983         CopyBoard(currentBoard, boards[0]);
14984         CopyBoard(menuBoard, initialPosition);
14985         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14986             SendToICS(ics_prefix);
14987             SendToICS("bsetup clear\n");
14988         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14989             SendToICS(ics_prefix);
14990             SendToICS("clearboard\n");
14991         } else {
14992             int nonEmpty = 0;
14993             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14994                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14995                 for (y = 0; y < BOARD_HEIGHT; y++) {
14996                     if (gameMode == IcsExamining) {
14997                         if (boards[currentMove][y][x] != EmptySquare) {
14998                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14999                                     AAA + x, ONE + y);
15000                             SendToICS(buf);
15001                         }
15002                     } else {
15003                         if(boards[0][y][x] != p) nonEmpty++;
15004                         boards[0][y][x] = p;
15005                     }
15006                 }
15007                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
15008             }
15009             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15010                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15011                     ChessSquare p = menuBoard[0][x];
15012                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
15013                     p = menuBoard[BOARD_HEIGHT-1][x];
15014                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
15015                 }
15016                 DisplayMessage("Clicking clock again restores position", "");
15017                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15018                 if(!nonEmpty) { // asked to clear an empty board
15019                     CopyBoard(boards[0], menuBoard);
15020                 } else
15021                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15022                     CopyBoard(boards[0], initialPosition);
15023                 } else
15024                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15025                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15026                     CopyBoard(boards[0], erasedBoard);
15027                 } else
15028                     CopyBoard(erasedBoard, currentBoard);
15029
15030             }
15031         }
15032         if (gameMode == EditPosition) {
15033             DrawPosition(FALSE, boards[0]);
15034         }
15035         break;
15036
15037       case WhitePlay:
15038         SetWhiteToPlayEvent();
15039         break;
15040
15041       case BlackPlay:
15042         SetBlackToPlayEvent();
15043         break;
15044
15045       case EmptySquare:
15046         if (gameMode == IcsExamining) {
15047             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15048             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15049             SendToICS(buf);
15050         } else {
15051             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15052                 if(x == BOARD_LEFT-2) {
15053                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15054                     boards[0][y][1] = 0;
15055                 } else
15056                 if(x == BOARD_RGHT+1) {
15057                     if(y >= gameInfo.holdingsSize) break;
15058                     boards[0][y][BOARD_WIDTH-2] = 0;
15059                 } else break;
15060             }
15061             boards[0][y][x] = EmptySquare;
15062             DrawPosition(FALSE, boards[0]);
15063         }
15064         break;
15065
15066       case PromotePiece:
15067         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15068            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15069             selection = (ChessSquare) (PROMOTED piece);
15070         } else if(piece == EmptySquare) selection = WhiteSilver;
15071         else selection = (ChessSquare)((int)piece - 1);
15072         goto defaultlabel;
15073
15074       case DemotePiece:
15075         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15076            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15077             selection = (ChessSquare) (DEMOTED piece);
15078         } else if(piece == EmptySquare) selection = BlackSilver;
15079         else selection = (ChessSquare)((int)piece + 1);
15080         goto defaultlabel;
15081
15082       case WhiteQueen:
15083       case BlackQueen:
15084         if(gameInfo.variant == VariantShatranj ||
15085            gameInfo.variant == VariantXiangqi  ||
15086            gameInfo.variant == VariantCourier  ||
15087            gameInfo.variant == VariantASEAN    ||
15088            gameInfo.variant == VariantMakruk     )
15089             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15090         goto defaultlabel;
15091
15092       case WhiteKing:
15093       case BlackKing:
15094         if(gameInfo.variant == VariantXiangqi)
15095             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15096         if(gameInfo.variant == VariantKnightmate)
15097             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15098       default:
15099         defaultlabel:
15100         if (gameMode == IcsExamining) {
15101             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15102             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15103                      PieceToChar(selection), AAA + x, ONE + y);
15104             SendToICS(buf);
15105         } else {
15106             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15107                 int n;
15108                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15109                     n = PieceToNumber(selection - BlackPawn);
15110                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15111                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15112                     boards[0][BOARD_HEIGHT-1-n][1]++;
15113                 } else
15114                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15115                     n = PieceToNumber(selection);
15116                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15117                     boards[0][n][BOARD_WIDTH-1] = selection;
15118                     boards[0][n][BOARD_WIDTH-2]++;
15119                 }
15120             } else
15121             boards[0][y][x] = selection;
15122             DrawPosition(TRUE, boards[0]);
15123             ClearHighlights();
15124             fromX = fromY = -1;
15125         }
15126         break;
15127     }
15128 }
15129
15130
15131 void
15132 DropMenuEvent (ChessSquare selection, int x, int y)
15133 {
15134     ChessMove moveType;
15135
15136     switch (gameMode) {
15137       case IcsPlayingWhite:
15138       case MachinePlaysBlack:
15139         if (!WhiteOnMove(currentMove)) {
15140             DisplayMoveError(_("It is Black's turn"));
15141             return;
15142         }
15143         moveType = WhiteDrop;
15144         break;
15145       case IcsPlayingBlack:
15146       case MachinePlaysWhite:
15147         if (WhiteOnMove(currentMove)) {
15148             DisplayMoveError(_("It is White's turn"));
15149             return;
15150         }
15151         moveType = BlackDrop;
15152         break;
15153       case EditGame:
15154         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15155         break;
15156       default:
15157         return;
15158     }
15159
15160     if (moveType == BlackDrop && selection < BlackPawn) {
15161       selection = (ChessSquare) ((int) selection
15162                                  + (int) BlackPawn - (int) WhitePawn);
15163     }
15164     if (boards[currentMove][y][x] != EmptySquare) {
15165         DisplayMoveError(_("That square is occupied"));
15166         return;
15167     }
15168
15169     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15170 }
15171
15172 void
15173 AcceptEvent ()
15174 {
15175     /* Accept a pending offer of any kind from opponent */
15176
15177     if (appData.icsActive) {
15178         SendToICS(ics_prefix);
15179         SendToICS("accept\n");
15180     } else if (cmailMsgLoaded) {
15181         if (currentMove == cmailOldMove &&
15182             commentList[cmailOldMove] != NULL &&
15183             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15184                    "Black offers a draw" : "White offers a draw")) {
15185             TruncateGame();
15186             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15187             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15188         } else {
15189             DisplayError(_("There is no pending offer on this move"), 0);
15190             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15191         }
15192     } else {
15193         /* Not used for offers from chess program */
15194     }
15195 }
15196
15197 void
15198 DeclineEvent ()
15199 {
15200     /* Decline a pending offer of any kind from opponent */
15201
15202     if (appData.icsActive) {
15203         SendToICS(ics_prefix);
15204         SendToICS("decline\n");
15205     } else if (cmailMsgLoaded) {
15206         if (currentMove == cmailOldMove &&
15207             commentList[cmailOldMove] != NULL &&
15208             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15209                    "Black offers a draw" : "White offers a draw")) {
15210 #ifdef NOTDEF
15211             AppendComment(cmailOldMove, "Draw declined", TRUE);
15212             DisplayComment(cmailOldMove - 1, "Draw declined");
15213 #endif /*NOTDEF*/
15214         } else {
15215             DisplayError(_("There is no pending offer on this move"), 0);
15216         }
15217     } else {
15218         /* Not used for offers from chess program */
15219     }
15220 }
15221
15222 void
15223 RematchEvent ()
15224 {
15225     /* Issue ICS rematch command */
15226     if (appData.icsActive) {
15227         SendToICS(ics_prefix);
15228         SendToICS("rematch\n");
15229     }
15230 }
15231
15232 void
15233 CallFlagEvent ()
15234 {
15235     /* Call your opponent's flag (claim a win on time) */
15236     if (appData.icsActive) {
15237         SendToICS(ics_prefix);
15238         SendToICS("flag\n");
15239     } else {
15240         switch (gameMode) {
15241           default:
15242             return;
15243           case MachinePlaysWhite:
15244             if (whiteFlag) {
15245                 if (blackFlag)
15246                   GameEnds(GameIsDrawn, "Both players ran out of time",
15247                            GE_PLAYER);
15248                 else
15249                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15250             } else {
15251                 DisplayError(_("Your opponent is not out of time"), 0);
15252             }
15253             break;
15254           case MachinePlaysBlack:
15255             if (blackFlag) {
15256                 if (whiteFlag)
15257                   GameEnds(GameIsDrawn, "Both players ran out of time",
15258                            GE_PLAYER);
15259                 else
15260                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15261             } else {
15262                 DisplayError(_("Your opponent is not out of time"), 0);
15263             }
15264             break;
15265         }
15266     }
15267 }
15268
15269 void
15270 ClockClick (int which)
15271 {       // [HGM] code moved to back-end from winboard.c
15272         if(which) { // black clock
15273           if (gameMode == EditPosition || gameMode == IcsExamining) {
15274             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15275             SetBlackToPlayEvent();
15276           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15277           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15278           } else if (shiftKey) {
15279             AdjustClock(which, -1);
15280           } else if (gameMode == IcsPlayingWhite ||
15281                      gameMode == MachinePlaysBlack) {
15282             CallFlagEvent();
15283           }
15284         } else { // white clock
15285           if (gameMode == EditPosition || gameMode == IcsExamining) {
15286             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15287             SetWhiteToPlayEvent();
15288           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15289           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15290           } else if (shiftKey) {
15291             AdjustClock(which, -1);
15292           } else if (gameMode == IcsPlayingBlack ||
15293                    gameMode == MachinePlaysWhite) {
15294             CallFlagEvent();
15295           }
15296         }
15297 }
15298
15299 void
15300 DrawEvent ()
15301 {
15302     /* Offer draw or accept pending draw offer from opponent */
15303
15304     if (appData.icsActive) {
15305         /* Note: tournament rules require draw offers to be
15306            made after you make your move but before you punch
15307            your clock.  Currently ICS doesn't let you do that;
15308            instead, you immediately punch your clock after making
15309            a move, but you can offer a draw at any time. */
15310
15311         SendToICS(ics_prefix);
15312         SendToICS("draw\n");
15313         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15314     } else if (cmailMsgLoaded) {
15315         if (currentMove == cmailOldMove &&
15316             commentList[cmailOldMove] != NULL &&
15317             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15318                    "Black offers a draw" : "White offers a draw")) {
15319             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15320             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15321         } else if (currentMove == cmailOldMove + 1) {
15322             char *offer = WhiteOnMove(cmailOldMove) ?
15323               "White offers a draw" : "Black offers a draw";
15324             AppendComment(currentMove, offer, TRUE);
15325             DisplayComment(currentMove - 1, offer);
15326             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15327         } else {
15328             DisplayError(_("You must make your move before offering a draw"), 0);
15329             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15330         }
15331     } else if (first.offeredDraw) {
15332         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15333     } else {
15334         if (first.sendDrawOffers) {
15335             SendToProgram("draw\n", &first);
15336             userOfferedDraw = TRUE;
15337         }
15338     }
15339 }
15340
15341 void
15342 AdjournEvent ()
15343 {
15344     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15345
15346     if (appData.icsActive) {
15347         SendToICS(ics_prefix);
15348         SendToICS("adjourn\n");
15349     } else {
15350         /* Currently GNU Chess doesn't offer or accept Adjourns */
15351     }
15352 }
15353
15354
15355 void
15356 AbortEvent ()
15357 {
15358     /* Offer Abort or accept pending Abort offer from opponent */
15359
15360     if (appData.icsActive) {
15361         SendToICS(ics_prefix);
15362         SendToICS("abort\n");
15363     } else {
15364         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15365     }
15366 }
15367
15368 void
15369 ResignEvent ()
15370 {
15371     /* Resign.  You can do this even if it's not your turn. */
15372
15373     if (appData.icsActive) {
15374         SendToICS(ics_prefix);
15375         SendToICS("resign\n");
15376     } else {
15377         switch (gameMode) {
15378           case MachinePlaysWhite:
15379             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15380             break;
15381           case MachinePlaysBlack:
15382             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15383             break;
15384           case EditGame:
15385             if (cmailMsgLoaded) {
15386                 TruncateGame();
15387                 if (WhiteOnMove(cmailOldMove)) {
15388                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15389                 } else {
15390                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15391                 }
15392                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15393             }
15394             break;
15395           default:
15396             break;
15397         }
15398     }
15399 }
15400
15401
15402 void
15403 StopObservingEvent ()
15404 {
15405     /* Stop observing current games */
15406     SendToICS(ics_prefix);
15407     SendToICS("unobserve\n");
15408 }
15409
15410 void
15411 StopExaminingEvent ()
15412 {
15413     /* Stop observing current game */
15414     SendToICS(ics_prefix);
15415     SendToICS("unexamine\n");
15416 }
15417
15418 void
15419 ForwardInner (int target)
15420 {
15421     int limit; int oldSeekGraphUp = seekGraphUp;
15422
15423     if (appData.debugMode)
15424         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15425                 target, currentMove, forwardMostMove);
15426
15427     if (gameMode == EditPosition)
15428       return;
15429
15430     seekGraphUp = FALSE;
15431     MarkTargetSquares(1);
15432
15433     if (gameMode == PlayFromGameFile && !pausing)
15434       PauseEvent();
15435
15436     if (gameMode == IcsExamining && pausing)
15437       limit = pauseExamForwardMostMove;
15438     else
15439       limit = forwardMostMove;
15440
15441     if (target > limit) target = limit;
15442
15443     if (target > 0 && moveList[target - 1][0]) {
15444         int fromX, fromY, toX, toY;
15445         toX = moveList[target - 1][2] - AAA;
15446         toY = moveList[target - 1][3] - ONE;
15447         if (moveList[target - 1][1] == '@') {
15448             if (appData.highlightLastMove) {
15449                 SetHighlights(-1, -1, toX, toY);
15450             }
15451         } else {
15452             fromX = moveList[target - 1][0] - AAA;
15453             fromY = moveList[target - 1][1] - ONE;
15454             if (target == currentMove + 1) {
15455                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15456             }
15457             if (appData.highlightLastMove) {
15458                 SetHighlights(fromX, fromY, toX, toY);
15459             }
15460         }
15461     }
15462     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15463         gameMode == Training || gameMode == PlayFromGameFile ||
15464         gameMode == AnalyzeFile) {
15465         while (currentMove < target) {
15466             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15467             SendMoveToProgram(currentMove++, &first);
15468         }
15469     } else {
15470         currentMove = target;
15471     }
15472
15473     if (gameMode == EditGame || gameMode == EndOfGame) {
15474         whiteTimeRemaining = timeRemaining[0][currentMove];
15475         blackTimeRemaining = timeRemaining[1][currentMove];
15476     }
15477     DisplayBothClocks();
15478     DisplayMove(currentMove - 1);
15479     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15480     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15481     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15482         DisplayComment(currentMove - 1, commentList[currentMove]);
15483     }
15484     ClearMap(); // [HGM] exclude: invalidate map
15485 }
15486
15487
15488 void
15489 ForwardEvent ()
15490 {
15491     if (gameMode == IcsExamining && !pausing) {
15492         SendToICS(ics_prefix);
15493         SendToICS("forward\n");
15494     } else {
15495         ForwardInner(currentMove + 1);
15496     }
15497 }
15498
15499 void
15500 ToEndEvent ()
15501 {
15502     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15503         /* to optimze, we temporarily turn off analysis mode while we feed
15504          * the remaining moves to the engine. Otherwise we get analysis output
15505          * after each move.
15506          */
15507         if (first.analysisSupport) {
15508           SendToProgram("exit\nforce\n", &first);
15509           first.analyzing = FALSE;
15510         }
15511     }
15512
15513     if (gameMode == IcsExamining && !pausing) {
15514         SendToICS(ics_prefix);
15515         SendToICS("forward 999999\n");
15516     } else {
15517         ForwardInner(forwardMostMove);
15518     }
15519
15520     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15521         /* we have fed all the moves, so reactivate analysis mode */
15522         SendToProgram("analyze\n", &first);
15523         first.analyzing = TRUE;
15524         /*first.maybeThinking = TRUE;*/
15525         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15526     }
15527 }
15528
15529 void
15530 BackwardInner (int target)
15531 {
15532     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15533
15534     if (appData.debugMode)
15535         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15536                 target, currentMove, forwardMostMove);
15537
15538     if (gameMode == EditPosition) return;
15539     seekGraphUp = FALSE;
15540     MarkTargetSquares(1);
15541     if (currentMove <= backwardMostMove) {
15542         ClearHighlights();
15543         DrawPosition(full_redraw, boards[currentMove]);
15544         return;
15545     }
15546     if (gameMode == PlayFromGameFile && !pausing)
15547       PauseEvent();
15548
15549     if (moveList[target][0]) {
15550         int fromX, fromY, toX, toY;
15551         toX = moveList[target][2] - AAA;
15552         toY = moveList[target][3] - ONE;
15553         if (moveList[target][1] == '@') {
15554             if (appData.highlightLastMove) {
15555                 SetHighlights(-1, -1, toX, toY);
15556             }
15557         } else {
15558             fromX = moveList[target][0] - AAA;
15559             fromY = moveList[target][1] - ONE;
15560             if (target == currentMove - 1) {
15561                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15562             }
15563             if (appData.highlightLastMove) {
15564                 SetHighlights(fromX, fromY, toX, toY);
15565             }
15566         }
15567     }
15568     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15569         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15570         while (currentMove > target) {
15571             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15572                 // null move cannot be undone. Reload program with move history before it.
15573                 int i;
15574                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15575                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15576                 }
15577                 SendBoard(&first, i);
15578               if(second.analyzing) SendBoard(&second, i);
15579                 for(currentMove=i; currentMove<target; currentMove++) {
15580                     SendMoveToProgram(currentMove, &first);
15581                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15582                 }
15583                 break;
15584             }
15585             SendToBoth("undo\n");
15586             currentMove--;
15587         }
15588     } else {
15589         currentMove = target;
15590     }
15591
15592     if (gameMode == EditGame || gameMode == EndOfGame) {
15593         whiteTimeRemaining = timeRemaining[0][currentMove];
15594         blackTimeRemaining = timeRemaining[1][currentMove];
15595     }
15596     DisplayBothClocks();
15597     DisplayMove(currentMove - 1);
15598     DrawPosition(full_redraw, boards[currentMove]);
15599     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15600     // [HGM] PV info: routine tests if comment empty
15601     DisplayComment(currentMove - 1, commentList[currentMove]);
15602     ClearMap(); // [HGM] exclude: invalidate map
15603 }
15604
15605 void
15606 BackwardEvent ()
15607 {
15608     if (gameMode == IcsExamining && !pausing) {
15609         SendToICS(ics_prefix);
15610         SendToICS("backward\n");
15611     } else {
15612         BackwardInner(currentMove - 1);
15613     }
15614 }
15615
15616 void
15617 ToStartEvent ()
15618 {
15619     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15620         /* to optimize, we temporarily turn off analysis mode while we undo
15621          * all the moves. Otherwise we get analysis output after each undo.
15622          */
15623         if (first.analysisSupport) {
15624           SendToProgram("exit\nforce\n", &first);
15625           first.analyzing = FALSE;
15626         }
15627     }
15628
15629     if (gameMode == IcsExamining && !pausing) {
15630         SendToICS(ics_prefix);
15631         SendToICS("backward 999999\n");
15632     } else {
15633         BackwardInner(backwardMostMove);
15634     }
15635
15636     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15637         /* we have fed all the moves, so reactivate analysis mode */
15638         SendToProgram("analyze\n", &first);
15639         first.analyzing = TRUE;
15640         /*first.maybeThinking = TRUE;*/
15641         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15642     }
15643 }
15644
15645 void
15646 ToNrEvent (int to)
15647 {
15648   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15649   if (to >= forwardMostMove) to = forwardMostMove;
15650   if (to <= backwardMostMove) to = backwardMostMove;
15651   if (to < currentMove) {
15652     BackwardInner(to);
15653   } else {
15654     ForwardInner(to);
15655   }
15656 }
15657
15658 void
15659 RevertEvent (Boolean annotate)
15660 {
15661     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15662         return;
15663     }
15664     if (gameMode != IcsExamining) {
15665         DisplayError(_("You are not examining a game"), 0);
15666         return;
15667     }
15668     if (pausing) {
15669         DisplayError(_("You can't revert while pausing"), 0);
15670         return;
15671     }
15672     SendToICS(ics_prefix);
15673     SendToICS("revert\n");
15674 }
15675
15676 void
15677 RetractMoveEvent ()
15678 {
15679     switch (gameMode) {
15680       case MachinePlaysWhite:
15681       case MachinePlaysBlack:
15682         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15683             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15684             return;
15685         }
15686         if (forwardMostMove < 2) return;
15687         currentMove = forwardMostMove = forwardMostMove - 2;
15688         whiteTimeRemaining = timeRemaining[0][currentMove];
15689         blackTimeRemaining = timeRemaining[1][currentMove];
15690         DisplayBothClocks();
15691         DisplayMove(currentMove - 1);
15692         ClearHighlights();/*!! could figure this out*/
15693         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15694         SendToProgram("remove\n", &first);
15695         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15696         break;
15697
15698       case BeginningOfGame:
15699       default:
15700         break;
15701
15702       case IcsPlayingWhite:
15703       case IcsPlayingBlack:
15704         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15705             SendToICS(ics_prefix);
15706             SendToICS("takeback 2\n");
15707         } else {
15708             SendToICS(ics_prefix);
15709             SendToICS("takeback 1\n");
15710         }
15711         break;
15712     }
15713 }
15714
15715 void
15716 MoveNowEvent ()
15717 {
15718     ChessProgramState *cps;
15719
15720     switch (gameMode) {
15721       case MachinePlaysWhite:
15722         if (!WhiteOnMove(forwardMostMove)) {
15723             DisplayError(_("It is your turn"), 0);
15724             return;
15725         }
15726         cps = &first;
15727         break;
15728       case MachinePlaysBlack:
15729         if (WhiteOnMove(forwardMostMove)) {
15730             DisplayError(_("It is your turn"), 0);
15731             return;
15732         }
15733         cps = &first;
15734         break;
15735       case TwoMachinesPlay:
15736         if (WhiteOnMove(forwardMostMove) ==
15737             (first.twoMachinesColor[0] == 'w')) {
15738             cps = &first;
15739         } else {
15740             cps = &second;
15741         }
15742         break;
15743       case BeginningOfGame:
15744       default:
15745         return;
15746     }
15747     SendToProgram("?\n", cps);
15748 }
15749
15750 void
15751 TruncateGameEvent ()
15752 {
15753     EditGameEvent();
15754     if (gameMode != EditGame) return;
15755     TruncateGame();
15756 }
15757
15758 void
15759 TruncateGame ()
15760 {
15761     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15762     if (forwardMostMove > currentMove) {
15763         if (gameInfo.resultDetails != NULL) {
15764             free(gameInfo.resultDetails);
15765             gameInfo.resultDetails = NULL;
15766             gameInfo.result = GameUnfinished;
15767         }
15768         forwardMostMove = currentMove;
15769         HistorySet(parseList, backwardMostMove, forwardMostMove,
15770                    currentMove-1);
15771     }
15772 }
15773
15774 void
15775 HintEvent ()
15776 {
15777     if (appData.noChessProgram) return;
15778     switch (gameMode) {
15779       case MachinePlaysWhite:
15780         if (WhiteOnMove(forwardMostMove)) {
15781             DisplayError(_("Wait until your turn."), 0);
15782             return;
15783         }
15784         break;
15785       case BeginningOfGame:
15786       case MachinePlaysBlack:
15787         if (!WhiteOnMove(forwardMostMove)) {
15788             DisplayError(_("Wait until your turn."), 0);
15789             return;
15790         }
15791         break;
15792       default:
15793         DisplayError(_("No hint available"), 0);
15794         return;
15795     }
15796     SendToProgram("hint\n", &first);
15797     hintRequested = TRUE;
15798 }
15799
15800 void
15801 CreateBookEvent ()
15802 {
15803     ListGame * lg = (ListGame *) gameList.head;
15804     FILE *f, *g;
15805     int nItem;
15806     static int secondTime = FALSE;
15807
15808     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15809         DisplayError(_("Game list not loaded or empty"), 0);
15810         return;
15811     }
15812
15813     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15814         fclose(g);
15815         secondTime++;
15816         DisplayNote(_("Book file exists! Try again for overwrite."));
15817         return;
15818     }
15819
15820     creatingBook = TRUE;
15821     secondTime = FALSE;
15822
15823     /* Get list size */
15824     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15825         LoadGame(f, nItem, "", TRUE);
15826         AddGameToBook(TRUE);
15827         lg = (ListGame *) lg->node.succ;
15828     }
15829
15830     creatingBook = FALSE;
15831     FlushBook();
15832 }
15833
15834 void
15835 BookEvent ()
15836 {
15837     if (appData.noChessProgram) return;
15838     switch (gameMode) {
15839       case MachinePlaysWhite:
15840         if (WhiteOnMove(forwardMostMove)) {
15841             DisplayError(_("Wait until your turn."), 0);
15842             return;
15843         }
15844         break;
15845       case BeginningOfGame:
15846       case MachinePlaysBlack:
15847         if (!WhiteOnMove(forwardMostMove)) {
15848             DisplayError(_("Wait until your turn."), 0);
15849             return;
15850         }
15851         break;
15852       case EditPosition:
15853         EditPositionDone(TRUE);
15854         break;
15855       case TwoMachinesPlay:
15856         return;
15857       default:
15858         break;
15859     }
15860     SendToProgram("bk\n", &first);
15861     bookOutput[0] = NULLCHAR;
15862     bookRequested = TRUE;
15863 }
15864
15865 void
15866 AboutGameEvent ()
15867 {
15868     char *tags = PGNTags(&gameInfo);
15869     TagsPopUp(tags, CmailMsg());
15870     free(tags);
15871 }
15872
15873 /* end button procedures */
15874
15875 void
15876 PrintPosition (FILE *fp, int move)
15877 {
15878     int i, j;
15879
15880     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15881         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15882             char c = PieceToChar(boards[move][i][j]);
15883             fputc(c == 'x' ? '.' : c, fp);
15884             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15885         }
15886     }
15887     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15888       fprintf(fp, "white to play\n");
15889     else
15890       fprintf(fp, "black to play\n");
15891 }
15892
15893 void
15894 PrintOpponents (FILE *fp)
15895 {
15896     if (gameInfo.white != NULL) {
15897         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15898     } else {
15899         fprintf(fp, "\n");
15900     }
15901 }
15902
15903 /* Find last component of program's own name, using some heuristics */
15904 void
15905 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15906 {
15907     char *p, *q, c;
15908     int local = (strcmp(host, "localhost") == 0);
15909     while (!local && (p = strchr(prog, ';')) != NULL) {
15910         p++;
15911         while (*p == ' ') p++;
15912         prog = p;
15913     }
15914     if (*prog == '"' || *prog == '\'') {
15915         q = strchr(prog + 1, *prog);
15916     } else {
15917         q = strchr(prog, ' ');
15918     }
15919     if (q == NULL) q = prog + strlen(prog);
15920     p = q;
15921     while (p >= prog && *p != '/' && *p != '\\') p--;
15922     p++;
15923     if(p == prog && *p == '"') p++;
15924     c = *q; *q = 0;
15925     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15926     memcpy(buf, p, q - p);
15927     buf[q - p] = NULLCHAR;
15928     if (!local) {
15929         strcat(buf, "@");
15930         strcat(buf, host);
15931     }
15932 }
15933
15934 char *
15935 TimeControlTagValue ()
15936 {
15937     char buf[MSG_SIZ];
15938     if (!appData.clockMode) {
15939       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15940     } else if (movesPerSession > 0) {
15941       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15942     } else if (timeIncrement == 0) {
15943       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15944     } else {
15945       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15946     }
15947     return StrSave(buf);
15948 }
15949
15950 void
15951 SetGameInfo ()
15952 {
15953     /* This routine is used only for certain modes */
15954     VariantClass v = gameInfo.variant;
15955     ChessMove r = GameUnfinished;
15956     char *p = NULL;
15957
15958     if(keepInfo) return;
15959
15960     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15961         r = gameInfo.result;
15962         p = gameInfo.resultDetails;
15963         gameInfo.resultDetails = NULL;
15964     }
15965     ClearGameInfo(&gameInfo);
15966     gameInfo.variant = v;
15967
15968     switch (gameMode) {
15969       case MachinePlaysWhite:
15970         gameInfo.event = StrSave( appData.pgnEventHeader );
15971         gameInfo.site = StrSave(HostName());
15972         gameInfo.date = PGNDate();
15973         gameInfo.round = StrSave("-");
15974         gameInfo.white = StrSave(first.tidy);
15975         gameInfo.black = StrSave(UserName());
15976         gameInfo.timeControl = TimeControlTagValue();
15977         break;
15978
15979       case MachinePlaysBlack:
15980         gameInfo.event = StrSave( appData.pgnEventHeader );
15981         gameInfo.site = StrSave(HostName());
15982         gameInfo.date = PGNDate();
15983         gameInfo.round = StrSave("-");
15984         gameInfo.white = StrSave(UserName());
15985         gameInfo.black = StrSave(first.tidy);
15986         gameInfo.timeControl = TimeControlTagValue();
15987         break;
15988
15989       case TwoMachinesPlay:
15990         gameInfo.event = StrSave( appData.pgnEventHeader );
15991         gameInfo.site = StrSave(HostName());
15992         gameInfo.date = PGNDate();
15993         if (roundNr > 0) {
15994             char buf[MSG_SIZ];
15995             snprintf(buf, MSG_SIZ, "%d", roundNr);
15996             gameInfo.round = StrSave(buf);
15997         } else {
15998             gameInfo.round = StrSave("-");
15999         }
16000         if (first.twoMachinesColor[0] == 'w') {
16001             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16002             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16003         } else {
16004             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16005             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16006         }
16007         gameInfo.timeControl = TimeControlTagValue();
16008         break;
16009
16010       case EditGame:
16011         gameInfo.event = StrSave("Edited game");
16012         gameInfo.site = StrSave(HostName());
16013         gameInfo.date = PGNDate();
16014         gameInfo.round = StrSave("-");
16015         gameInfo.white = StrSave("-");
16016         gameInfo.black = StrSave("-");
16017         gameInfo.result = r;
16018         gameInfo.resultDetails = p;
16019         break;
16020
16021       case EditPosition:
16022         gameInfo.event = StrSave("Edited position");
16023         gameInfo.site = StrSave(HostName());
16024         gameInfo.date = PGNDate();
16025         gameInfo.round = StrSave("-");
16026         gameInfo.white = StrSave("-");
16027         gameInfo.black = StrSave("-");
16028         break;
16029
16030       case IcsPlayingWhite:
16031       case IcsPlayingBlack:
16032       case IcsObserving:
16033       case IcsExamining:
16034         break;
16035
16036       case PlayFromGameFile:
16037         gameInfo.event = StrSave("Game from non-PGN file");
16038         gameInfo.site = StrSave(HostName());
16039         gameInfo.date = PGNDate();
16040         gameInfo.round = StrSave("-");
16041         gameInfo.white = StrSave("?");
16042         gameInfo.black = StrSave("?");
16043         break;
16044
16045       default:
16046         break;
16047     }
16048 }
16049
16050 void
16051 ReplaceComment (int index, char *text)
16052 {
16053     int len;
16054     char *p;
16055     float score;
16056
16057     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16058        pvInfoList[index-1].depth == len &&
16059        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16060        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16061     while (*text == '\n') text++;
16062     len = strlen(text);
16063     while (len > 0 && text[len - 1] == '\n') len--;
16064
16065     if (commentList[index] != NULL)
16066       free(commentList[index]);
16067
16068     if (len == 0) {
16069         commentList[index] = NULL;
16070         return;
16071     }
16072   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16073       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16074       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16075     commentList[index] = (char *) malloc(len + 2);
16076     strncpy(commentList[index], text, len);
16077     commentList[index][len] = '\n';
16078     commentList[index][len + 1] = NULLCHAR;
16079   } else {
16080     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16081     char *p;
16082     commentList[index] = (char *) malloc(len + 7);
16083     safeStrCpy(commentList[index], "{\n", 3);
16084     safeStrCpy(commentList[index]+2, text, len+1);
16085     commentList[index][len+2] = NULLCHAR;
16086     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16087     strcat(commentList[index], "\n}\n");
16088   }
16089 }
16090
16091 void
16092 CrushCRs (char *text)
16093 {
16094   char *p = text;
16095   char *q = text;
16096   char ch;
16097
16098   do {
16099     ch = *p++;
16100     if (ch == '\r') continue;
16101     *q++ = ch;
16102   } while (ch != '\0');
16103 }
16104
16105 void
16106 AppendComment (int index, char *text, Boolean addBraces)
16107 /* addBraces  tells if we should add {} */
16108 {
16109     int oldlen, len;
16110     char *old;
16111
16112 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16113     if(addBraces == 3) addBraces = 0; else // force appending literally
16114     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16115
16116     CrushCRs(text);
16117     while (*text == '\n') text++;
16118     len = strlen(text);
16119     while (len > 0 && text[len - 1] == '\n') len--;
16120     text[len] = NULLCHAR;
16121
16122     if (len == 0) return;
16123
16124     if (commentList[index] != NULL) {
16125       Boolean addClosingBrace = addBraces;
16126         old = commentList[index];
16127         oldlen = strlen(old);
16128         while(commentList[index][oldlen-1] ==  '\n')
16129           commentList[index][--oldlen] = NULLCHAR;
16130         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16131         safeStrCpy(commentList[index], old, oldlen + len + 6);
16132         free(old);
16133         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16134         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16135           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16136           while (*text == '\n') { text++; len--; }
16137           commentList[index][--oldlen] = NULLCHAR;
16138       }
16139         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16140         else          strcat(commentList[index], "\n");
16141         strcat(commentList[index], text);
16142         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16143         else          strcat(commentList[index], "\n");
16144     } else {
16145         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16146         if(addBraces)
16147           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16148         else commentList[index][0] = NULLCHAR;
16149         strcat(commentList[index], text);
16150         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16151         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16152     }
16153 }
16154
16155 static char *
16156 FindStr (char * text, char * sub_text)
16157 {
16158     char * result = strstr( text, sub_text );
16159
16160     if( result != NULL ) {
16161         result += strlen( sub_text );
16162     }
16163
16164     return result;
16165 }
16166
16167 /* [AS] Try to extract PV info from PGN comment */
16168 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16169 char *
16170 GetInfoFromComment (int index, char * text)
16171 {
16172     char * sep = text, *p;
16173
16174     if( text != NULL && index > 0 ) {
16175         int score = 0;
16176         int depth = 0;
16177         int time = -1, sec = 0, deci;
16178         char * s_eval = FindStr( text, "[%eval " );
16179         char * s_emt = FindStr( text, "[%emt " );
16180 #if 0
16181         if( s_eval != NULL || s_emt != NULL ) {
16182 #else
16183         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16184 #endif
16185             /* New style */
16186             char delim;
16187
16188             if( s_eval != NULL ) {
16189                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16190                     return text;
16191                 }
16192
16193                 if( delim != ']' ) {
16194                     return text;
16195                 }
16196             }
16197
16198             if( s_emt != NULL ) {
16199             }
16200                 return text;
16201         }
16202         else {
16203             /* We expect something like: [+|-]nnn.nn/dd */
16204             int score_lo = 0;
16205
16206             if(*text != '{') return text; // [HGM] braces: must be normal comment
16207
16208             sep = strchr( text, '/' );
16209             if( sep == NULL || sep < (text+4) ) {
16210                 return text;
16211             }
16212
16213             p = text;
16214             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16215             if(p[1] == '(') { // comment starts with PV
16216                p = strchr(p, ')'); // locate end of PV
16217                if(p == NULL || sep < p+5) return text;
16218                // at this point we have something like "{(.*) +0.23/6 ..."
16219                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16220                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16221                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16222             }
16223             time = -1; sec = -1; deci = -1;
16224             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16225                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16226                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16227                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16228                 return text;
16229             }
16230
16231             if( score_lo < 0 || score_lo >= 100 ) {
16232                 return text;
16233             }
16234
16235             if(sec >= 0) time = 600*time + 10*sec; else
16236             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16237
16238             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16239
16240             /* [HGM] PV time: now locate end of PV info */
16241             while( *++sep >= '0' && *sep <= '9'); // strip depth
16242             if(time >= 0)
16243             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16244             if(sec >= 0)
16245             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16246             if(deci >= 0)
16247             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16248             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16249         }
16250
16251         if( depth <= 0 ) {
16252             return text;
16253         }
16254
16255         if( time < 0 ) {
16256             time = -1;
16257         }
16258
16259         pvInfoList[index-1].depth = depth;
16260         pvInfoList[index-1].score = score;
16261         pvInfoList[index-1].time  = 10*time; // centi-sec
16262         if(*sep == '}') *sep = 0; else *--sep = '{';
16263         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16264     }
16265     return sep;
16266 }
16267
16268 void
16269 SendToProgram (char *message, ChessProgramState *cps)
16270 {
16271     int count, outCount, error;
16272     char buf[MSG_SIZ];
16273
16274     if (cps->pr == NoProc) return;
16275     Attention(cps);
16276
16277     if (appData.debugMode) {
16278         TimeMark now;
16279         GetTimeMark(&now);
16280         fprintf(debugFP, "%ld >%-6s: %s",
16281                 SubtractTimeMarks(&now, &programStartTime),
16282                 cps->which, message);
16283         if(serverFP)
16284             fprintf(serverFP, "%ld >%-6s: %s",
16285                 SubtractTimeMarks(&now, &programStartTime),
16286                 cps->which, message), fflush(serverFP);
16287     }
16288
16289     count = strlen(message);
16290     outCount = OutputToProcess(cps->pr, message, count, &error);
16291     if (outCount < count && !exiting
16292                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16293       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16294       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16295         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16296             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16297                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16298                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16299                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16300             } else {
16301                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16302                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16303                 gameInfo.result = res;
16304             }
16305             gameInfo.resultDetails = StrSave(buf);
16306         }
16307         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16308         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16309     }
16310 }
16311
16312 void
16313 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16314 {
16315     char *end_str;
16316     char buf[MSG_SIZ];
16317     ChessProgramState *cps = (ChessProgramState *)closure;
16318
16319     if (isr != cps->isr) return; /* Killed intentionally */
16320     if (count <= 0) {
16321         if (count == 0) {
16322             RemoveInputSource(cps->isr);
16323             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16324                     _(cps->which), cps->program);
16325             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16326             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16327                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16328                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16329                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16330                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16331                 } else {
16332                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16333                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16334                     gameInfo.result = res;
16335                 }
16336                 gameInfo.resultDetails = StrSave(buf);
16337             }
16338             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16339             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16340         } else {
16341             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16342                     _(cps->which), cps->program);
16343             RemoveInputSource(cps->isr);
16344
16345             /* [AS] Program is misbehaving badly... kill it */
16346             if( count == -2 ) {
16347                 DestroyChildProcess( cps->pr, 9 );
16348                 cps->pr = NoProc;
16349             }
16350
16351             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16352         }
16353         return;
16354     }
16355
16356     if ((end_str = strchr(message, '\r')) != NULL)
16357       *end_str = NULLCHAR;
16358     if ((end_str = strchr(message, '\n')) != NULL)
16359       *end_str = NULLCHAR;
16360
16361     if (appData.debugMode) {
16362         TimeMark now; int print = 1;
16363         char *quote = ""; char c; int i;
16364
16365         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16366                 char start = message[0];
16367                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16368                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16369                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16370                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16371                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16372                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16373                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16374                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16375                    sscanf(message, "hint: %c", &c)!=1 &&
16376                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16377                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16378                     print = (appData.engineComments >= 2);
16379                 }
16380                 message[0] = start; // restore original message
16381         }
16382         if(print) {
16383                 GetTimeMark(&now);
16384                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16385                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16386                         quote,
16387                         message);
16388                 if(serverFP)
16389                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16390                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16391                         quote,
16392                         message), fflush(serverFP);
16393         }
16394     }
16395
16396     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16397     if (appData.icsEngineAnalyze) {
16398         if (strstr(message, "whisper") != NULL ||
16399              strstr(message, "kibitz") != NULL ||
16400             strstr(message, "tellics") != NULL) return;
16401     }
16402
16403     HandleMachineMove(message, cps);
16404 }
16405
16406
16407 void
16408 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16409 {
16410     char buf[MSG_SIZ];
16411     int seconds;
16412
16413     if( timeControl_2 > 0 ) {
16414         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16415             tc = timeControl_2;
16416         }
16417     }
16418     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16419     inc /= cps->timeOdds;
16420     st  /= cps->timeOdds;
16421
16422     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16423
16424     if (st > 0) {
16425       /* Set exact time per move, normally using st command */
16426       if (cps->stKludge) {
16427         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16428         seconds = st % 60;
16429         if (seconds == 0) {
16430           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16431         } else {
16432           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16433         }
16434       } else {
16435         snprintf(buf, MSG_SIZ, "st %d\n", st);
16436       }
16437     } else {
16438       /* Set conventional or incremental time control, using level command */
16439       if (seconds == 0) {
16440         /* Note old gnuchess bug -- minutes:seconds used to not work.
16441            Fixed in later versions, but still avoid :seconds
16442            when seconds is 0. */
16443         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16444       } else {
16445         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16446                  seconds, inc/1000.);
16447       }
16448     }
16449     SendToProgram(buf, cps);
16450
16451     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16452     /* Orthogonally, limit search to given depth */
16453     if (sd > 0) {
16454       if (cps->sdKludge) {
16455         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16456       } else {
16457         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16458       }
16459       SendToProgram(buf, cps);
16460     }
16461
16462     if(cps->nps >= 0) { /* [HGM] nps */
16463         if(cps->supportsNPS == FALSE)
16464           cps->nps = -1; // don't use if engine explicitly says not supported!
16465         else {
16466           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16467           SendToProgram(buf, cps);
16468         }
16469     }
16470 }
16471
16472 ChessProgramState *
16473 WhitePlayer ()
16474 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16475 {
16476     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16477        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16478         return &second;
16479     return &first;
16480 }
16481
16482 void
16483 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16484 {
16485     char message[MSG_SIZ];
16486     long time, otime;
16487
16488     /* Note: this routine must be called when the clocks are stopped
16489        or when they have *just* been set or switched; otherwise
16490        it will be off by the time since the current tick started.
16491     */
16492     if (machineWhite) {
16493         time = whiteTimeRemaining / 10;
16494         otime = blackTimeRemaining / 10;
16495     } else {
16496         time = blackTimeRemaining / 10;
16497         otime = whiteTimeRemaining / 10;
16498     }
16499     /* [HGM] translate opponent's time by time-odds factor */
16500     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16501
16502     if (time <= 0) time = 1;
16503     if (otime <= 0) otime = 1;
16504
16505     snprintf(message, MSG_SIZ, "time %ld\n", time);
16506     SendToProgram(message, cps);
16507
16508     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16509     SendToProgram(message, cps);
16510 }
16511
16512 char *
16513 EngineDefinedVariant (ChessProgramState *cps, int n)
16514 {   // return name of n-th unknown variant that engine supports
16515     static char buf[MSG_SIZ];
16516     char *p, *s = cps->variants;
16517     if(!s) return NULL;
16518     do { // parse string from variants feature
16519       VariantClass v;
16520         p = strchr(s, ',');
16521         if(p) *p = NULLCHAR;
16522       v = StringToVariant(s);
16523       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16524         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16525             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16526         }
16527         if(p) *p++ = ',';
16528         if(n < 0) return buf;
16529     } while(s = p);
16530     return NULL;
16531 }
16532
16533 int
16534 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16535 {
16536   char buf[MSG_SIZ];
16537   int len = strlen(name);
16538   int val;
16539
16540   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16541     (*p) += len + 1;
16542     sscanf(*p, "%d", &val);
16543     *loc = (val != 0);
16544     while (**p && **p != ' ')
16545       (*p)++;
16546     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16547     SendToProgram(buf, cps);
16548     return TRUE;
16549   }
16550   return FALSE;
16551 }
16552
16553 int
16554 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16555 {
16556   char buf[MSG_SIZ];
16557   int len = strlen(name);
16558   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16559     (*p) += len + 1;
16560     sscanf(*p, "%d", loc);
16561     while (**p && **p != ' ') (*p)++;
16562     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16563     SendToProgram(buf, cps);
16564     return TRUE;
16565   }
16566   return FALSE;
16567 }
16568
16569 int
16570 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16571 {
16572   char buf[MSG_SIZ];
16573   int len = strlen(name);
16574   if (strncmp((*p), name, len) == 0
16575       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16576     (*p) += len + 2;
16577     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16578     sscanf(*p, "%[^\"]", *loc);
16579     while (**p && **p != '\"') (*p)++;
16580     if (**p == '\"') (*p)++;
16581     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16582     SendToProgram(buf, cps);
16583     return TRUE;
16584   }
16585   return FALSE;
16586 }
16587
16588 int
16589 ParseOption (Option *opt, ChessProgramState *cps)
16590 // [HGM] options: process the string that defines an engine option, and determine
16591 // name, type, default value, and allowed value range
16592 {
16593         char *p, *q, buf[MSG_SIZ];
16594         int n, min = (-1)<<31, max = 1<<31, def;
16595
16596         if(p = strstr(opt->name, " -spin ")) {
16597             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16598             if(max < min) max = min; // enforce consistency
16599             if(def < min) def = min;
16600             if(def > max) def = max;
16601             opt->value = def;
16602             opt->min = min;
16603             opt->max = max;
16604             opt->type = Spin;
16605         } else if((p = strstr(opt->name, " -slider "))) {
16606             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16607             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16608             if(max < min) max = min; // enforce consistency
16609             if(def < min) def = min;
16610             if(def > max) def = max;
16611             opt->value = def;
16612             opt->min = min;
16613             opt->max = max;
16614             opt->type = Spin; // Slider;
16615         } else if((p = strstr(opt->name, " -string "))) {
16616             opt->textValue = p+9;
16617             opt->type = TextBox;
16618         } else if((p = strstr(opt->name, " -file "))) {
16619             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16620             opt->textValue = p+7;
16621             opt->type = FileName; // FileName;
16622         } else if((p = strstr(opt->name, " -path "))) {
16623             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16624             opt->textValue = p+7;
16625             opt->type = PathName; // PathName;
16626         } else if(p = strstr(opt->name, " -check ")) {
16627             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16628             opt->value = (def != 0);
16629             opt->type = CheckBox;
16630         } else if(p = strstr(opt->name, " -combo ")) {
16631             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16632             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16633             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16634             opt->value = n = 0;
16635             while(q = StrStr(q, " /// ")) {
16636                 n++; *q = 0;    // count choices, and null-terminate each of them
16637                 q += 5;
16638                 if(*q == '*') { // remember default, which is marked with * prefix
16639                     q++;
16640                     opt->value = n;
16641                 }
16642                 cps->comboList[cps->comboCnt++] = q;
16643             }
16644             cps->comboList[cps->comboCnt++] = NULL;
16645             opt->max = n + 1;
16646             opt->type = ComboBox;
16647         } else if(p = strstr(opt->name, " -button")) {
16648             opt->type = Button;
16649         } else if(p = strstr(opt->name, " -save")) {
16650             opt->type = SaveButton;
16651         } else return FALSE;
16652         *p = 0; // terminate option name
16653         // now look if the command-line options define a setting for this engine option.
16654         if(cps->optionSettings && cps->optionSettings[0])
16655             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16656         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16657           snprintf(buf, MSG_SIZ, "option %s", p);
16658                 if(p = strstr(buf, ",")) *p = 0;
16659                 if(q = strchr(buf, '=')) switch(opt->type) {
16660                     case ComboBox:
16661                         for(n=0; n<opt->max; n++)
16662                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16663                         break;
16664                     case TextBox:
16665                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16666                         break;
16667                     case Spin:
16668                     case CheckBox:
16669                         opt->value = atoi(q+1);
16670                     default:
16671                         break;
16672                 }
16673                 strcat(buf, "\n");
16674                 SendToProgram(buf, cps);
16675         }
16676         return TRUE;
16677 }
16678
16679 void
16680 FeatureDone (ChessProgramState *cps, int val)
16681 {
16682   DelayedEventCallback cb = GetDelayedEvent();
16683   if ((cb == InitBackEnd3 && cps == &first) ||
16684       (cb == SettingsMenuIfReady && cps == &second) ||
16685       (cb == LoadEngine) ||
16686       (cb == TwoMachinesEventIfReady)) {
16687     CancelDelayedEvent();
16688     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16689   }
16690   cps->initDone = val;
16691   if(val) cps->reload = FALSE;
16692 }
16693
16694 /* Parse feature command from engine */
16695 void
16696 ParseFeatures (char *args, ChessProgramState *cps)
16697 {
16698   char *p = args;
16699   char *q = NULL;
16700   int val;
16701   char buf[MSG_SIZ];
16702
16703   for (;;) {
16704     while (*p == ' ') p++;
16705     if (*p == NULLCHAR) return;
16706
16707     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16708     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16709     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16710     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16711     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16712     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16713     if (BoolFeature(&p, "reuse", &val, cps)) {
16714       /* Engine can disable reuse, but can't enable it if user said no */
16715       if (!val) cps->reuse = FALSE;
16716       continue;
16717     }
16718     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16719     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16720       if (gameMode == TwoMachinesPlay) {
16721         DisplayTwoMachinesTitle();
16722       } else {
16723         DisplayTitle("");
16724       }
16725       continue;
16726     }
16727     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16728     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16729     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16730     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16731     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16732     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16733     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16734     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16735     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16736     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16737     if (IntFeature(&p, "done", &val, cps)) {
16738       FeatureDone(cps, val);
16739       continue;
16740     }
16741     /* Added by Tord: */
16742     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16743     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16744     /* End of additions by Tord */
16745
16746     /* [HGM] added features: */
16747     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16748     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16749     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16750     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16751     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16752     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16753     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16754     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16755         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16756         FREE(cps->option[cps->nrOptions].name);
16757         cps->option[cps->nrOptions].name = q; q = NULL;
16758         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16759           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16760             SendToProgram(buf, cps);
16761             continue;
16762         }
16763         if(cps->nrOptions >= MAX_OPTIONS) {
16764             cps->nrOptions--;
16765             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16766             DisplayError(buf, 0);
16767         }
16768         continue;
16769     }
16770     /* End of additions by HGM */
16771
16772     /* unknown feature: complain and skip */
16773     q = p;
16774     while (*q && *q != '=') q++;
16775     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16776     SendToProgram(buf, cps);
16777     p = q;
16778     if (*p == '=') {
16779       p++;
16780       if (*p == '\"') {
16781         p++;
16782         while (*p && *p != '\"') p++;
16783         if (*p == '\"') p++;
16784       } else {
16785         while (*p && *p != ' ') p++;
16786       }
16787     }
16788   }
16789
16790 }
16791
16792 void
16793 PeriodicUpdatesEvent (int newState)
16794 {
16795     if (newState == appData.periodicUpdates)
16796       return;
16797
16798     appData.periodicUpdates=newState;
16799
16800     /* Display type changes, so update it now */
16801 //    DisplayAnalysis();
16802
16803     /* Get the ball rolling again... */
16804     if (newState) {
16805         AnalysisPeriodicEvent(1);
16806         StartAnalysisClock();
16807     }
16808 }
16809
16810 void
16811 PonderNextMoveEvent (int newState)
16812 {
16813     if (newState == appData.ponderNextMove) return;
16814     if (gameMode == EditPosition) EditPositionDone(TRUE);
16815     if (newState) {
16816         SendToProgram("hard\n", &first);
16817         if (gameMode == TwoMachinesPlay) {
16818             SendToProgram("hard\n", &second);
16819         }
16820     } else {
16821         SendToProgram("easy\n", &first);
16822         thinkOutput[0] = NULLCHAR;
16823         if (gameMode == TwoMachinesPlay) {
16824             SendToProgram("easy\n", &second);
16825         }
16826     }
16827     appData.ponderNextMove = newState;
16828 }
16829
16830 void
16831 NewSettingEvent (int option, int *feature, char *command, int value)
16832 {
16833     char buf[MSG_SIZ];
16834
16835     if (gameMode == EditPosition) EditPositionDone(TRUE);
16836     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16837     if(feature == NULL || *feature) SendToProgram(buf, &first);
16838     if (gameMode == TwoMachinesPlay) {
16839         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16840     }
16841 }
16842
16843 void
16844 ShowThinkingEvent ()
16845 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16846 {
16847     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16848     int newState = appData.showThinking
16849         // [HGM] thinking: other features now need thinking output as well
16850         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16851
16852     if (oldState == newState) return;
16853     oldState = newState;
16854     if (gameMode == EditPosition) EditPositionDone(TRUE);
16855     if (oldState) {
16856         SendToProgram("post\n", &first);
16857         if (gameMode == TwoMachinesPlay) {
16858             SendToProgram("post\n", &second);
16859         }
16860     } else {
16861         SendToProgram("nopost\n", &first);
16862         thinkOutput[0] = NULLCHAR;
16863         if (gameMode == TwoMachinesPlay) {
16864             SendToProgram("nopost\n", &second);
16865         }
16866     }
16867 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16868 }
16869
16870 void
16871 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16872 {
16873   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16874   if (pr == NoProc) return;
16875   AskQuestion(title, question, replyPrefix, pr);
16876 }
16877
16878 void
16879 TypeInEvent (char firstChar)
16880 {
16881     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16882         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16883         gameMode == AnalyzeMode || gameMode == EditGame ||
16884         gameMode == EditPosition || gameMode == IcsExamining ||
16885         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16886         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16887                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16888                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16889         gameMode == Training) PopUpMoveDialog(firstChar);
16890 }
16891
16892 void
16893 TypeInDoneEvent (char *move)
16894 {
16895         Board board;
16896         int n, fromX, fromY, toX, toY;
16897         char promoChar;
16898         ChessMove moveType;
16899
16900         // [HGM] FENedit
16901         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16902                 EditPositionPasteFEN(move);
16903                 return;
16904         }
16905         // [HGM] movenum: allow move number to be typed in any mode
16906         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16907           ToNrEvent(2*n-1);
16908           return;
16909         }
16910         // undocumented kludge: allow command-line option to be typed in!
16911         // (potentially fatal, and does not implement the effect of the option.)
16912         // should only be used for options that are values on which future decisions will be made,
16913         // and definitely not on options that would be used during initialization.
16914         if(strstr(move, "!!! -") == move) {
16915             ParseArgsFromString(move+4);
16916             return;
16917         }
16918
16919       if (gameMode != EditGame && currentMove != forwardMostMove &&
16920         gameMode != Training) {
16921         DisplayMoveError(_("Displayed move is not current"));
16922       } else {
16923         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16924           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16925         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16926         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16927           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16928           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16929         } else {
16930           DisplayMoveError(_("Could not parse move"));
16931         }
16932       }
16933 }
16934
16935 void
16936 DisplayMove (int moveNumber)
16937 {
16938     char message[MSG_SIZ];
16939     char res[MSG_SIZ];
16940     char cpThinkOutput[MSG_SIZ];
16941
16942     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16943
16944     if (moveNumber == forwardMostMove - 1 ||
16945         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16946
16947         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16948
16949         if (strchr(cpThinkOutput, '\n')) {
16950             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16951         }
16952     } else {
16953         *cpThinkOutput = NULLCHAR;
16954     }
16955
16956     /* [AS] Hide thinking from human user */
16957     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16958         *cpThinkOutput = NULLCHAR;
16959         if( thinkOutput[0] != NULLCHAR ) {
16960             int i;
16961
16962             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16963                 cpThinkOutput[i] = '.';
16964             }
16965             cpThinkOutput[i] = NULLCHAR;
16966             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16967         }
16968     }
16969
16970     if (moveNumber == forwardMostMove - 1 &&
16971         gameInfo.resultDetails != NULL) {
16972         if (gameInfo.resultDetails[0] == NULLCHAR) {
16973           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16974         } else {
16975           snprintf(res, MSG_SIZ, " {%s} %s",
16976                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16977         }
16978     } else {
16979         res[0] = NULLCHAR;
16980     }
16981
16982     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16983         DisplayMessage(res, cpThinkOutput);
16984     } else {
16985       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16986                 WhiteOnMove(moveNumber) ? " " : ".. ",
16987                 parseList[moveNumber], res);
16988         DisplayMessage(message, cpThinkOutput);
16989     }
16990 }
16991
16992 void
16993 DisplayComment (int moveNumber, char *text)
16994 {
16995     char title[MSG_SIZ];
16996
16997     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16998       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16999     } else {
17000       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17001               WhiteOnMove(moveNumber) ? " " : ".. ",
17002               parseList[moveNumber]);
17003     }
17004     if (text != NULL && (appData.autoDisplayComment || commentUp))
17005         CommentPopUp(title, text);
17006 }
17007
17008 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17009  * might be busy thinking or pondering.  It can be omitted if your
17010  * gnuchess is configured to stop thinking immediately on any user
17011  * input.  However, that gnuchess feature depends on the FIONREAD
17012  * ioctl, which does not work properly on some flavors of Unix.
17013  */
17014 void
17015 Attention (ChessProgramState *cps)
17016 {
17017 #if ATTENTION
17018     if (!cps->useSigint) return;
17019     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17020     switch (gameMode) {
17021       case MachinePlaysWhite:
17022       case MachinePlaysBlack:
17023       case TwoMachinesPlay:
17024       case IcsPlayingWhite:
17025       case IcsPlayingBlack:
17026       case AnalyzeMode:
17027       case AnalyzeFile:
17028         /* Skip if we know it isn't thinking */
17029         if (!cps->maybeThinking) return;
17030         if (appData.debugMode)
17031           fprintf(debugFP, "Interrupting %s\n", cps->which);
17032         InterruptChildProcess(cps->pr);
17033         cps->maybeThinking = FALSE;
17034         break;
17035       default:
17036         break;
17037     }
17038 #endif /*ATTENTION*/
17039 }
17040
17041 int
17042 CheckFlags ()
17043 {
17044     if (whiteTimeRemaining <= 0) {
17045         if (!whiteFlag) {
17046             whiteFlag = TRUE;
17047             if (appData.icsActive) {
17048                 if (appData.autoCallFlag &&
17049                     gameMode == IcsPlayingBlack && !blackFlag) {
17050                   SendToICS(ics_prefix);
17051                   SendToICS("flag\n");
17052                 }
17053             } else {
17054                 if (blackFlag) {
17055                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17056                 } else {
17057                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17058                     if (appData.autoCallFlag) {
17059                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17060                         return TRUE;
17061                     }
17062                 }
17063             }
17064         }
17065     }
17066     if (blackTimeRemaining <= 0) {
17067         if (!blackFlag) {
17068             blackFlag = TRUE;
17069             if (appData.icsActive) {
17070                 if (appData.autoCallFlag &&
17071                     gameMode == IcsPlayingWhite && !whiteFlag) {
17072                   SendToICS(ics_prefix);
17073                   SendToICS("flag\n");
17074                 }
17075             } else {
17076                 if (whiteFlag) {
17077                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17078                 } else {
17079                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17080                     if (appData.autoCallFlag) {
17081                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17082                         return TRUE;
17083                     }
17084                 }
17085             }
17086         }
17087     }
17088     return FALSE;
17089 }
17090
17091 void
17092 CheckTimeControl ()
17093 {
17094     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17095         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17096
17097     /*
17098      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17099      */
17100     if ( !WhiteOnMove(forwardMostMove) ) {
17101         /* White made time control */
17102         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17103         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17104         /* [HGM] time odds: correct new time quota for time odds! */
17105                                             / WhitePlayer()->timeOdds;
17106         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17107     } else {
17108         lastBlack -= blackTimeRemaining;
17109         /* Black made time control */
17110         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17111                                             / WhitePlayer()->other->timeOdds;
17112         lastWhite = whiteTimeRemaining;
17113     }
17114 }
17115
17116 void
17117 DisplayBothClocks ()
17118 {
17119     int wom = gameMode == EditPosition ?
17120       !blackPlaysFirst : WhiteOnMove(currentMove);
17121     DisplayWhiteClock(whiteTimeRemaining, wom);
17122     DisplayBlackClock(blackTimeRemaining, !wom);
17123 }
17124
17125
17126 /* Timekeeping seems to be a portability nightmare.  I think everyone
17127    has ftime(), but I'm really not sure, so I'm including some ifdefs
17128    to use other calls if you don't.  Clocks will be less accurate if
17129    you have neither ftime nor gettimeofday.
17130 */
17131
17132 /* VS 2008 requires the #include outside of the function */
17133 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17134 #include <sys/timeb.h>
17135 #endif
17136
17137 /* Get the current time as a TimeMark */
17138 void
17139 GetTimeMark (TimeMark *tm)
17140 {
17141 #if HAVE_GETTIMEOFDAY
17142
17143     struct timeval timeVal;
17144     struct timezone timeZone;
17145
17146     gettimeofday(&timeVal, &timeZone);
17147     tm->sec = (long) timeVal.tv_sec;
17148     tm->ms = (int) (timeVal.tv_usec / 1000L);
17149
17150 #else /*!HAVE_GETTIMEOFDAY*/
17151 #if HAVE_FTIME
17152
17153 // include <sys/timeb.h> / moved to just above start of function
17154     struct timeb timeB;
17155
17156     ftime(&timeB);
17157     tm->sec = (long) timeB.time;
17158     tm->ms = (int) timeB.millitm;
17159
17160 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17161     tm->sec = (long) time(NULL);
17162     tm->ms = 0;
17163 #endif
17164 #endif
17165 }
17166
17167 /* Return the difference in milliseconds between two
17168    time marks.  We assume the difference will fit in a long!
17169 */
17170 long
17171 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17172 {
17173     return 1000L*(tm2->sec - tm1->sec) +
17174            (long) (tm2->ms - tm1->ms);
17175 }
17176
17177
17178 /*
17179  * Code to manage the game clocks.
17180  *
17181  * In tournament play, black starts the clock and then white makes a move.
17182  * We give the human user a slight advantage if he is playing white---the
17183  * clocks don't run until he makes his first move, so it takes zero time.
17184  * Also, we don't account for network lag, so we could get out of sync
17185  * with GNU Chess's clock -- but then, referees are always right.
17186  */
17187
17188 static TimeMark tickStartTM;
17189 static long intendedTickLength;
17190
17191 long
17192 NextTickLength (long timeRemaining)
17193 {
17194     long nominalTickLength, nextTickLength;
17195
17196     if (timeRemaining > 0L && timeRemaining <= 10000L)
17197       nominalTickLength = 100L;
17198     else
17199       nominalTickLength = 1000L;
17200     nextTickLength = timeRemaining % nominalTickLength;
17201     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17202
17203     return nextTickLength;
17204 }
17205
17206 /* Adjust clock one minute up or down */
17207 void
17208 AdjustClock (Boolean which, int dir)
17209 {
17210     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17211     if(which) blackTimeRemaining += 60000*dir;
17212     else      whiteTimeRemaining += 60000*dir;
17213     DisplayBothClocks();
17214     adjustedClock = TRUE;
17215 }
17216
17217 /* Stop clocks and reset to a fresh time control */
17218 void
17219 ResetClocks ()
17220 {
17221     (void) StopClockTimer();
17222     if (appData.icsActive) {
17223         whiteTimeRemaining = blackTimeRemaining = 0;
17224     } else if (searchTime) {
17225         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17226         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17227     } else { /* [HGM] correct new time quote for time odds */
17228         whiteTC = blackTC = fullTimeControlString;
17229         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17230         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17231     }
17232     if (whiteFlag || blackFlag) {
17233         DisplayTitle("");
17234         whiteFlag = blackFlag = FALSE;
17235     }
17236     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17237     DisplayBothClocks();
17238     adjustedClock = FALSE;
17239 }
17240
17241 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17242
17243 /* Decrement running clock by amount of time that has passed */
17244 void
17245 DecrementClocks ()
17246 {
17247     long timeRemaining;
17248     long lastTickLength, fudge;
17249     TimeMark now;
17250
17251     if (!appData.clockMode) return;
17252     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17253
17254     GetTimeMark(&now);
17255
17256     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17257
17258     /* Fudge if we woke up a little too soon */
17259     fudge = intendedTickLength - lastTickLength;
17260     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17261
17262     if (WhiteOnMove(forwardMostMove)) {
17263         if(whiteNPS >= 0) lastTickLength = 0;
17264         timeRemaining = whiteTimeRemaining -= lastTickLength;
17265         if(timeRemaining < 0 && !appData.icsActive) {
17266             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17267             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17268                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17269                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17270             }
17271         }
17272         DisplayWhiteClock(whiteTimeRemaining - fudge,
17273                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17274     } else {
17275         if(blackNPS >= 0) lastTickLength = 0;
17276         timeRemaining = blackTimeRemaining -= lastTickLength;
17277         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17278             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17279             if(suddenDeath) {
17280                 blackStartMove = forwardMostMove;
17281                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17282             }
17283         }
17284         DisplayBlackClock(blackTimeRemaining - fudge,
17285                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17286     }
17287     if (CheckFlags()) return;
17288
17289     if(twoBoards) { // count down secondary board's clocks as well
17290         activePartnerTime -= lastTickLength;
17291         partnerUp = 1;
17292         if(activePartner == 'W')
17293             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17294         else
17295             DisplayBlackClock(activePartnerTime, TRUE);
17296         partnerUp = 0;
17297     }
17298
17299     tickStartTM = now;
17300     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17301     StartClockTimer(intendedTickLength);
17302
17303     /* if the time remaining has fallen below the alarm threshold, sound the
17304      * alarm. if the alarm has sounded and (due to a takeback or time control
17305      * with increment) the time remaining has increased to a level above the
17306      * threshold, reset the alarm so it can sound again.
17307      */
17308
17309     if (appData.icsActive && appData.icsAlarm) {
17310
17311         /* make sure we are dealing with the user's clock */
17312         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17313                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17314            )) return;
17315
17316         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17317             alarmSounded = FALSE;
17318         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17319             PlayAlarmSound();
17320             alarmSounded = TRUE;
17321         }
17322     }
17323 }
17324
17325
17326 /* A player has just moved, so stop the previously running
17327    clock and (if in clock mode) start the other one.
17328    We redisplay both clocks in case we're in ICS mode, because
17329    ICS gives us an update to both clocks after every move.
17330    Note that this routine is called *after* forwardMostMove
17331    is updated, so the last fractional tick must be subtracted
17332    from the color that is *not* on move now.
17333 */
17334 void
17335 SwitchClocks (int newMoveNr)
17336 {
17337     long lastTickLength;
17338     TimeMark now;
17339     int flagged = FALSE;
17340
17341     GetTimeMark(&now);
17342
17343     if (StopClockTimer() && appData.clockMode) {
17344         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17345         if (!WhiteOnMove(forwardMostMove)) {
17346             if(blackNPS >= 0) lastTickLength = 0;
17347             blackTimeRemaining -= lastTickLength;
17348            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17349 //         if(pvInfoList[forwardMostMove].time == -1)
17350                  pvInfoList[forwardMostMove].time =               // use GUI time
17351                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17352         } else {
17353            if(whiteNPS >= 0) lastTickLength = 0;
17354            whiteTimeRemaining -= lastTickLength;
17355            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17356 //         if(pvInfoList[forwardMostMove].time == -1)
17357                  pvInfoList[forwardMostMove].time =
17358                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17359         }
17360         flagged = CheckFlags();
17361     }
17362     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17363     CheckTimeControl();
17364
17365     if (flagged || !appData.clockMode) return;
17366
17367     switch (gameMode) {
17368       case MachinePlaysBlack:
17369       case MachinePlaysWhite:
17370       case BeginningOfGame:
17371         if (pausing) return;
17372         break;
17373
17374       case EditGame:
17375       case PlayFromGameFile:
17376       case IcsExamining:
17377         return;
17378
17379       default:
17380         break;
17381     }
17382
17383     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17384         if(WhiteOnMove(forwardMostMove))
17385              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17386         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17387     }
17388
17389     tickStartTM = now;
17390     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17391       whiteTimeRemaining : blackTimeRemaining);
17392     StartClockTimer(intendedTickLength);
17393 }
17394
17395
17396 /* Stop both clocks */
17397 void
17398 StopClocks ()
17399 {
17400     long lastTickLength;
17401     TimeMark now;
17402
17403     if (!StopClockTimer()) return;
17404     if (!appData.clockMode) return;
17405
17406     GetTimeMark(&now);
17407
17408     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17409     if (WhiteOnMove(forwardMostMove)) {
17410         if(whiteNPS >= 0) lastTickLength = 0;
17411         whiteTimeRemaining -= lastTickLength;
17412         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17413     } else {
17414         if(blackNPS >= 0) lastTickLength = 0;
17415         blackTimeRemaining -= lastTickLength;
17416         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17417     }
17418     CheckFlags();
17419 }
17420
17421 /* Start clock of player on move.  Time may have been reset, so
17422    if clock is already running, stop and restart it. */
17423 void
17424 StartClocks ()
17425 {
17426     (void) StopClockTimer(); /* in case it was running already */
17427     DisplayBothClocks();
17428     if (CheckFlags()) return;
17429
17430     if (!appData.clockMode) return;
17431     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17432
17433     GetTimeMark(&tickStartTM);
17434     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17435       whiteTimeRemaining : blackTimeRemaining);
17436
17437    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17438     whiteNPS = blackNPS = -1;
17439     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17440        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17441         whiteNPS = first.nps;
17442     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17443        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17444         blackNPS = first.nps;
17445     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17446         whiteNPS = second.nps;
17447     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17448         blackNPS = second.nps;
17449     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17450
17451     StartClockTimer(intendedTickLength);
17452 }
17453
17454 char *
17455 TimeString (long ms)
17456 {
17457     long second, minute, hour, day;
17458     char *sign = "";
17459     static char buf[32];
17460
17461     if (ms > 0 && ms <= 9900) {
17462       /* convert milliseconds to tenths, rounding up */
17463       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17464
17465       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17466       return buf;
17467     }
17468
17469     /* convert milliseconds to seconds, rounding up */
17470     /* use floating point to avoid strangeness of integer division
17471        with negative dividends on many machines */
17472     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17473
17474     if (second < 0) {
17475         sign = "-";
17476         second = -second;
17477     }
17478
17479     day = second / (60 * 60 * 24);
17480     second = second % (60 * 60 * 24);
17481     hour = second / (60 * 60);
17482     second = second % (60 * 60);
17483     minute = second / 60;
17484     second = second % 60;
17485
17486     if (day > 0)
17487       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17488               sign, day, hour, minute, second);
17489     else if (hour > 0)
17490       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17491     else
17492       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17493
17494     return buf;
17495 }
17496
17497
17498 /*
17499  * This is necessary because some C libraries aren't ANSI C compliant yet.
17500  */
17501 char *
17502 StrStr (char *string, char *match)
17503 {
17504     int i, length;
17505
17506     length = strlen(match);
17507
17508     for (i = strlen(string) - length; i >= 0; i--, string++)
17509       if (!strncmp(match, string, length))
17510         return string;
17511
17512     return NULL;
17513 }
17514
17515 char *
17516 StrCaseStr (char *string, char *match)
17517 {
17518     int i, j, length;
17519
17520     length = strlen(match);
17521
17522     for (i = strlen(string) - length; i >= 0; i--, string++) {
17523         for (j = 0; j < length; j++) {
17524             if (ToLower(match[j]) != ToLower(string[j]))
17525               break;
17526         }
17527         if (j == length) return string;
17528     }
17529
17530     return NULL;
17531 }
17532
17533 #ifndef _amigados
17534 int
17535 StrCaseCmp (char *s1, char *s2)
17536 {
17537     char c1, c2;
17538
17539     for (;;) {
17540         c1 = ToLower(*s1++);
17541         c2 = ToLower(*s2++);
17542         if (c1 > c2) return 1;
17543         if (c1 < c2) return -1;
17544         if (c1 == NULLCHAR) return 0;
17545     }
17546 }
17547
17548
17549 int
17550 ToLower (int c)
17551 {
17552     return isupper(c) ? tolower(c) : c;
17553 }
17554
17555
17556 int
17557 ToUpper (int c)
17558 {
17559     return islower(c) ? toupper(c) : c;
17560 }
17561 #endif /* !_amigados    */
17562
17563 char *
17564 StrSave (char *s)
17565 {
17566   char *ret;
17567
17568   if ((ret = (char *) malloc(strlen(s) + 1)))
17569     {
17570       safeStrCpy(ret, s, strlen(s)+1);
17571     }
17572   return ret;
17573 }
17574
17575 char *
17576 StrSavePtr (char *s, char **savePtr)
17577 {
17578     if (*savePtr) {
17579         free(*savePtr);
17580     }
17581     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17582       safeStrCpy(*savePtr, s, strlen(s)+1);
17583     }
17584     return(*savePtr);
17585 }
17586
17587 char *
17588 PGNDate ()
17589 {
17590     time_t clock;
17591     struct tm *tm;
17592     char buf[MSG_SIZ];
17593
17594     clock = time((time_t *)NULL);
17595     tm = localtime(&clock);
17596     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17597             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17598     return StrSave(buf);
17599 }
17600
17601
17602 char *
17603 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17604 {
17605     int i, j, fromX, fromY, toX, toY;
17606     int whiteToPlay;
17607     char buf[MSG_SIZ];
17608     char *p, *q;
17609     int emptycount;
17610     ChessSquare piece;
17611
17612     whiteToPlay = (gameMode == EditPosition) ?
17613       !blackPlaysFirst : (move % 2 == 0);
17614     p = buf;
17615
17616     /* Piece placement data */
17617     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17618         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17619         emptycount = 0;
17620         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17621             if (boards[move][i][j] == EmptySquare) {
17622                 emptycount++;
17623             } else { ChessSquare piece = boards[move][i][j];
17624                 if (emptycount > 0) {
17625                     if(emptycount<10) /* [HGM] can be >= 10 */
17626                         *p++ = '0' + emptycount;
17627                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17628                     emptycount = 0;
17629                 }
17630                 if(PieceToChar(piece) == '+') {
17631                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17632                     *p++ = '+';
17633                     piece = (ChessSquare)(DEMOTED piece);
17634                 }
17635                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17636                 if(p[-1] == '~') {
17637                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17638                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17639                     *p++ = '~';
17640                 }
17641             }
17642         }
17643         if (emptycount > 0) {
17644             if(emptycount<10) /* [HGM] can be >= 10 */
17645                 *p++ = '0' + emptycount;
17646             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17647             emptycount = 0;
17648         }
17649         *p++ = '/';
17650     }
17651     *(p - 1) = ' ';
17652
17653     /* [HGM] print Crazyhouse or Shogi holdings */
17654     if( gameInfo.holdingsWidth ) {
17655         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17656         q = p;
17657         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17658             piece = boards[move][i][BOARD_WIDTH-1];
17659             if( piece != EmptySquare )
17660               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17661                   *p++ = PieceToChar(piece);
17662         }
17663         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17664             piece = boards[move][BOARD_HEIGHT-i-1][0];
17665             if( piece != EmptySquare )
17666               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17667                   *p++ = PieceToChar(piece);
17668         }
17669
17670         if( q == p ) *p++ = '-';
17671         *p++ = ']';
17672         *p++ = ' ';
17673     }
17674
17675     /* Active color */
17676     *p++ = whiteToPlay ? 'w' : 'b';
17677     *p++ = ' ';
17678
17679   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17680     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17681   } else {
17682   if(nrCastlingRights) {
17683      q = p;
17684      if(appData.fischerCastling) {
17685        /* [HGM] write directly from rights */
17686            if(boards[move][CASTLING][2] != NoRights &&
17687               boards[move][CASTLING][0] != NoRights   )
17688                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17689            if(boards[move][CASTLING][2] != NoRights &&
17690               boards[move][CASTLING][1] != NoRights   )
17691                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17692            if(boards[move][CASTLING][5] != NoRights &&
17693               boards[move][CASTLING][3] != NoRights   )
17694                 *p++ = boards[move][CASTLING][3] + AAA;
17695            if(boards[move][CASTLING][5] != NoRights &&
17696               boards[move][CASTLING][4] != NoRights   )
17697                 *p++ = boards[move][CASTLING][4] + AAA;
17698      } else {
17699
17700         /* [HGM] write true castling rights */
17701         if( nrCastlingRights == 6 ) {
17702             int q, k=0;
17703             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17704                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17705             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17706                  boards[move][CASTLING][2] != NoRights  );
17707             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17708                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17709                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17710                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17711                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17712             }
17713             if(q) *p++ = 'Q';
17714             k = 0;
17715             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17716                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17717             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17718                  boards[move][CASTLING][5] != NoRights  );
17719             if(gameInfo.variant == VariantSChess) {
17720                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17721                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17722                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17723                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17724             }
17725             if(q) *p++ = 'q';
17726         }
17727      }
17728      if (q == p) *p++ = '-'; /* No castling rights */
17729      *p++ = ' ';
17730   }
17731
17732   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17733      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17734      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17735     /* En passant target square */
17736     if (move > backwardMostMove) {
17737         fromX = moveList[move - 1][0] - AAA;
17738         fromY = moveList[move - 1][1] - ONE;
17739         toX = moveList[move - 1][2] - AAA;
17740         toY = moveList[move - 1][3] - ONE;
17741         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17742             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17743             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17744             fromX == toX) {
17745             /* 2-square pawn move just happened */
17746             *p++ = toX + AAA;
17747             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17748         } else {
17749             *p++ = '-';
17750         }
17751     } else if(move == backwardMostMove) {
17752         // [HGM] perhaps we should always do it like this, and forget the above?
17753         if((signed char)boards[move][EP_STATUS] >= 0) {
17754             *p++ = boards[move][EP_STATUS] + AAA;
17755             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17756         } else {
17757             *p++ = '-';
17758         }
17759     } else {
17760         *p++ = '-';
17761     }
17762     *p++ = ' ';
17763   }
17764   }
17765
17766     if(moveCounts)
17767     {   int i = 0, j=move;
17768
17769         /* [HGM] find reversible plies */
17770         if (appData.debugMode) { int k;
17771             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17772             for(k=backwardMostMove; k<=forwardMostMove; k++)
17773                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17774
17775         }
17776
17777         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17778         if( j == backwardMostMove ) i += initialRulePlies;
17779         sprintf(p, "%d ", i);
17780         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17781
17782         /* Fullmove number */
17783         sprintf(p, "%d", (move / 2) + 1);
17784     } else *--p = NULLCHAR;
17785
17786     return StrSave(buf);
17787 }
17788
17789 Boolean
17790 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17791 {
17792     int i, j, k, w=0, subst=0, shuffle=0;
17793     char *p, c;
17794     int emptycount, virgin[BOARD_FILES];
17795     ChessSquare piece;
17796
17797     p = fen;
17798
17799     /* Piece placement data */
17800     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17801         j = 0;
17802         for (;;) {
17803             if (*p == '/' || *p == ' ' || *p == '[' ) {
17804                 if(j > w) w = j;
17805                 emptycount = gameInfo.boardWidth - j;
17806                 while (emptycount--)
17807                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17808                 if (*p == '/') p++;
17809                 else if(autoSize) { // we stumbled unexpectedly into end of board
17810                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17811                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17812                     }
17813                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17814                 }
17815                 break;
17816 #if(BOARD_FILES >= 10)
17817             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17818                 p++; emptycount=10;
17819                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17820                 while (emptycount--)
17821                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17822 #endif
17823             } else if (*p == '*') {
17824                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17825             } else if (isdigit(*p)) {
17826                 emptycount = *p++ - '0';
17827                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17828                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17829                 while (emptycount--)
17830                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17831             } else if (*p == '<') {
17832                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17833                 else if (i != 0 || !shuffle) return FALSE;
17834                 p++;
17835             } else if (shuffle && *p == '>') {
17836                 p++; // for now ignore closing shuffle range, and assume rank-end
17837             } else if (*p == '?') {
17838                 if (j >= gameInfo.boardWidth) return FALSE;
17839                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17840                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17841             } else if (*p == '+' || isalpha(*p)) {
17842                 if (j >= gameInfo.boardWidth) return FALSE;
17843                 if(*p=='+') {
17844                     piece = CharToPiece(*++p);
17845                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17846                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17847                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17848                 } else piece = CharToPiece(*p++);
17849
17850                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17851                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17852                     piece = (ChessSquare) (PROMOTED piece);
17853                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17854                     p++;
17855                 }
17856                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17857             } else {
17858                 return FALSE;
17859             }
17860         }
17861     }
17862     while (*p == '/' || *p == ' ') p++;
17863
17864     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17865
17866     /* [HGM] by default clear Crazyhouse holdings, if present */
17867     if(gameInfo.holdingsWidth) {
17868        for(i=0; i<BOARD_HEIGHT; i++) {
17869            board[i][0]             = EmptySquare; /* black holdings */
17870            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17871            board[i][1]             = (ChessSquare) 0; /* black counts */
17872            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17873        }
17874     }
17875
17876     /* [HGM] look for Crazyhouse holdings here */
17877     while(*p==' ') p++;
17878     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17879         int swap=0, wcnt=0, bcnt=0;
17880         if(*p == '[') p++;
17881         if(*p == '<') swap++, p++;
17882         if(*p == '-' ) p++; /* empty holdings */ else {
17883             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17884             /* if we would allow FEN reading to set board size, we would   */
17885             /* have to add holdings and shift the board read so far here   */
17886             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17887                 p++;
17888                 if((int) piece >= (int) BlackPawn ) {
17889                     i = (int)piece - (int)BlackPawn;
17890                     i = PieceToNumber((ChessSquare)i);
17891                     if( i >= gameInfo.holdingsSize ) return FALSE;
17892                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17893                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17894                     bcnt++;
17895                 } else {
17896                     i = (int)piece - (int)WhitePawn;
17897                     i = PieceToNumber((ChessSquare)i);
17898                     if( i >= gameInfo.holdingsSize ) return FALSE;
17899                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17900                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17901                     wcnt++;
17902                 }
17903             }
17904             if(subst) { // substitute back-rank question marks by holdings pieces
17905                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17906                     int k, m, n = bcnt + 1;
17907                     if(board[0][j] == ClearBoard) {
17908                         if(!wcnt) return FALSE;
17909                         n = rand() % wcnt;
17910                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17911                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17912                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17913                             break;
17914                         }
17915                     }
17916                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17917                         if(!bcnt) return FALSE;
17918                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17919                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17920                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17921                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17922                             break;
17923                         }
17924                     }
17925                 }
17926                 subst = 0;
17927             }
17928         }
17929         if(*p == ']') p++;
17930     }
17931
17932     if(subst) return FALSE; // substitution requested, but no holdings
17933
17934     while(*p == ' ') p++;
17935
17936     /* Active color */
17937     c = *p++;
17938     if(appData.colorNickNames) {
17939       if( c == appData.colorNickNames[0] ) c = 'w'; else
17940       if( c == appData.colorNickNames[1] ) c = 'b';
17941     }
17942     switch (c) {
17943       case 'w':
17944         *blackPlaysFirst = FALSE;
17945         break;
17946       case 'b':
17947         *blackPlaysFirst = TRUE;
17948         break;
17949       default:
17950         return FALSE;
17951     }
17952
17953     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17954     /* return the extra info in global variiables             */
17955
17956     /* set defaults in case FEN is incomplete */
17957     board[EP_STATUS] = EP_UNKNOWN;
17958     for(i=0; i<nrCastlingRights; i++ ) {
17959         board[CASTLING][i] =
17960             appData.fischerCastling ? NoRights : initialRights[i];
17961     }   /* assume possible unless obviously impossible */
17962     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17963     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17964     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17965                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17966     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17967     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17968     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17969                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17970     FENrulePlies = 0;
17971
17972     while(*p==' ') p++;
17973     if(nrCastlingRights) {
17974       int fischer = 0;
17975       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17976       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17977           /* castling indicator present, so default becomes no castlings */
17978           for(i=0; i<nrCastlingRights; i++ ) {
17979                  board[CASTLING][i] = NoRights;
17980           }
17981       }
17982       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17983              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17984              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17985              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17986         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17987
17988         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17989             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17990             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17991         }
17992         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17993             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17994         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17995                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17996         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17997                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17998         switch(c) {
17999           case'K':
18000               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18001               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18002               board[CASTLING][2] = whiteKingFile;
18003               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18004               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18005               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18006               break;
18007           case'Q':
18008               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18009               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18010               board[CASTLING][2] = whiteKingFile;
18011               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18012               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18013               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18014               break;
18015           case'k':
18016               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18017               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18018               board[CASTLING][5] = blackKingFile;
18019               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18020               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18021               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18022               break;
18023           case'q':
18024               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18025               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18026               board[CASTLING][5] = blackKingFile;
18027               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18028               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18029               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18030           case '-':
18031               break;
18032           default: /* FRC castlings */
18033               if(c >= 'a') { /* black rights */
18034                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18035                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18036                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18037                   if(i == BOARD_RGHT) break;
18038                   board[CASTLING][5] = i;
18039                   c -= AAA;
18040                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18041                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18042                   if(c > i)
18043                       board[CASTLING][3] = c;
18044                   else
18045                       board[CASTLING][4] = c;
18046               } else { /* white rights */
18047                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18048                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18049                     if(board[0][i] == WhiteKing) break;
18050                   if(i == BOARD_RGHT) break;
18051                   board[CASTLING][2] = i;
18052                   c -= AAA - 'a' + 'A';
18053                   if(board[0][c] >= WhiteKing) break;
18054                   if(c > i)
18055                       board[CASTLING][0] = c;
18056                   else
18057                       board[CASTLING][1] = c;
18058               }
18059         }
18060       }
18061       for(i=0; i<nrCastlingRights; i++)
18062         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18063       if(gameInfo.variant == VariantSChess)
18064         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18065       if(fischer && shuffle) appData.fischerCastling = TRUE;
18066     if (appData.debugMode) {
18067         fprintf(debugFP, "FEN castling rights:");
18068         for(i=0; i<nrCastlingRights; i++)
18069         fprintf(debugFP, " %d", board[CASTLING][i]);
18070         fprintf(debugFP, "\n");
18071     }
18072
18073       while(*p==' ') p++;
18074     }
18075
18076     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18077
18078     /* read e.p. field in games that know e.p. capture */
18079     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18080        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18081        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18082       if(*p=='-') {
18083         p++; board[EP_STATUS] = EP_NONE;
18084       } else {
18085          char c = *p++ - AAA;
18086
18087          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18088          if(*p >= '0' && *p <='9') p++;
18089          board[EP_STATUS] = c;
18090       }
18091     }
18092
18093
18094     if(sscanf(p, "%d", &i) == 1) {
18095         FENrulePlies = i; /* 50-move ply counter */
18096         /* (The move number is still ignored)    */
18097     }
18098
18099     return TRUE;
18100 }
18101
18102 void
18103 EditPositionPasteFEN (char *fen)
18104 {
18105   if (fen != NULL) {
18106     Board initial_position;
18107
18108     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18109       DisplayError(_("Bad FEN position in clipboard"), 0);
18110       return ;
18111     } else {
18112       int savedBlackPlaysFirst = blackPlaysFirst;
18113       EditPositionEvent();
18114       blackPlaysFirst = savedBlackPlaysFirst;
18115       CopyBoard(boards[0], initial_position);
18116       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18117       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18118       DisplayBothClocks();
18119       DrawPosition(FALSE, boards[currentMove]);
18120     }
18121   }
18122 }
18123
18124 static char cseq[12] = "\\   ";
18125
18126 Boolean
18127 set_cont_sequence (char *new_seq)
18128 {
18129     int len;
18130     Boolean ret;
18131
18132     // handle bad attempts to set the sequence
18133         if (!new_seq)
18134                 return 0; // acceptable error - no debug
18135
18136     len = strlen(new_seq);
18137     ret = (len > 0) && (len < sizeof(cseq));
18138     if (ret)
18139       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18140     else if (appData.debugMode)
18141       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18142     return ret;
18143 }
18144
18145 /*
18146     reformat a source message so words don't cross the width boundary.  internal
18147     newlines are not removed.  returns the wrapped size (no null character unless
18148     included in source message).  If dest is NULL, only calculate the size required
18149     for the dest buffer.  lp argument indicats line position upon entry, and it's
18150     passed back upon exit.
18151 */
18152 int
18153 wrap (char *dest, char *src, int count, int width, int *lp)
18154 {
18155     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18156
18157     cseq_len = strlen(cseq);
18158     old_line = line = *lp;
18159     ansi = len = clen = 0;
18160
18161     for (i=0; i < count; i++)
18162     {
18163         if (src[i] == '\033')
18164             ansi = 1;
18165
18166         // if we hit the width, back up
18167         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18168         {
18169             // store i & len in case the word is too long
18170             old_i = i, old_len = len;
18171
18172             // find the end of the last word
18173             while (i && src[i] != ' ' && src[i] != '\n')
18174             {
18175                 i--;
18176                 len--;
18177             }
18178
18179             // word too long?  restore i & len before splitting it
18180             if ((old_i-i+clen) >= width)
18181             {
18182                 i = old_i;
18183                 len = old_len;
18184             }
18185
18186             // extra space?
18187             if (i && src[i-1] == ' ')
18188                 len--;
18189
18190             if (src[i] != ' ' && src[i] != '\n')
18191             {
18192                 i--;
18193                 if (len)
18194                     len--;
18195             }
18196
18197             // now append the newline and continuation sequence
18198             if (dest)
18199                 dest[len] = '\n';
18200             len++;
18201             if (dest)
18202                 strncpy(dest+len, cseq, cseq_len);
18203             len += cseq_len;
18204             line = cseq_len;
18205             clen = cseq_len;
18206             continue;
18207         }
18208
18209         if (dest)
18210             dest[len] = src[i];
18211         len++;
18212         if (!ansi)
18213             line++;
18214         if (src[i] == '\n')
18215             line = 0;
18216         if (src[i] == 'm')
18217             ansi = 0;
18218     }
18219     if (dest && appData.debugMode)
18220     {
18221         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18222             count, width, line, len, *lp);
18223         show_bytes(debugFP, src, count);
18224         fprintf(debugFP, "\ndest: ");
18225         show_bytes(debugFP, dest, len);
18226         fprintf(debugFP, "\n");
18227     }
18228     *lp = dest ? line : old_line;
18229
18230     return len;
18231 }
18232
18233 // [HGM] vari: routines for shelving variations
18234 Boolean modeRestore = FALSE;
18235
18236 void
18237 PushInner (int firstMove, int lastMove)
18238 {
18239         int i, j, nrMoves = lastMove - firstMove;
18240
18241         // push current tail of game on stack
18242         savedResult[storedGames] = gameInfo.result;
18243         savedDetails[storedGames] = gameInfo.resultDetails;
18244         gameInfo.resultDetails = NULL;
18245         savedFirst[storedGames] = firstMove;
18246         savedLast [storedGames] = lastMove;
18247         savedFramePtr[storedGames] = framePtr;
18248         framePtr -= nrMoves; // reserve space for the boards
18249         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18250             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18251             for(j=0; j<MOVE_LEN; j++)
18252                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18253             for(j=0; j<2*MOVE_LEN; j++)
18254                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18255             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18256             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18257             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18258             pvInfoList[firstMove+i-1].depth = 0;
18259             commentList[framePtr+i] = commentList[firstMove+i];
18260             commentList[firstMove+i] = NULL;
18261         }
18262
18263         storedGames++;
18264         forwardMostMove = firstMove; // truncate game so we can start variation
18265 }
18266
18267 void
18268 PushTail (int firstMove, int lastMove)
18269 {
18270         if(appData.icsActive) { // only in local mode
18271                 forwardMostMove = currentMove; // mimic old ICS behavior
18272                 return;
18273         }
18274         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18275
18276         PushInner(firstMove, lastMove);
18277         if(storedGames == 1) GreyRevert(FALSE);
18278         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18279 }
18280
18281 void
18282 PopInner (Boolean annotate)
18283 {
18284         int i, j, nrMoves;
18285         char buf[8000], moveBuf[20];
18286
18287         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18288         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18289         nrMoves = savedLast[storedGames] - currentMove;
18290         if(annotate) {
18291                 int cnt = 10;
18292                 if(!WhiteOnMove(currentMove))
18293                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18294                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18295                 for(i=currentMove; i<forwardMostMove; i++) {
18296                         if(WhiteOnMove(i))
18297                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18298                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18299                         strcat(buf, moveBuf);
18300                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18301                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18302                 }
18303                 strcat(buf, ")");
18304         }
18305         for(i=1; i<=nrMoves; i++) { // copy last variation back
18306             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18307             for(j=0; j<MOVE_LEN; j++)
18308                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18309             for(j=0; j<2*MOVE_LEN; j++)
18310                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18311             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18312             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18313             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18314             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18315             commentList[currentMove+i] = commentList[framePtr+i];
18316             commentList[framePtr+i] = NULL;
18317         }
18318         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18319         framePtr = savedFramePtr[storedGames];
18320         gameInfo.result = savedResult[storedGames];
18321         if(gameInfo.resultDetails != NULL) {
18322             free(gameInfo.resultDetails);
18323       }
18324         gameInfo.resultDetails = savedDetails[storedGames];
18325         forwardMostMove = currentMove + nrMoves;
18326 }
18327
18328 Boolean
18329 PopTail (Boolean annotate)
18330 {
18331         if(appData.icsActive) return FALSE; // only in local mode
18332         if(!storedGames) return FALSE; // sanity
18333         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18334
18335         PopInner(annotate);
18336         if(currentMove < forwardMostMove) ForwardEvent(); else
18337         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18338
18339         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18340         return TRUE;
18341 }
18342
18343 void
18344 CleanupTail ()
18345 {       // remove all shelved variations
18346         int i;
18347         for(i=0; i<storedGames; i++) {
18348             if(savedDetails[i])
18349                 free(savedDetails[i]);
18350             savedDetails[i] = NULL;
18351         }
18352         for(i=framePtr; i<MAX_MOVES; i++) {
18353                 if(commentList[i]) free(commentList[i]);
18354                 commentList[i] = NULL;
18355         }
18356         framePtr = MAX_MOVES-1;
18357         storedGames = 0;
18358 }
18359
18360 void
18361 LoadVariation (int index, char *text)
18362 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18363         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18364         int level = 0, move;
18365
18366         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18367         // first find outermost bracketing variation
18368         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18369             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18370                 if(*p == '{') wait = '}'; else
18371                 if(*p == '[') wait = ']'; else
18372                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18373                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18374             }
18375             if(*p == wait) wait = NULLCHAR; // closing ]} found
18376             p++;
18377         }
18378         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18379         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18380         end[1] = NULLCHAR; // clip off comment beyond variation
18381         ToNrEvent(currentMove-1);
18382         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18383         // kludge: use ParsePV() to append variation to game
18384         move = currentMove;
18385         ParsePV(start, TRUE, TRUE);
18386         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18387         ClearPremoveHighlights();
18388         CommentPopDown();
18389         ToNrEvent(currentMove+1);
18390 }
18391
18392 void
18393 LoadTheme ()
18394 {
18395     char *p, *q, buf[MSG_SIZ];
18396     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18397         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18398         ParseArgsFromString(buf);
18399         ActivateTheme(TRUE); // also redo colors
18400         return;
18401     }
18402     p = nickName;
18403     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18404     {
18405         int len;
18406         q = appData.themeNames;
18407         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18408       if(appData.useBitmaps) {
18409         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18410                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18411                 appData.liteBackTextureMode,
18412                 appData.darkBackTextureMode );
18413       } else {
18414         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18415                 Col2Text(2),   // lightSquareColor
18416                 Col2Text(3) ); // darkSquareColor
18417       }
18418       if(appData.useBorder) {
18419         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18420                 appData.border);
18421       } else {
18422         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18423       }
18424       if(appData.useFont) {
18425         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18426                 appData.renderPiecesWithFont,
18427                 appData.fontToPieceTable,
18428                 Col2Text(9),    // appData.fontBackColorWhite
18429                 Col2Text(10) ); // appData.fontForeColorBlack
18430       } else {
18431         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18432                 appData.pieceDirectory);
18433         if(!appData.pieceDirectory[0])
18434           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18435                 Col2Text(0),   // whitePieceColor
18436                 Col2Text(1) ); // blackPieceColor
18437       }
18438       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18439                 Col2Text(4),   // highlightSquareColor
18440                 Col2Text(5) ); // premoveHighlightColor
18441         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18442         if(insert != q) insert[-1] = NULLCHAR;
18443         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18444         if(q)   free(q);
18445     }
18446     ActivateTheme(FALSE);
18447 }