Overhaul kill code
[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 "gettext.h"
152
153 #ifdef ENABLE_NLS
154 # define _(s) gettext (s)
155 # define N_(s) gettext_noop (s)
156 # define T_(s) gettext(s)
157 #else
158 # ifdef WIN32
159 #   define _(s) T_(s)
160 #   define N_(s) s
161 # else
162 #   define _(s) (s)
163 #   define N_(s) s
164 #   define T_(s) s
165 # endif
166 #endif
167
168
169 int establish P((void));
170 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
171                          char *buf, int count, int error));
172 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
173                       char *buf, int count, int error));
174 void SendToICS P((char *s));
175 void SendToICSDelayed P((char *s, long msdelay));
176 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
177 void HandleMachineMove P((char *message, ChessProgramState *cps));
178 int AutoPlayOneMove P((void));
179 int LoadGameOneMove P((ChessMove readAhead));
180 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
181 int LoadPositionFromFile P((char *filename, int n, char *title));
182 int SavePositionToFile P((char *filename));
183 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
184 void ShowMove P((int fromX, int fromY, int toX, int toY));
185 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
186                    /*char*/int promoChar));
187 void BackwardInner P((int target));
188 void ForwardInner P((int target));
189 int Adjudicate P((ChessProgramState *cps));
190 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
191 void EditPositionDone P((Boolean fakeRights));
192 void PrintOpponents P((FILE *fp));
193 void PrintPosition P((FILE *fp, int move));
194 void StartChessProgram P((ChessProgramState *cps));
195 void SendToProgram P((char *message, ChessProgramState *cps));
196 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
197 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
198                            char *buf, int count, int error));
199 void SendTimeControl P((ChessProgramState *cps,
200                         int mps, long tc, int inc, int sd, int st));
201 char *TimeControlTagValue P((void));
202 void Attention P((ChessProgramState *cps));
203 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
204 int ResurrectChessProgram P((void));
205 void DisplayComment P((int moveNumber, char *text));
206 void DisplayMove P((int moveNumber));
207
208 void ParseGameHistory P((char *game));
209 void ParseBoard12 P((char *string));
210 void KeepAlive P((void));
211 void StartClocks P((void));
212 void SwitchClocks P((int nr));
213 void StopClocks P((void));
214 void ResetClocks P((void));
215 char *PGNDate P((void));
216 void SetGameInfo P((void));
217 int RegisterMove P((void));
218 void MakeRegisteredMove P((void));
219 void TruncateGame P((void));
220 int looking_at P((char *, int *, char *));
221 void CopyPlayerNameIntoFileName P((char **, char *));
222 char *SavePart P((char *));
223 int SaveGameOldStyle P((FILE *));
224 int SaveGamePGN P((FILE *));
225 int CheckFlags P((void));
226 long NextTickLength P((long));
227 void CheckTimeControl P((void));
228 void show_bytes P((FILE *, char *, int));
229 int string_to_rating P((char *str));
230 void ParseFeatures P((char* args, ChessProgramState *cps));
231 void InitBackEnd3 P((void));
232 void FeatureDone P((ChessProgramState* cps, int val));
233 void InitChessProgram P((ChessProgramState *cps, int setup));
234 void OutputKibitz(int window, char *text);
235 int PerpetualChase(int first, int last);
236 int EngineOutputIsUp();
237 void InitDrawingSizes(int x, int y);
238 void NextMatchGame P((void));
239 int NextTourneyGame P((int nr, int *swap));
240 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
241 FILE *WriteTourneyFile P((char *results, FILE *f));
242 void DisplayTwoMachinesTitle P(());
243 static void ExcludeClick P((int index));
244 void ToggleSecond P((void));
245 void PauseEngine P((ChessProgramState *cps));
246 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
247
248 #ifdef WIN32
249        extern void ConsoleCreate();
250 #endif
251
252 ChessProgramState *WhitePlayer();
253 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
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 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
297
298 /* States for ics_getting_history */
299 #define H_FALSE 0
300 #define H_REQUESTED 1
301 #define H_GOT_REQ_HEADER 2
302 #define H_GOT_UNREQ_HEADER 3
303 #define H_GETTING_MOVES 4
304 #define H_GOT_UNWANTED_HEADER 5
305
306 /* whosays values for GameEnds */
307 #define GE_ICS 0
308 #define GE_ENGINE 1
309 #define GE_PLAYER 2
310 #define GE_FILE 3
311 #define GE_XBOARD 4
312 #define GE_ENGINE1 5
313 #define GE_ENGINE2 6
314
315 /* Maximum number of games in a cmail message */
316 #define CMAIL_MAX_GAMES 20
317
318 /* Different types of move when calling RegisterMove */
319 #define CMAIL_MOVE   0
320 #define CMAIL_RESIGN 1
321 #define CMAIL_DRAW   2
322 #define CMAIL_ACCEPT 3
323
324 /* Different types of result to remember for each game */
325 #define CMAIL_NOT_RESULT 0
326 #define CMAIL_OLD_RESULT 1
327 #define CMAIL_NEW_RESULT 2
328
329 /* Telnet protocol constants */
330 #define TN_WILL 0373
331 #define TN_WONT 0374
332 #define TN_DO   0375
333 #define TN_DONT 0376
334 #define TN_IAC  0377
335 #define TN_ECHO 0001
336 #define TN_SGA  0003
337 #define TN_PORT 23
338
339 char*
340 safeStrCpy (char *dst, const char *src, size_t count)
341 { // [HGM] made safe
342   int i;
343   assert( dst != NULL );
344   assert( src != NULL );
345   assert( count > 0 );
346
347   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
348   if(  i == count && dst[count-1] != NULLCHAR)
349     {
350       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
351       if(appData.debugMode)
352         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
353     }
354
355   return dst;
356 }
357
358 /* Some compiler can't cast u64 to double
359  * This function do the job for us:
360
361  * We use the highest bit for cast, this only
362  * works if the highest bit is not
363  * in use (This should not happen)
364  *
365  * We used this for all compiler
366  */
367 double
368 u64ToDouble (u64 value)
369 {
370   double r;
371   u64 tmp = value & u64Const(0x7fffffffffffffff);
372   r = (double)(s64)tmp;
373   if (value & u64Const(0x8000000000000000))
374        r +=  9.2233720368547758080e18; /* 2^63 */
375  return r;
376 }
377
378 /* Fake up flags for now, as we aren't keeping track of castling
379    availability yet. [HGM] Change of logic: the flag now only
380    indicates the type of castlings allowed by the rule of the game.
381    The actual rights themselves are maintained in the array
382    castlingRights, as part of the game history, and are not probed
383    by this function.
384  */
385 int
386 PosFlags (index)
387 {
388   int flags = F_ALL_CASTLE_OK;
389   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
390   switch (gameInfo.variant) {
391   case VariantSuicide:
392     flags &= ~F_ALL_CASTLE_OK;
393   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
394     flags |= F_IGNORE_CHECK;
395   case VariantLosers:
396     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
397     break;
398   case VariantAtomic:
399     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
400     break;
401   case VariantKriegspiel:
402     flags |= F_KRIEGSPIEL_CAPTURE;
403     break;
404   case VariantCapaRandom:
405   case VariantFischeRandom:
406     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
407   case VariantNoCastle:
408   case VariantShatranj:
409   case VariantCourier:
410   case VariantMakruk:
411   case VariantASEAN:
412   case VariantGrand:
413     flags &= ~F_ALL_CASTLE_OK;
414     break;
415   default:
416     break;
417   }
418   return flags;
419 }
420
421 FILE *gameFileFP, *debugFP, *serverFP;
422 char *currentDebugFile; // [HGM] debug split: to remember name
423
424 /*
425     [AS] Note: sometimes, the sscanf() function is used to parse the input
426     into a fixed-size buffer. Because of this, we must be prepared to
427     receive strings as long as the size of the input buffer, which is currently
428     set to 4K for Windows and 8K for the rest.
429     So, we must either allocate sufficiently large buffers here, or
430     reduce the size of the input buffer in the input reading part.
431 */
432
433 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
434 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
435 char thinkOutput1[MSG_SIZ*10];
436
437 ChessProgramState first, second, pairing;
438
439 /* premove variables */
440 int premoveToX = 0;
441 int premoveToY = 0;
442 int premoveFromX = 0;
443 int premoveFromY = 0;
444 int premovePromoChar = 0;
445 int gotPremove = 0;
446 Boolean alarmSounded;
447 /* end premove variables */
448
449 char *ics_prefix = "$";
450 enum ICS_TYPE ics_type = ICS_GENERIC;
451
452 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
453 int pauseExamForwardMostMove = 0;
454 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
455 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
456 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
457 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
458 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
459 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
460 int whiteFlag = FALSE, blackFlag = FALSE;
461 int userOfferedDraw = FALSE;
462 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
463 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
464 int cmailMoveType[CMAIL_MAX_GAMES];
465 long ics_clock_paused = 0;
466 ProcRef icsPR = NoProc, cmailPR = NoProc;
467 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
468 GameMode gameMode = BeginningOfGame;
469 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
470 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
471 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
472 int hiddenThinkOutputState = 0; /* [AS] */
473 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
474 int adjudicateLossPlies = 6;
475 char white_holding[64], black_holding[64];
476 TimeMark lastNodeCountTime;
477 long lastNodeCount=0;
478 int shiftKey, controlKey; // [HGM] set by mouse handler
479
480 int have_sent_ICS_logon = 0;
481 int movesPerSession;
482 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
483 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
484 Boolean adjustedClock;
485 long timeControl_2; /* [AS] Allow separate time controls */
486 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
487 long timeRemaining[2][MAX_MOVES];
488 int matchGame = 0, nextGame = 0, roundNr = 0;
489 Boolean waitingForGame = FALSE, startingEngine = FALSE;
490 TimeMark programStartTime, pauseStart;
491 char ics_handle[MSG_SIZ];
492 int have_set_title = 0;
493
494 /* animateTraining preserves the state of appData.animate
495  * when Training mode is activated. This allows the
496  * response to be animated when appData.animate == TRUE and
497  * appData.animateDragging == TRUE.
498  */
499 Boolean animateTraining;
500
501 GameInfo gameInfo;
502
503 AppData appData;
504
505 Board boards[MAX_MOVES];
506 /* [HGM] Following 7 needed for accurate legality tests: */
507 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
508 signed char  initialRights[BOARD_FILES];
509 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
510 int   initialRulePlies, FENrulePlies;
511 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
512 int loadFlag = 0;
513 Boolean shuffleOpenings;
514 int mute; // mute all sounds
515
516 // [HGM] vari: next 12 to save and restore variations
517 #define MAX_VARIATIONS 10
518 int framePtr = MAX_MOVES-1; // points to free stack entry
519 int storedGames = 0;
520 int savedFirst[MAX_VARIATIONS];
521 int savedLast[MAX_VARIATIONS];
522 int savedFramePtr[MAX_VARIATIONS];
523 char *savedDetails[MAX_VARIATIONS];
524 ChessMove savedResult[MAX_VARIATIONS];
525
526 void PushTail P((int firstMove, int lastMove));
527 Boolean PopTail P((Boolean annotate));
528 void PushInner P((int firstMove, int lastMove));
529 void PopInner P((Boolean annotate));
530 void CleanupTail P((void));
531
532 ChessSquare  FIDEArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
536         BlackKing, BlackBishop, BlackKnight, BlackRook }
537 };
538
539 ChessSquare twoKingsArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
543         BlackKing, BlackKing, BlackKnight, BlackRook }
544 };
545
546 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
548         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
549     { BlackRook, BlackMan, BlackBishop, BlackQueen,
550         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
551 };
552
553 ChessSquare SpartanArray[2][BOARD_FILES] = {
554     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
555         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
556     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
557         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
558 };
559
560 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
561     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
562         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
563     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
564         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
565 };
566
567 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
568     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
569         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
571         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
572 };
573
574 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
575     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
576         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
577     { BlackRook, BlackKnight, BlackMan, BlackFerz,
578         BlackKing, BlackMan, BlackKnight, BlackRook }
579 };
580
581 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
582     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
583         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
584     { BlackRook, BlackKnight, BlackMan, BlackFerz,
585         BlackKing, BlackMan, BlackKnight, BlackRook }
586 };
587
588 ChessSquare  lionArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
590         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
591     { BlackRook, BlackLion, BlackBishop, BlackQueen,
592         BlackKing, BlackBishop, BlackKnight, BlackRook }
593 };
594
595
596 #if (BOARD_FILES>=10)
597 ChessSquare ShogiArray[2][BOARD_FILES] = {
598     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
599         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
600     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
601         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
602 };
603
604 ChessSquare XiangqiArray[2][BOARD_FILES] = {
605     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
606         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
607     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
608         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
609 };
610
611 ChessSquare CapablancaArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
613         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
615         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
616 };
617
618 ChessSquare GreatArray[2][BOARD_FILES] = {
619     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
620         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
621     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
622         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
623 };
624
625 ChessSquare JanusArray[2][BOARD_FILES] = {
626     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
627         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
628     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
629         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
630 };
631
632 ChessSquare GrandArray[2][BOARD_FILES] = {
633     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
634         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
635     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
636         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
637 };
638
639 ChessSquare ChuChessArray[2][BOARD_FILES] = {
640     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
641         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
642     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
643         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
644 };
645
646 #ifdef GOTHIC
647 ChessSquare GothicArray[2][BOARD_FILES] = {
648     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
649         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
650     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
651         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
652 };
653 #else // !GOTHIC
654 #define GothicArray CapablancaArray
655 #endif // !GOTHIC
656
657 #ifdef FALCON
658 ChessSquare FalconArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
660         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
662         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !FALCON
665 #define FalconArray CapablancaArray
666 #endif // !FALCON
667
668 #else // !(BOARD_FILES>=10)
669 #define XiangqiPosition FIDEArray
670 #define CapablancaArray FIDEArray
671 #define GothicArray FIDEArray
672 #define GreatArray FIDEArray
673 #endif // !(BOARD_FILES>=10)
674
675 #if (BOARD_FILES>=12)
676 ChessSquare CourierArray[2][BOARD_FILES] = {
677     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
678         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
679     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
680         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
681 };
682 ChessSquare ChuArray[6][BOARD_FILES] = {
683     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
684       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
685     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
686       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
687     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
688       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
689     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
690       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
691     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
692       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
693     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
694       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
695 };
696 #else // !(BOARD_FILES>=12)
697 #define CourierArray CapablancaArray
698 #define ChuArray CapablancaArray
699 #endif // !(BOARD_FILES>=12)
700
701
702 Board initialPosition;
703
704
705 /* Convert str to a rating. Checks for special cases of "----",
706
707    "++++", etc. Also strips ()'s */
708 int
709 string_to_rating (char *str)
710 {
711   while(*str && !isdigit(*str)) ++str;
712   if (!*str)
713     return 0;   /* One of the special "no rating" cases */
714   else
715     return atoi(str);
716 }
717
718 void
719 ClearProgramStats ()
720 {
721     /* Init programStats */
722     programStats.movelist[0] = 0;
723     programStats.depth = 0;
724     programStats.nr_moves = 0;
725     programStats.moves_left = 0;
726     programStats.nodes = 0;
727     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
728     programStats.score = 0;
729     programStats.got_only_move = 0;
730     programStats.got_fail = 0;
731     programStats.line_is_book = 0;
732 }
733
734 void
735 CommonEngineInit ()
736 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
737     if (appData.firstPlaysBlack) {
738         first.twoMachinesColor = "black\n";
739         second.twoMachinesColor = "white\n";
740     } else {
741         first.twoMachinesColor = "white\n";
742         second.twoMachinesColor = "black\n";
743     }
744
745     first.other = &second;
746     second.other = &first;
747
748     { float norm = 1;
749         if(appData.timeOddsMode) {
750             norm = appData.timeOdds[0];
751             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
752         }
753         first.timeOdds  = appData.timeOdds[0]/norm;
754         second.timeOdds = appData.timeOdds[1]/norm;
755     }
756
757     if(programVersion) free(programVersion);
758     if (appData.noChessProgram) {
759         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
760         sprintf(programVersion, "%s", PACKAGE_STRING);
761     } else {
762       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
763       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
764       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
765     }
766 }
767
768 void
769 UnloadEngine (ChessProgramState *cps)
770 {
771         /* Kill off first chess program */
772         if (cps->isr != NULL)
773           RemoveInputSource(cps->isr);
774         cps->isr = NULL;
775
776         if (cps->pr != NoProc) {
777             ExitAnalyzeMode();
778             DoSleep( appData.delayBeforeQuit );
779             SendToProgram("quit\n", cps);
780             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
781         }
782         cps->pr = NoProc;
783         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
784 }
785
786 void
787 ClearOptions (ChessProgramState *cps)
788 {
789     int i;
790     cps->nrOptions = cps->comboCnt = 0;
791     for(i=0; i<MAX_OPTIONS; i++) {
792         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
793         cps->option[i].textValue = 0;
794     }
795 }
796
797 char *engineNames[] = {
798   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
799      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
800 N_("first"),
801   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
802      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
803 N_("second")
804 };
805
806 void
807 InitEngine (ChessProgramState *cps, int n)
808 {   // [HGM] all engine initialiation put in a function that does one engine
809
810     ClearOptions(cps);
811
812     cps->which = engineNames[n];
813     cps->maybeThinking = FALSE;
814     cps->pr = NoProc;
815     cps->isr = NULL;
816     cps->sendTime = 2;
817     cps->sendDrawOffers = 1;
818
819     cps->program = appData.chessProgram[n];
820     cps->host = appData.host[n];
821     cps->dir = appData.directory[n];
822     cps->initString = appData.engInitString[n];
823     cps->computerString = appData.computerString[n];
824     cps->useSigint  = TRUE;
825     cps->useSigterm = TRUE;
826     cps->reuse = appData.reuse[n];
827     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
828     cps->useSetboard = FALSE;
829     cps->useSAN = FALSE;
830     cps->usePing = FALSE;
831     cps->lastPing = 0;
832     cps->lastPong = 0;
833     cps->usePlayother = FALSE;
834     cps->useColors = TRUE;
835     cps->useUsermove = FALSE;
836     cps->sendICS = FALSE;
837     cps->sendName = appData.icsActive;
838     cps->sdKludge = FALSE;
839     cps->stKludge = FALSE;
840     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
841     TidyProgramName(cps->program, cps->host, cps->tidy);
842     cps->matchWins = 0;
843     ASSIGN(cps->variants, appData.variant);
844     cps->analysisSupport = 2; /* detect */
845     cps->analyzing = FALSE;
846     cps->initDone = FALSE;
847     cps->reload = FALSE;
848
849     /* New features added by Tord: */
850     cps->useFEN960 = FALSE;
851     cps->useOOCastle = TRUE;
852     /* End of new features added by Tord. */
853     cps->fenOverride  = appData.fenOverride[n];
854
855     /* [HGM] time odds: set factor for each machine */
856     cps->timeOdds  = appData.timeOdds[n];
857
858     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
859     cps->accumulateTC = appData.accumulateTC[n];
860     cps->maxNrOfSessions = 1;
861
862     /* [HGM] debug */
863     cps->debug = FALSE;
864
865     cps->drawDepth = appData.drawDepth[n];
866     cps->supportsNPS = UNKNOWN;
867     cps->memSize = FALSE;
868     cps->maxCores = FALSE;
869     ASSIGN(cps->egtFormats, "");
870
871     /* [HGM] options */
872     cps->optionSettings  = appData.engOptions[n];
873
874     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
875     cps->isUCI = appData.isUCI[n]; /* [AS] */
876     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
877     cps->highlight = 0;
878
879     if (appData.protocolVersion[n] > PROTOVER
880         || appData.protocolVersion[n] < 1)
881       {
882         char buf[MSG_SIZ];
883         int len;
884
885         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
886                        appData.protocolVersion[n]);
887         if( (len >= MSG_SIZ) && appData.debugMode )
888           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
889
890         DisplayFatalError(buf, 0, 2);
891       }
892     else
893       {
894         cps->protocolVersion = appData.protocolVersion[n];
895       }
896
897     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
898     ParseFeatures(appData.featureDefaults, cps);
899 }
900
901 ChessProgramState *savCps;
902
903 GameMode oldMode;
904
905 void
906 LoadEngine ()
907 {
908     int i;
909     if(WaitForEngine(savCps, LoadEngine)) return;
910     CommonEngineInit(); // recalculate time odds
911     if(gameInfo.variant != StringToVariant(appData.variant)) {
912         // we changed variant when loading the engine; this forces us to reset
913         Reset(TRUE, savCps != &first);
914         oldMode = BeginningOfGame; // to prevent restoring old mode
915     }
916     InitChessProgram(savCps, FALSE);
917     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
918     DisplayMessage("", "");
919     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
920     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
921     ThawUI();
922     SetGNUMode();
923     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
924 }
925
926 void
927 ReplaceEngine (ChessProgramState *cps, int n)
928 {
929     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
930     keepInfo = 1;
931     if(oldMode != BeginningOfGame) EditGameEvent();
932     keepInfo = 0;
933     UnloadEngine(cps);
934     appData.noChessProgram = FALSE;
935     appData.clockMode = TRUE;
936     InitEngine(cps, n);
937     UpdateLogos(TRUE);
938     if(n) return; // only startup first engine immediately; second can wait
939     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
940     LoadEngine();
941 }
942
943 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
944 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
945
946 static char resetOptions[] =
947         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
948         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
949         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
950         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
951
952 void
953 FloatToFront(char **list, char *engineLine)
954 {
955     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
956     int i=0;
957     if(appData.recentEngines <= 0) return;
958     TidyProgramName(engineLine, "localhost", tidy+1);
959     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
960     strncpy(buf+1, *list, MSG_SIZ-50);
961     if(p = strstr(buf, tidy)) { // tidy name appears in list
962         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
963         while(*p++ = *++q); // squeeze out
964     }
965     strcat(tidy, buf+1); // put list behind tidy name
966     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
967     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
968     ASSIGN(*list, tidy+1);
969 }
970
971 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
972
973 void
974 Load (ChessProgramState *cps, int i)
975 {
976     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
977     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
978         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
979         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
980         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
981         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
982         appData.firstProtocolVersion = PROTOVER;
983         ParseArgsFromString(buf);
984         SwapEngines(i);
985         ReplaceEngine(cps, i);
986         FloatToFront(&appData.recentEngineList, engineLine);
987         return;
988     }
989     p = engineName;
990     while(q = strchr(p, SLASH)) p = q+1;
991     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
992     if(engineDir[0] != NULLCHAR) {
993         ASSIGN(appData.directory[i], engineDir); p = engineName;
994     } else if(p != engineName) { // derive directory from engine path, when not given
995         p[-1] = 0;
996         ASSIGN(appData.directory[i], engineName);
997         p[-1] = SLASH;
998         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
999     } else { ASSIGN(appData.directory[i], "."); }
1000     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1001     if(params[0]) {
1002         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1003         snprintf(command, MSG_SIZ, "%s %s", p, params);
1004         p = command;
1005     }
1006     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1007     ASSIGN(appData.chessProgram[i], p);
1008     appData.isUCI[i] = isUCI;
1009     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1010     appData.hasOwnBookUCI[i] = hasBook;
1011     if(!nickName[0]) useNick = FALSE;
1012     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1013     if(addToList) {
1014         int len;
1015         char quote;
1016         q = firstChessProgramNames;
1017         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1018         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1019         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1020                         quote, p, quote, appData.directory[i],
1021                         useNick ? " -fn \"" : "",
1022                         useNick ? nickName : "",
1023                         useNick ? "\"" : "",
1024                         v1 ? " -firstProtocolVersion 1" : "",
1025                         hasBook ? "" : " -fNoOwnBookUCI",
1026                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1027                         storeVariant ? " -variant " : "",
1028                         storeVariant ? VariantName(gameInfo.variant) : "");
1029         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1030         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1031         if(insert != q) insert[-1] = NULLCHAR;
1032         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1033         if(q)   free(q);
1034         FloatToFront(&appData.recentEngineList, buf);
1035     }
1036     ReplaceEngine(cps, i);
1037 }
1038
1039 void
1040 InitTimeControls ()
1041 {
1042     int matched, min, sec;
1043     /*
1044      * Parse timeControl resource
1045      */
1046     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1047                           appData.movesPerSession)) {
1048         char buf[MSG_SIZ];
1049         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1050         DisplayFatalError(buf, 0, 2);
1051     }
1052
1053     /*
1054      * Parse searchTime resource
1055      */
1056     if (*appData.searchTime != NULLCHAR) {
1057         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1058         if (matched == 1) {
1059             searchTime = min * 60;
1060         } else if (matched == 2) {
1061             searchTime = min * 60 + sec;
1062         } else {
1063             char buf[MSG_SIZ];
1064             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1065             DisplayFatalError(buf, 0, 2);
1066         }
1067     }
1068 }
1069
1070 void
1071 InitBackEnd1 ()
1072 {
1073
1074     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1075     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1076
1077     GetTimeMark(&programStartTime);
1078     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1079     appData.seedBase = random() + (random()<<15);
1080     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1081
1082     ClearProgramStats();
1083     programStats.ok_to_send = 1;
1084     programStats.seen_stat = 0;
1085
1086     /*
1087      * Initialize game list
1088      */
1089     ListNew(&gameList);
1090
1091
1092     /*
1093      * Internet chess server status
1094      */
1095     if (appData.icsActive) {
1096         appData.matchMode = FALSE;
1097         appData.matchGames = 0;
1098 #if ZIPPY
1099         appData.noChessProgram = !appData.zippyPlay;
1100 #else
1101         appData.zippyPlay = FALSE;
1102         appData.zippyTalk = FALSE;
1103         appData.noChessProgram = TRUE;
1104 #endif
1105         if (*appData.icsHelper != NULLCHAR) {
1106             appData.useTelnet = TRUE;
1107             appData.telnetProgram = appData.icsHelper;
1108         }
1109     } else {
1110         appData.zippyTalk = appData.zippyPlay = FALSE;
1111     }
1112
1113     /* [AS] Initialize pv info list [HGM] and game state */
1114     {
1115         int i, j;
1116
1117         for( i=0; i<=framePtr; i++ ) {
1118             pvInfoList[i].depth = -1;
1119             boards[i][EP_STATUS] = EP_NONE;
1120             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1121         }
1122     }
1123
1124     InitTimeControls();
1125
1126     /* [AS] Adjudication threshold */
1127     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1128
1129     InitEngine(&first, 0);
1130     InitEngine(&second, 1);
1131     CommonEngineInit();
1132
1133     pairing.which = "pairing"; // pairing engine
1134     pairing.pr = NoProc;
1135     pairing.isr = NULL;
1136     pairing.program = appData.pairingEngine;
1137     pairing.host = "localhost";
1138     pairing.dir = ".";
1139
1140     if (appData.icsActive) {
1141         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1142     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1143         appData.clockMode = FALSE;
1144         first.sendTime = second.sendTime = 0;
1145     }
1146
1147 #if ZIPPY
1148     /* Override some settings from environment variables, for backward
1149        compatibility.  Unfortunately it's not feasible to have the env
1150        vars just set defaults, at least in xboard.  Ugh.
1151     */
1152     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1153       ZippyInit();
1154     }
1155 #endif
1156
1157     if (!appData.icsActive) {
1158       char buf[MSG_SIZ];
1159       int len;
1160
1161       /* Check for variants that are supported only in ICS mode,
1162          or not at all.  Some that are accepted here nevertheless
1163          have bugs; see comments below.
1164       */
1165       VariantClass variant = StringToVariant(appData.variant);
1166       switch (variant) {
1167       case VariantBughouse:     /* need four players and two boards */
1168       case VariantKriegspiel:   /* need to hide pieces and move details */
1169         /* case VariantFischeRandom: (Fabien: moved below) */
1170         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1171         if( (len >= MSG_SIZ) && appData.debugMode )
1172           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1173
1174         DisplayFatalError(buf, 0, 2);
1175         return;
1176
1177       case VariantUnknown:
1178       case VariantLoadable:
1179       case Variant29:
1180       case Variant30:
1181       case Variant31:
1182       case Variant32:
1183       case Variant33:
1184       case Variant34:
1185       case Variant35:
1186       case Variant36:
1187       default:
1188         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1189         if( (len >= MSG_SIZ) && appData.debugMode )
1190           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1191
1192         DisplayFatalError(buf, 0, 2);
1193         return;
1194
1195       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1196       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1197       case VariantGothic:     /* [HGM] should work */
1198       case VariantCapablanca: /* [HGM] should work */
1199       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1200       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1201       case VariantChu:        /* [HGM] experimental */
1202       case VariantKnightmate: /* [HGM] should work */
1203       case VariantCylinder:   /* [HGM] untested */
1204       case VariantFalcon:     /* [HGM] untested */
1205       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1206                                  offboard interposition not understood */
1207       case VariantNormal:     /* definitely works! */
1208       case VariantWildCastle: /* pieces not automatically shuffled */
1209       case VariantNoCastle:   /* pieces not automatically shuffled */
1210       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1211       case VariantLosers:     /* should work except for win condition,
1212                                  and doesn't know captures are mandatory */
1213       case VariantSuicide:    /* should work except for win condition,
1214                                  and doesn't know captures are mandatory */
1215       case VariantGiveaway:   /* should work except for win condition,
1216                                  and doesn't know captures are mandatory */
1217       case VariantTwoKings:   /* should work */
1218       case VariantAtomic:     /* should work except for win condition */
1219       case Variant3Check:     /* should work except for win condition */
1220       case VariantShatranj:   /* should work except for all win conditions */
1221       case VariantMakruk:     /* should work except for draw countdown */
1222       case VariantASEAN :     /* should work except for draw countdown */
1223       case VariantBerolina:   /* might work if TestLegality is off */
1224       case VariantCapaRandom: /* should work */
1225       case VariantJanus:      /* should work */
1226       case VariantSuper:      /* experimental */
1227       case VariantGreat:      /* experimental, requires legality testing to be off */
1228       case VariantSChess:     /* S-Chess, should work */
1229       case VariantGrand:      /* should work */
1230       case VariantSpartan:    /* should work */
1231       case VariantLion:       /* should work */
1232       case VariantChuChess:   /* should work */
1233         break;
1234       }
1235     }
1236
1237 }
1238
1239 int
1240 NextIntegerFromString (char ** str, long * value)
1241 {
1242     int result = -1;
1243     char * s = *str;
1244
1245     while( *s == ' ' || *s == '\t' ) {
1246         s++;
1247     }
1248
1249     *value = 0;
1250
1251     if( *s >= '0' && *s <= '9' ) {
1252         while( *s >= '0' && *s <= '9' ) {
1253             *value = *value * 10 + (*s - '0');
1254             s++;
1255         }
1256
1257         result = 0;
1258     }
1259
1260     *str = s;
1261
1262     return result;
1263 }
1264
1265 int
1266 NextTimeControlFromString (char ** str, long * value)
1267 {
1268     long temp;
1269     int result = NextIntegerFromString( str, &temp );
1270
1271     if( result == 0 ) {
1272         *value = temp * 60; /* Minutes */
1273         if( **str == ':' ) {
1274             (*str)++;
1275             result = NextIntegerFromString( str, &temp );
1276             *value += temp; /* Seconds */
1277         }
1278     }
1279
1280     return result;
1281 }
1282
1283 int
1284 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1285 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1286     int result = -1, type = 0; long temp, temp2;
1287
1288     if(**str != ':') return -1; // old params remain in force!
1289     (*str)++;
1290     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1291     if( NextIntegerFromString( str, &temp ) ) return -1;
1292     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1293
1294     if(**str != '/') {
1295         /* time only: incremental or sudden-death time control */
1296         if(**str == '+') { /* increment follows; read it */
1297             (*str)++;
1298             if(**str == '!') type = *(*str)++; // Bronstein TC
1299             if(result = NextIntegerFromString( str, &temp2)) return -1;
1300             *inc = temp2 * 1000;
1301             if(**str == '.') { // read fraction of increment
1302                 char *start = ++(*str);
1303                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1304                 temp2 *= 1000;
1305                 while(start++ < *str) temp2 /= 10;
1306                 *inc += temp2;
1307             }
1308         } else *inc = 0;
1309         *moves = 0; *tc = temp * 1000; *incType = type;
1310         return 0;
1311     }
1312
1313     (*str)++; /* classical time control */
1314     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1315
1316     if(result == 0) {
1317         *moves = temp;
1318         *tc    = temp2 * 1000;
1319         *inc   = 0;
1320         *incType = type;
1321     }
1322     return result;
1323 }
1324
1325 int
1326 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1327 {   /* [HGM] get time to add from the multi-session time-control string */
1328     int incType, moves=1; /* kludge to force reading of first session */
1329     long time, increment;
1330     char *s = tcString;
1331
1332     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1333     do {
1334         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1335         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1336         if(movenr == -1) return time;    /* last move before new session     */
1337         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1338         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1339         if(!moves) return increment;     /* current session is incremental   */
1340         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1341     } while(movenr >= -1);               /* try again for next session       */
1342
1343     return 0; // no new time quota on this move
1344 }
1345
1346 int
1347 ParseTimeControl (char *tc, float ti, int mps)
1348 {
1349   long tc1;
1350   long tc2;
1351   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1352   int min, sec=0;
1353
1354   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1355   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1356       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1357   if(ti > 0) {
1358
1359     if(mps)
1360       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1361     else
1362       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1363   } else {
1364     if(mps)
1365       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1366     else
1367       snprintf(buf, MSG_SIZ, ":%s", mytc);
1368   }
1369   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1370
1371   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1372     return FALSE;
1373   }
1374
1375   if( *tc == '/' ) {
1376     /* Parse second time control */
1377     tc++;
1378
1379     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1380       return FALSE;
1381     }
1382
1383     if( tc2 == 0 ) {
1384       return FALSE;
1385     }
1386
1387     timeControl_2 = tc2 * 1000;
1388   }
1389   else {
1390     timeControl_2 = 0;
1391   }
1392
1393   if( tc1 == 0 ) {
1394     return FALSE;
1395   }
1396
1397   timeControl = tc1 * 1000;
1398
1399   if (ti >= 0) {
1400     timeIncrement = ti * 1000;  /* convert to ms */
1401     movesPerSession = 0;
1402   } else {
1403     timeIncrement = 0;
1404     movesPerSession = mps;
1405   }
1406   return TRUE;
1407 }
1408
1409 void
1410 InitBackEnd2 ()
1411 {
1412     if (appData.debugMode) {
1413 #    ifdef __GIT_VERSION
1414       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1415 #    else
1416       fprintf(debugFP, "Version: %s\n", programVersion);
1417 #    endif
1418     }
1419     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1420
1421     set_cont_sequence(appData.wrapContSeq);
1422     if (appData.matchGames > 0) {
1423         appData.matchMode = TRUE;
1424     } else if (appData.matchMode) {
1425         appData.matchGames = 1;
1426     }
1427     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1428         appData.matchGames = appData.sameColorGames;
1429     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1430         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1431         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1432     }
1433     Reset(TRUE, FALSE);
1434     if (appData.noChessProgram || first.protocolVersion == 1) {
1435       InitBackEnd3();
1436     } else {
1437       /* kludge: allow timeout for initial "feature" commands */
1438       FreezeUI();
1439       DisplayMessage("", _("Starting chess program"));
1440       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1441     }
1442 }
1443
1444 int
1445 CalculateIndex (int index, int gameNr)
1446 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1447     int res;
1448     if(index > 0) return index; // fixed nmber
1449     if(index == 0) return 1;
1450     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1451     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1452     return res;
1453 }
1454
1455 int
1456 LoadGameOrPosition (int gameNr)
1457 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1458     if (*appData.loadGameFile != NULLCHAR) {
1459         if (!LoadGameFromFile(appData.loadGameFile,
1460                 CalculateIndex(appData.loadGameIndex, gameNr),
1461                               appData.loadGameFile, FALSE)) {
1462             DisplayFatalError(_("Bad game file"), 0, 1);
1463             return 0;
1464         }
1465     } else if (*appData.loadPositionFile != NULLCHAR) {
1466         if (!LoadPositionFromFile(appData.loadPositionFile,
1467                 CalculateIndex(appData.loadPositionIndex, gameNr),
1468                                   appData.loadPositionFile)) {
1469             DisplayFatalError(_("Bad position file"), 0, 1);
1470             return 0;
1471         }
1472     }
1473     return 1;
1474 }
1475
1476 void
1477 ReserveGame (int gameNr, char resChar)
1478 {
1479     FILE *tf = fopen(appData.tourneyFile, "r+");
1480     char *p, *q, c, buf[MSG_SIZ];
1481     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1482     safeStrCpy(buf, lastMsg, MSG_SIZ);
1483     DisplayMessage(_("Pick new game"), "");
1484     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1485     ParseArgsFromFile(tf);
1486     p = q = appData.results;
1487     if(appData.debugMode) {
1488       char *r = appData.participants;
1489       fprintf(debugFP, "results = '%s'\n", p);
1490       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1491       fprintf(debugFP, "\n");
1492     }
1493     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1494     nextGame = q - p;
1495     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1496     safeStrCpy(q, p, strlen(p) + 2);
1497     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1498     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1499     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1500         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1501         q[nextGame] = '*';
1502     }
1503     fseek(tf, -(strlen(p)+4), SEEK_END);
1504     c = fgetc(tf);
1505     if(c != '"') // depending on DOS or Unix line endings we can be one off
1506          fseek(tf, -(strlen(p)+2), SEEK_END);
1507     else fseek(tf, -(strlen(p)+3), SEEK_END);
1508     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1509     DisplayMessage(buf, "");
1510     free(p); appData.results = q;
1511     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1512        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1513       int round = appData.defaultMatchGames * appData.tourneyType;
1514       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1515          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1516         UnloadEngine(&first);  // next game belongs to other pairing;
1517         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1518     }
1519     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1520 }
1521
1522 void
1523 MatchEvent (int mode)
1524 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1525         int dummy;
1526         if(matchMode) { // already in match mode: switch it off
1527             abortMatch = TRUE;
1528             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1529             return;
1530         }
1531 //      if(gameMode != BeginningOfGame) {
1532 //          DisplayError(_("You can only start a match from the initial position."), 0);
1533 //          return;
1534 //      }
1535         abortMatch = FALSE;
1536         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1537         /* Set up machine vs. machine match */
1538         nextGame = 0;
1539         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1540         if(appData.tourneyFile[0]) {
1541             ReserveGame(-1, 0);
1542             if(nextGame > appData.matchGames) {
1543                 char buf[MSG_SIZ];
1544                 if(strchr(appData.results, '*') == NULL) {
1545                     FILE *f;
1546                     appData.tourneyCycles++;
1547                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1548                         fclose(f);
1549                         NextTourneyGame(-1, &dummy);
1550                         ReserveGame(-1, 0);
1551                         if(nextGame <= appData.matchGames) {
1552                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1553                             matchMode = mode;
1554                             ScheduleDelayedEvent(NextMatchGame, 10000);
1555                             return;
1556                         }
1557                     }
1558                 }
1559                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1560                 DisplayError(buf, 0);
1561                 appData.tourneyFile[0] = 0;
1562                 return;
1563             }
1564         } else
1565         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1566             DisplayFatalError(_("Can't have a match with no chess programs"),
1567                               0, 2);
1568             return;
1569         }
1570         matchMode = mode;
1571         matchGame = roundNr = 1;
1572         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1573         NextMatchGame();
1574 }
1575
1576 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1577
1578 void
1579 InitBackEnd3 P((void))
1580 {
1581     GameMode initialMode;
1582     char buf[MSG_SIZ];
1583     int err, len;
1584
1585     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1586        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1587         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1588        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1589        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1590         char c, *q = first.variants, *p = strchr(q, ',');
1591         if(p) *p = NULLCHAR;
1592         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1593             int w, h, s;
1594             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1595                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1596             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1597             Reset(TRUE, FALSE);         // and re-initialize
1598         }
1599         if(p) *p = ',';
1600     }
1601
1602     InitChessProgram(&first, startedFromSetupPosition);
1603
1604     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1605         free(programVersion);
1606         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1607         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1608         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1609     }
1610
1611     if (appData.icsActive) {
1612 #ifdef WIN32
1613         /* [DM] Make a console window if needed [HGM] merged ifs */
1614         ConsoleCreate();
1615 #endif
1616         err = establish();
1617         if (err != 0)
1618           {
1619             if (*appData.icsCommPort != NULLCHAR)
1620               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1621                              appData.icsCommPort);
1622             else
1623               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1624                         appData.icsHost, appData.icsPort);
1625
1626             if( (len >= MSG_SIZ) && appData.debugMode )
1627               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1628
1629             DisplayFatalError(buf, err, 1);
1630             return;
1631         }
1632         SetICSMode();
1633         telnetISR =
1634           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1635         fromUserISR =
1636           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1637         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1638             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1639     } else if (appData.noChessProgram) {
1640         SetNCPMode();
1641     } else {
1642         SetGNUMode();
1643     }
1644
1645     if (*appData.cmailGameName != NULLCHAR) {
1646         SetCmailMode();
1647         OpenLoopback(&cmailPR);
1648         cmailISR =
1649           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1650     }
1651
1652     ThawUI();
1653     DisplayMessage("", "");
1654     if (StrCaseCmp(appData.initialMode, "") == 0) {
1655       initialMode = BeginningOfGame;
1656       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1657         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1658         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1659         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1660         ModeHighlight();
1661       }
1662     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1663       initialMode = TwoMachinesPlay;
1664     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1665       initialMode = AnalyzeFile;
1666     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1667       initialMode = AnalyzeMode;
1668     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1669       initialMode = MachinePlaysWhite;
1670     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1671       initialMode = MachinePlaysBlack;
1672     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1673       initialMode = EditGame;
1674     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1675       initialMode = EditPosition;
1676     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1677       initialMode = Training;
1678     } else {
1679       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1680       if( (len >= MSG_SIZ) && appData.debugMode )
1681         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1682
1683       DisplayFatalError(buf, 0, 2);
1684       return;
1685     }
1686
1687     if (appData.matchMode) {
1688         if(appData.tourneyFile[0]) { // start tourney from command line
1689             FILE *f;
1690             if(f = fopen(appData.tourneyFile, "r")) {
1691                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1692                 fclose(f);
1693                 appData.clockMode = TRUE;
1694                 SetGNUMode();
1695             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1696         }
1697         MatchEvent(TRUE);
1698     } else if (*appData.cmailGameName != NULLCHAR) {
1699         /* Set up cmail mode */
1700         ReloadCmailMsgEvent(TRUE);
1701     } else {
1702         /* Set up other modes */
1703         if (initialMode == AnalyzeFile) {
1704           if (*appData.loadGameFile == NULLCHAR) {
1705             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1706             return;
1707           }
1708         }
1709         if (*appData.loadGameFile != NULLCHAR) {
1710             (void) LoadGameFromFile(appData.loadGameFile,
1711                                     appData.loadGameIndex,
1712                                     appData.loadGameFile, TRUE);
1713         } else if (*appData.loadPositionFile != NULLCHAR) {
1714             (void) LoadPositionFromFile(appData.loadPositionFile,
1715                                         appData.loadPositionIndex,
1716                                         appData.loadPositionFile);
1717             /* [HGM] try to make self-starting even after FEN load */
1718             /* to allow automatic setup of fairy variants with wtm */
1719             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1720                 gameMode = BeginningOfGame;
1721                 setboardSpoiledMachineBlack = 1;
1722             }
1723             /* [HGM] loadPos: make that every new game uses the setup */
1724             /* from file as long as we do not switch variant          */
1725             if(!blackPlaysFirst) {
1726                 startedFromPositionFile = TRUE;
1727                 CopyBoard(filePosition, boards[0]);
1728             }
1729         }
1730         if (initialMode == AnalyzeMode) {
1731           if (appData.noChessProgram) {
1732             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1733             return;
1734           }
1735           if (appData.icsActive) {
1736             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1737             return;
1738           }
1739           AnalyzeModeEvent();
1740         } else if (initialMode == AnalyzeFile) {
1741           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1742           ShowThinkingEvent();
1743           AnalyzeFileEvent();
1744           AnalysisPeriodicEvent(1);
1745         } else if (initialMode == MachinePlaysWhite) {
1746           if (appData.noChessProgram) {
1747             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1748                               0, 2);
1749             return;
1750           }
1751           if (appData.icsActive) {
1752             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1753                               0, 2);
1754             return;
1755           }
1756           MachineWhiteEvent();
1757         } else if (initialMode == MachinePlaysBlack) {
1758           if (appData.noChessProgram) {
1759             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1760                               0, 2);
1761             return;
1762           }
1763           if (appData.icsActive) {
1764             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1765                               0, 2);
1766             return;
1767           }
1768           MachineBlackEvent();
1769         } else if (initialMode == TwoMachinesPlay) {
1770           if (appData.noChessProgram) {
1771             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1772                               0, 2);
1773             return;
1774           }
1775           if (appData.icsActive) {
1776             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1777                               0, 2);
1778             return;
1779           }
1780           TwoMachinesEvent();
1781         } else if (initialMode == EditGame) {
1782           EditGameEvent();
1783         } else if (initialMode == EditPosition) {
1784           EditPositionEvent();
1785         } else if (initialMode == Training) {
1786           if (*appData.loadGameFile == NULLCHAR) {
1787             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1788             return;
1789           }
1790           TrainingEvent();
1791         }
1792     }
1793 }
1794
1795 void
1796 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1797 {
1798     DisplayBook(current+1);
1799
1800     MoveHistorySet( movelist, first, last, current, pvInfoList );
1801
1802     EvalGraphSet( first, last, current, pvInfoList );
1803
1804     MakeEngineOutputTitle();
1805 }
1806
1807 /*
1808  * Establish will establish a contact to a remote host.port.
1809  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1810  *  used to talk to the host.
1811  * Returns 0 if okay, error code if not.
1812  */
1813 int
1814 establish ()
1815 {
1816     char buf[MSG_SIZ];
1817
1818     if (*appData.icsCommPort != NULLCHAR) {
1819         /* Talk to the host through a serial comm port */
1820         return OpenCommPort(appData.icsCommPort, &icsPR);
1821
1822     } else if (*appData.gateway != NULLCHAR) {
1823         if (*appData.remoteShell == NULLCHAR) {
1824             /* Use the rcmd protocol to run telnet program on a gateway host */
1825             snprintf(buf, sizeof(buf), "%s %s %s",
1826                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1827             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1828
1829         } else {
1830             /* Use the rsh program to run telnet program on a gateway host */
1831             if (*appData.remoteUser == NULLCHAR) {
1832                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1833                         appData.gateway, appData.telnetProgram,
1834                         appData.icsHost, appData.icsPort);
1835             } else {
1836                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1837                         appData.remoteShell, appData.gateway,
1838                         appData.remoteUser, appData.telnetProgram,
1839                         appData.icsHost, appData.icsPort);
1840             }
1841             return StartChildProcess(buf, "", &icsPR);
1842
1843         }
1844     } else if (appData.useTelnet) {
1845         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1846
1847     } else {
1848         /* TCP socket interface differs somewhat between
1849            Unix and NT; handle details in the front end.
1850            */
1851         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1852     }
1853 }
1854
1855 void
1856 EscapeExpand (char *p, char *q)
1857 {       // [HGM] initstring: routine to shape up string arguments
1858         while(*p++ = *q++) if(p[-1] == '\\')
1859             switch(*q++) {
1860                 case 'n': p[-1] = '\n'; break;
1861                 case 'r': p[-1] = '\r'; break;
1862                 case 't': p[-1] = '\t'; break;
1863                 case '\\': p[-1] = '\\'; break;
1864                 case 0: *p = 0; return;
1865                 default: p[-1] = q[-1]; break;
1866             }
1867 }
1868
1869 void
1870 show_bytes (FILE *fp, char *buf, int count)
1871 {
1872     while (count--) {
1873         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1874             fprintf(fp, "\\%03o", *buf & 0xff);
1875         } else {
1876             putc(*buf, fp);
1877         }
1878         buf++;
1879     }
1880     fflush(fp);
1881 }
1882
1883 /* Returns an errno value */
1884 int
1885 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1886 {
1887     char buf[8192], *p, *q, *buflim;
1888     int left, newcount, outcount;
1889
1890     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1891         *appData.gateway != NULLCHAR) {
1892         if (appData.debugMode) {
1893             fprintf(debugFP, ">ICS: ");
1894             show_bytes(debugFP, message, count);
1895             fprintf(debugFP, "\n");
1896         }
1897         return OutputToProcess(pr, message, count, outError);
1898     }
1899
1900     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1901     p = message;
1902     q = buf;
1903     left = count;
1904     newcount = 0;
1905     while (left) {
1906         if (q >= buflim) {
1907             if (appData.debugMode) {
1908                 fprintf(debugFP, ">ICS: ");
1909                 show_bytes(debugFP, buf, newcount);
1910                 fprintf(debugFP, "\n");
1911             }
1912             outcount = OutputToProcess(pr, buf, newcount, outError);
1913             if (outcount < newcount) return -1; /* to be sure */
1914             q = buf;
1915             newcount = 0;
1916         }
1917         if (*p == '\n') {
1918             *q++ = '\r';
1919             newcount++;
1920         } else if (((unsigned char) *p) == TN_IAC) {
1921             *q++ = (char) TN_IAC;
1922             newcount ++;
1923         }
1924         *q++ = *p++;
1925         newcount++;
1926         left--;
1927     }
1928     if (appData.debugMode) {
1929         fprintf(debugFP, ">ICS: ");
1930         show_bytes(debugFP, buf, newcount);
1931         fprintf(debugFP, "\n");
1932     }
1933     outcount = OutputToProcess(pr, buf, newcount, outError);
1934     if (outcount < newcount) return -1; /* to be sure */
1935     return count;
1936 }
1937
1938 void
1939 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1940 {
1941     int outError, outCount;
1942     static int gotEof = 0;
1943     static FILE *ini;
1944
1945     /* Pass data read from player on to ICS */
1946     if (count > 0) {
1947         gotEof = 0;
1948         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1949         if (outCount < count) {
1950             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1951         }
1952         if(have_sent_ICS_logon == 2) {
1953           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1954             fprintf(ini, "%s", message);
1955             have_sent_ICS_logon = 3;
1956           } else
1957             have_sent_ICS_logon = 1;
1958         } else if(have_sent_ICS_logon == 3) {
1959             fprintf(ini, "%s", message);
1960             fclose(ini);
1961           have_sent_ICS_logon = 1;
1962         }
1963     } else if (count < 0) {
1964         RemoveInputSource(isr);
1965         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1966     } else if (gotEof++ > 0) {
1967         RemoveInputSource(isr);
1968         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1969     }
1970 }
1971
1972 void
1973 KeepAlive ()
1974 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1975     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1976     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1977     SendToICS("date\n");
1978     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1979 }
1980
1981 /* added routine for printf style output to ics */
1982 void
1983 ics_printf (char *format, ...)
1984 {
1985     char buffer[MSG_SIZ];
1986     va_list args;
1987
1988     va_start(args, format);
1989     vsnprintf(buffer, sizeof(buffer), format, args);
1990     buffer[sizeof(buffer)-1] = '\0';
1991     SendToICS(buffer);
1992     va_end(args);
1993 }
1994
1995 void
1996 SendToICS (char *s)
1997 {
1998     int count, outCount, outError;
1999
2000     if (icsPR == NoProc) return;
2001
2002     count = strlen(s);
2003     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2004     if (outCount < count) {
2005         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2006     }
2007 }
2008
2009 /* This is used for sending logon scripts to the ICS. Sending
2010    without a delay causes problems when using timestamp on ICC
2011    (at least on my machine). */
2012 void
2013 SendToICSDelayed (char *s, long msdelay)
2014 {
2015     int count, outCount, outError;
2016
2017     if (icsPR == NoProc) return;
2018
2019     count = strlen(s);
2020     if (appData.debugMode) {
2021         fprintf(debugFP, ">ICS: ");
2022         show_bytes(debugFP, s, count);
2023         fprintf(debugFP, "\n");
2024     }
2025     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2026                                       msdelay);
2027     if (outCount < count) {
2028         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2029     }
2030 }
2031
2032
2033 /* Remove all highlighting escape sequences in s
2034    Also deletes any suffix starting with '('
2035    */
2036 char *
2037 StripHighlightAndTitle (char *s)
2038 {
2039     static char retbuf[MSG_SIZ];
2040     char *p = retbuf;
2041
2042     while (*s != NULLCHAR) {
2043         while (*s == '\033') {
2044             while (*s != NULLCHAR && !isalpha(*s)) s++;
2045             if (*s != NULLCHAR) s++;
2046         }
2047         while (*s != NULLCHAR && *s != '\033') {
2048             if (*s == '(' || *s == '[') {
2049                 *p = NULLCHAR;
2050                 return retbuf;
2051             }
2052             *p++ = *s++;
2053         }
2054     }
2055     *p = NULLCHAR;
2056     return retbuf;
2057 }
2058
2059 /* Remove all highlighting escape sequences in s */
2060 char *
2061 StripHighlight (char *s)
2062 {
2063     static char retbuf[MSG_SIZ];
2064     char *p = retbuf;
2065
2066     while (*s != NULLCHAR) {
2067         while (*s == '\033') {
2068             while (*s != NULLCHAR && !isalpha(*s)) s++;
2069             if (*s != NULLCHAR) s++;
2070         }
2071         while (*s != NULLCHAR && *s != '\033') {
2072             *p++ = *s++;
2073         }
2074     }
2075     *p = NULLCHAR;
2076     return retbuf;
2077 }
2078
2079 char engineVariant[MSG_SIZ];
2080 char *variantNames[] = VARIANT_NAMES;
2081 char *
2082 VariantName (VariantClass v)
2083 {
2084     if(v == VariantUnknown || *engineVariant) return engineVariant;
2085     return variantNames[v];
2086 }
2087
2088
2089 /* Identify a variant from the strings the chess servers use or the
2090    PGN Variant tag names we use. */
2091 VariantClass
2092 StringToVariant (char *e)
2093 {
2094     char *p;
2095     int wnum = -1;
2096     VariantClass v = VariantNormal;
2097     int i, found = FALSE;
2098     char buf[MSG_SIZ];
2099     int len;
2100
2101     if (!e) return v;
2102
2103     /* [HGM] skip over optional board-size prefixes */
2104     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2105         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2106         while( *e++ != '_');
2107     }
2108
2109     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2110         v = VariantNormal;
2111         found = TRUE;
2112     } else
2113     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2114       if (p = StrCaseStr(e, variantNames[i])) {
2115         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2116         v = (VariantClass) i;
2117         found = TRUE;
2118         break;
2119       }
2120     }
2121
2122     if (!found) {
2123       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2124           || StrCaseStr(e, "wild/fr")
2125           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2126         v = VariantFischeRandom;
2127       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2128                  (i = 1, p = StrCaseStr(e, "w"))) {
2129         p += i;
2130         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2131         if (isdigit(*p)) {
2132           wnum = atoi(p);
2133         } else {
2134           wnum = -1;
2135         }
2136         switch (wnum) {
2137         case 0: /* FICS only, actually */
2138         case 1:
2139           /* Castling legal even if K starts on d-file */
2140           v = VariantWildCastle;
2141           break;
2142         case 2:
2143         case 3:
2144         case 4:
2145           /* Castling illegal even if K & R happen to start in
2146              normal positions. */
2147           v = VariantNoCastle;
2148           break;
2149         case 5:
2150         case 7:
2151         case 8:
2152         case 10:
2153         case 11:
2154         case 12:
2155         case 13:
2156         case 14:
2157         case 15:
2158         case 18:
2159         case 19:
2160           /* Castling legal iff K & R start in normal positions */
2161           v = VariantNormal;
2162           break;
2163         case 6:
2164         case 20:
2165         case 21:
2166           /* Special wilds for position setup; unclear what to do here */
2167           v = VariantLoadable;
2168           break;
2169         case 9:
2170           /* Bizarre ICC game */
2171           v = VariantTwoKings;
2172           break;
2173         case 16:
2174           v = VariantKriegspiel;
2175           break;
2176         case 17:
2177           v = VariantLosers;
2178           break;
2179         case 22:
2180           v = VariantFischeRandom;
2181           break;
2182         case 23:
2183           v = VariantCrazyhouse;
2184           break;
2185         case 24:
2186           v = VariantBughouse;
2187           break;
2188         case 25:
2189           v = Variant3Check;
2190           break;
2191         case 26:
2192           /* Not quite the same as FICS suicide! */
2193           v = VariantGiveaway;
2194           break;
2195         case 27:
2196           v = VariantAtomic;
2197           break;
2198         case 28:
2199           v = VariantShatranj;
2200           break;
2201
2202         /* Temporary names for future ICC types.  The name *will* change in
2203            the next xboard/WinBoard release after ICC defines it. */
2204         case 29:
2205           v = Variant29;
2206           break;
2207         case 30:
2208           v = Variant30;
2209           break;
2210         case 31:
2211           v = Variant31;
2212           break;
2213         case 32:
2214           v = Variant32;
2215           break;
2216         case 33:
2217           v = Variant33;
2218           break;
2219         case 34:
2220           v = Variant34;
2221           break;
2222         case 35:
2223           v = Variant35;
2224           break;
2225         case 36:
2226           v = Variant36;
2227           break;
2228         case 37:
2229           v = VariantShogi;
2230           break;
2231         case 38:
2232           v = VariantXiangqi;
2233           break;
2234         case 39:
2235           v = VariantCourier;
2236           break;
2237         case 40:
2238           v = VariantGothic;
2239           break;
2240         case 41:
2241           v = VariantCapablanca;
2242           break;
2243         case 42:
2244           v = VariantKnightmate;
2245           break;
2246         case 43:
2247           v = VariantFairy;
2248           break;
2249         case 44:
2250           v = VariantCylinder;
2251           break;
2252         case 45:
2253           v = VariantFalcon;
2254           break;
2255         case 46:
2256           v = VariantCapaRandom;
2257           break;
2258         case 47:
2259           v = VariantBerolina;
2260           break;
2261         case 48:
2262           v = VariantJanus;
2263           break;
2264         case 49:
2265           v = VariantSuper;
2266           break;
2267         case 50:
2268           v = VariantGreat;
2269           break;
2270         case -1:
2271           /* Found "wild" or "w" in the string but no number;
2272              must assume it's normal chess. */
2273           v = VariantNormal;
2274           break;
2275         default:
2276           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2277           if( (len >= MSG_SIZ) && appData.debugMode )
2278             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2279
2280           DisplayError(buf, 0);
2281           v = VariantUnknown;
2282           break;
2283         }
2284       }
2285     }
2286     if (appData.debugMode) {
2287       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2288               e, wnum, VariantName(v));
2289     }
2290     return v;
2291 }
2292
2293 static int leftover_start = 0, leftover_len = 0;
2294 char star_match[STAR_MATCH_N][MSG_SIZ];
2295
2296 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2297    advance *index beyond it, and set leftover_start to the new value of
2298    *index; else return FALSE.  If pattern contains the character '*', it
2299    matches any sequence of characters not containing '\r', '\n', or the
2300    character following the '*' (if any), and the matched sequence(s) are
2301    copied into star_match.
2302    */
2303 int
2304 looking_at ( char *buf, int *index, char *pattern)
2305 {
2306     char *bufp = &buf[*index], *patternp = pattern;
2307     int star_count = 0;
2308     char *matchp = star_match[0];
2309
2310     for (;;) {
2311         if (*patternp == NULLCHAR) {
2312             *index = leftover_start = bufp - buf;
2313             *matchp = NULLCHAR;
2314             return TRUE;
2315         }
2316         if (*bufp == NULLCHAR) return FALSE;
2317         if (*patternp == '*') {
2318             if (*bufp == *(patternp + 1)) {
2319                 *matchp = NULLCHAR;
2320                 matchp = star_match[++star_count];
2321                 patternp += 2;
2322                 bufp++;
2323                 continue;
2324             } else if (*bufp == '\n' || *bufp == '\r') {
2325                 patternp++;
2326                 if (*patternp == NULLCHAR)
2327                   continue;
2328                 else
2329                   return FALSE;
2330             } else {
2331                 *matchp++ = *bufp++;
2332                 continue;
2333             }
2334         }
2335         if (*patternp != *bufp) return FALSE;
2336         patternp++;
2337         bufp++;
2338     }
2339 }
2340
2341 void
2342 SendToPlayer (char *data, int length)
2343 {
2344     int error, outCount;
2345     outCount = OutputToProcess(NoProc, data, length, &error);
2346     if (outCount < length) {
2347         DisplayFatalError(_("Error writing to display"), error, 1);
2348     }
2349 }
2350
2351 void
2352 PackHolding (char packed[], char *holding)
2353 {
2354     char *p = holding;
2355     char *q = packed;
2356     int runlength = 0;
2357     int curr = 9999;
2358     do {
2359         if (*p == curr) {
2360             runlength++;
2361         } else {
2362             switch (runlength) {
2363               case 0:
2364                 break;
2365               case 1:
2366                 *q++ = curr;
2367                 break;
2368               case 2:
2369                 *q++ = curr;
2370                 *q++ = curr;
2371                 break;
2372               default:
2373                 sprintf(q, "%d", runlength);
2374                 while (*q) q++;
2375                 *q++ = curr;
2376                 break;
2377             }
2378             runlength = 1;
2379             curr = *p;
2380         }
2381     } while (*p++);
2382     *q = NULLCHAR;
2383 }
2384
2385 /* Telnet protocol requests from the front end */
2386 void
2387 TelnetRequest (unsigned char ddww, unsigned char option)
2388 {
2389     unsigned char msg[3];
2390     int outCount, outError;
2391
2392     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2393
2394     if (appData.debugMode) {
2395         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2396         switch (ddww) {
2397           case TN_DO:
2398             ddwwStr = "DO";
2399             break;
2400           case TN_DONT:
2401             ddwwStr = "DONT";
2402             break;
2403           case TN_WILL:
2404             ddwwStr = "WILL";
2405             break;
2406           case TN_WONT:
2407             ddwwStr = "WONT";
2408             break;
2409           default:
2410             ddwwStr = buf1;
2411             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2412             break;
2413         }
2414         switch (option) {
2415           case TN_ECHO:
2416             optionStr = "ECHO";
2417             break;
2418           default:
2419             optionStr = buf2;
2420             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2421             break;
2422         }
2423         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2424     }
2425     msg[0] = TN_IAC;
2426     msg[1] = ddww;
2427     msg[2] = option;
2428     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2429     if (outCount < 3) {
2430         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2431     }
2432 }
2433
2434 void
2435 DoEcho ()
2436 {
2437     if (!appData.icsActive) return;
2438     TelnetRequest(TN_DO, TN_ECHO);
2439 }
2440
2441 void
2442 DontEcho ()
2443 {
2444     if (!appData.icsActive) return;
2445     TelnetRequest(TN_DONT, TN_ECHO);
2446 }
2447
2448 void
2449 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2450 {
2451     /* put the holdings sent to us by the server on the board holdings area */
2452     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2453     char p;
2454     ChessSquare piece;
2455
2456     if(gameInfo.holdingsWidth < 2)  return;
2457     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2458         return; // prevent overwriting by pre-board holdings
2459
2460     if( (int)lowestPiece >= BlackPawn ) {
2461         holdingsColumn = 0;
2462         countsColumn = 1;
2463         holdingsStartRow = BOARD_HEIGHT-1;
2464         direction = -1;
2465     } else {
2466         holdingsColumn = BOARD_WIDTH-1;
2467         countsColumn = BOARD_WIDTH-2;
2468         holdingsStartRow = 0;
2469         direction = 1;
2470     }
2471
2472     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2473         board[i][holdingsColumn] = EmptySquare;
2474         board[i][countsColumn]   = (ChessSquare) 0;
2475     }
2476     while( (p=*holdings++) != NULLCHAR ) {
2477         piece = CharToPiece( ToUpper(p) );
2478         if(piece == EmptySquare) continue;
2479         /*j = (int) piece - (int) WhitePawn;*/
2480         j = PieceToNumber(piece);
2481         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2482         if(j < 0) continue;               /* should not happen */
2483         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2484         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2485         board[holdingsStartRow+j*direction][countsColumn]++;
2486     }
2487 }
2488
2489
2490 void
2491 VariantSwitch (Board board, VariantClass newVariant)
2492 {
2493    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2494    static Board oldBoard;
2495
2496    startedFromPositionFile = FALSE;
2497    if(gameInfo.variant == newVariant) return;
2498
2499    /* [HGM] This routine is called each time an assignment is made to
2500     * gameInfo.variant during a game, to make sure the board sizes
2501     * are set to match the new variant. If that means adding or deleting
2502     * holdings, we shift the playing board accordingly
2503     * This kludge is needed because in ICS observe mode, we get boards
2504     * of an ongoing game without knowing the variant, and learn about the
2505     * latter only later. This can be because of the move list we requested,
2506     * in which case the game history is refilled from the beginning anyway,
2507     * but also when receiving holdings of a crazyhouse game. In the latter
2508     * case we want to add those holdings to the already received position.
2509     */
2510
2511
2512    if (appData.debugMode) {
2513      fprintf(debugFP, "Switch board from %s to %s\n",
2514              VariantName(gameInfo.variant), VariantName(newVariant));
2515      setbuf(debugFP, NULL);
2516    }
2517    shuffleOpenings = 0;       /* [HGM] shuffle */
2518    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2519    switch(newVariant)
2520      {
2521      case VariantShogi:
2522        newWidth = 9;  newHeight = 9;
2523        gameInfo.holdingsSize = 7;
2524      case VariantBughouse:
2525      case VariantCrazyhouse:
2526        newHoldingsWidth = 2; break;
2527      case VariantGreat:
2528        newWidth = 10;
2529      case VariantSuper:
2530        newHoldingsWidth = 2;
2531        gameInfo.holdingsSize = 8;
2532        break;
2533      case VariantGothic:
2534      case VariantCapablanca:
2535      case VariantCapaRandom:
2536        newWidth = 10;
2537      default:
2538        newHoldingsWidth = gameInfo.holdingsSize = 0;
2539      };
2540
2541    if(newWidth  != gameInfo.boardWidth  ||
2542       newHeight != gameInfo.boardHeight ||
2543       newHoldingsWidth != gameInfo.holdingsWidth ) {
2544
2545      /* shift position to new playing area, if needed */
2546      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2547        for(i=0; i<BOARD_HEIGHT; i++)
2548          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2549            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2550              board[i][j];
2551        for(i=0; i<newHeight; i++) {
2552          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2553          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2554        }
2555      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2556        for(i=0; i<BOARD_HEIGHT; i++)
2557          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2558            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2559              board[i][j];
2560      }
2561      board[HOLDINGS_SET] = 0;
2562      gameInfo.boardWidth  = newWidth;
2563      gameInfo.boardHeight = newHeight;
2564      gameInfo.holdingsWidth = newHoldingsWidth;
2565      gameInfo.variant = newVariant;
2566      InitDrawingSizes(-2, 0);
2567    } else gameInfo.variant = newVariant;
2568    CopyBoard(oldBoard, board);   // remember correctly formatted board
2569      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2570    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2571 }
2572
2573 static int loggedOn = FALSE;
2574
2575 /*-- Game start info cache: --*/
2576 int gs_gamenum;
2577 char gs_kind[MSG_SIZ];
2578 static char player1Name[128] = "";
2579 static char player2Name[128] = "";
2580 static char cont_seq[] = "\n\\   ";
2581 static int player1Rating = -1;
2582 static int player2Rating = -1;
2583 /*----------------------------*/
2584
2585 ColorClass curColor = ColorNormal;
2586 int suppressKibitz = 0;
2587
2588 // [HGM] seekgraph
2589 Boolean soughtPending = FALSE;
2590 Boolean seekGraphUp;
2591 #define MAX_SEEK_ADS 200
2592 #define SQUARE 0x80
2593 char *seekAdList[MAX_SEEK_ADS];
2594 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2595 float tcList[MAX_SEEK_ADS];
2596 char colorList[MAX_SEEK_ADS];
2597 int nrOfSeekAds = 0;
2598 int minRating = 1010, maxRating = 2800;
2599 int hMargin = 10, vMargin = 20, h, w;
2600 extern int squareSize, lineGap;
2601
2602 void
2603 PlotSeekAd (int i)
2604 {
2605         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2606         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2607         if(r < minRating+100 && r >=0 ) r = minRating+100;
2608         if(r > maxRating) r = maxRating;
2609         if(tc < 1.f) tc = 1.f;
2610         if(tc > 95.f) tc = 95.f;
2611         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2612         y = ((double)r - minRating)/(maxRating - minRating)
2613             * (h-vMargin-squareSize/8-1) + vMargin;
2614         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2615         if(strstr(seekAdList[i], " u ")) color = 1;
2616         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2617            !strstr(seekAdList[i], "bullet") &&
2618            !strstr(seekAdList[i], "blitz") &&
2619            !strstr(seekAdList[i], "standard") ) color = 2;
2620         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2621         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2622 }
2623
2624 void
2625 PlotSingleSeekAd (int i)
2626 {
2627         PlotSeekAd(i);
2628 }
2629
2630 void
2631 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2632 {
2633         char buf[MSG_SIZ], *ext = "";
2634         VariantClass v = StringToVariant(type);
2635         if(strstr(type, "wild")) {
2636             ext = type + 4; // append wild number
2637             if(v == VariantFischeRandom) type = "chess960"; else
2638             if(v == VariantLoadable) type = "setup"; else
2639             type = VariantName(v);
2640         }
2641         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2642         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2643             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2644             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2645             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2646             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2647             seekNrList[nrOfSeekAds] = nr;
2648             zList[nrOfSeekAds] = 0;
2649             seekAdList[nrOfSeekAds++] = StrSave(buf);
2650             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2651         }
2652 }
2653
2654 void
2655 EraseSeekDot (int i)
2656 {
2657     int x = xList[i], y = yList[i], d=squareSize/4, k;
2658     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2659     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2660     // now replot every dot that overlapped
2661     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2662         int xx = xList[k], yy = yList[k];
2663         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2664             DrawSeekDot(xx, yy, colorList[k]);
2665     }
2666 }
2667
2668 void
2669 RemoveSeekAd (int nr)
2670 {
2671         int i;
2672         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2673             EraseSeekDot(i);
2674             if(seekAdList[i]) free(seekAdList[i]);
2675             seekAdList[i] = seekAdList[--nrOfSeekAds];
2676             seekNrList[i] = seekNrList[nrOfSeekAds];
2677             ratingList[i] = ratingList[nrOfSeekAds];
2678             colorList[i]  = colorList[nrOfSeekAds];
2679             tcList[i] = tcList[nrOfSeekAds];
2680             xList[i]  = xList[nrOfSeekAds];
2681             yList[i]  = yList[nrOfSeekAds];
2682             zList[i]  = zList[nrOfSeekAds];
2683             seekAdList[nrOfSeekAds] = NULL;
2684             break;
2685         }
2686 }
2687
2688 Boolean
2689 MatchSoughtLine (char *line)
2690 {
2691     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2692     int nr, base, inc, u=0; char dummy;
2693
2694     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2695        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2696        (u=1) &&
2697        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2698         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2699         // match: compact and save the line
2700         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2701         return TRUE;
2702     }
2703     return FALSE;
2704 }
2705
2706 int
2707 DrawSeekGraph ()
2708 {
2709     int i;
2710     if(!seekGraphUp) return FALSE;
2711     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2712     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2713
2714     DrawSeekBackground(0, 0, w, h);
2715     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2716     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2717     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2718         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2719         yy = h-1-yy;
2720         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2721         if(i%500 == 0) {
2722             char buf[MSG_SIZ];
2723             snprintf(buf, MSG_SIZ, "%d", i);
2724             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2725         }
2726     }
2727     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2728     for(i=1; i<100; i+=(i<10?1:5)) {
2729         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2730         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2731         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2732             char buf[MSG_SIZ];
2733             snprintf(buf, MSG_SIZ, "%d", i);
2734             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2735         }
2736     }
2737     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2738     return TRUE;
2739 }
2740
2741 int
2742 SeekGraphClick (ClickType click, int x, int y, int moving)
2743 {
2744     static int lastDown = 0, displayed = 0, lastSecond;
2745     if(y < 0) return FALSE;
2746     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2747         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2748         if(!seekGraphUp) return FALSE;
2749         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2750         DrawPosition(TRUE, NULL);
2751         return TRUE;
2752     }
2753     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2754         if(click == Release || moving) return FALSE;
2755         nrOfSeekAds = 0;
2756         soughtPending = TRUE;
2757         SendToICS(ics_prefix);
2758         SendToICS("sought\n"); // should this be "sought all"?
2759     } else { // issue challenge based on clicked ad
2760         int dist = 10000; int i, closest = 0, second = 0;
2761         for(i=0; i<nrOfSeekAds; i++) {
2762             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2763             if(d < dist) { dist = d; closest = i; }
2764             second += (d - zList[i] < 120); // count in-range ads
2765             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2766         }
2767         if(dist < 120) {
2768             char buf[MSG_SIZ];
2769             second = (second > 1);
2770             if(displayed != closest || second != lastSecond) {
2771                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2772                 lastSecond = second; displayed = closest;
2773             }
2774             if(click == Press) {
2775                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2776                 lastDown = closest;
2777                 return TRUE;
2778             } // on press 'hit', only show info
2779             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2780             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2781             SendToICS(ics_prefix);
2782             SendToICS(buf);
2783             return TRUE; // let incoming board of started game pop down the graph
2784         } else if(click == Release) { // release 'miss' is ignored
2785             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2786             if(moving == 2) { // right up-click
2787                 nrOfSeekAds = 0; // refresh graph
2788                 soughtPending = TRUE;
2789                 SendToICS(ics_prefix);
2790                 SendToICS("sought\n"); // should this be "sought all"?
2791             }
2792             return TRUE;
2793         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2794         // press miss or release hit 'pop down' seek graph
2795         seekGraphUp = FALSE;
2796         DrawPosition(TRUE, NULL);
2797     }
2798     return TRUE;
2799 }
2800
2801 void
2802 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2803 {
2804 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2805 #define STARTED_NONE 0
2806 #define STARTED_MOVES 1
2807 #define STARTED_BOARD 2
2808 #define STARTED_OBSERVE 3
2809 #define STARTED_HOLDINGS 4
2810 #define STARTED_CHATTER 5
2811 #define STARTED_COMMENT 6
2812 #define STARTED_MOVES_NOHIDE 7
2813
2814     static int started = STARTED_NONE;
2815     static char parse[20000];
2816     static int parse_pos = 0;
2817     static char buf[BUF_SIZE + 1];
2818     static int firstTime = TRUE, intfSet = FALSE;
2819     static ColorClass prevColor = ColorNormal;
2820     static int savingComment = FALSE;
2821     static int cmatch = 0; // continuation sequence match
2822     char *bp;
2823     char str[MSG_SIZ];
2824     int i, oldi;
2825     int buf_len;
2826     int next_out;
2827     int tkind;
2828     int backup;    /* [DM] For zippy color lines */
2829     char *p;
2830     char talker[MSG_SIZ]; // [HGM] chat
2831     int channel;
2832
2833     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2834
2835     if (appData.debugMode) {
2836       if (!error) {
2837         fprintf(debugFP, "<ICS: ");
2838         show_bytes(debugFP, data, count);
2839         fprintf(debugFP, "\n");
2840       }
2841     }
2842
2843     if (appData.debugMode) { int f = forwardMostMove;
2844         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2845                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2846                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2847     }
2848     if (count > 0) {
2849         /* If last read ended with a partial line that we couldn't parse,
2850            prepend it to the new read and try again. */
2851         if (leftover_len > 0) {
2852             for (i=0; i<leftover_len; i++)
2853               buf[i] = buf[leftover_start + i];
2854         }
2855
2856     /* copy new characters into the buffer */
2857     bp = buf + leftover_len;
2858     buf_len=leftover_len;
2859     for (i=0; i<count; i++)
2860     {
2861         // ignore these
2862         if (data[i] == '\r')
2863             continue;
2864
2865         // join lines split by ICS?
2866         if (!appData.noJoin)
2867         {
2868             /*
2869                 Joining just consists of finding matches against the
2870                 continuation sequence, and discarding that sequence
2871                 if found instead of copying it.  So, until a match
2872                 fails, there's nothing to do since it might be the
2873                 complete sequence, and thus, something we don't want
2874                 copied.
2875             */
2876             if (data[i] == cont_seq[cmatch])
2877             {
2878                 cmatch++;
2879                 if (cmatch == strlen(cont_seq))
2880                 {
2881                     cmatch = 0; // complete match.  just reset the counter
2882
2883                     /*
2884                         it's possible for the ICS to not include the space
2885                         at the end of the last word, making our [correct]
2886                         join operation fuse two separate words.  the server
2887                         does this when the space occurs at the width setting.
2888                     */
2889                     if (!buf_len || buf[buf_len-1] != ' ')
2890                     {
2891                         *bp++ = ' ';
2892                         buf_len++;
2893                     }
2894                 }
2895                 continue;
2896             }
2897             else if (cmatch)
2898             {
2899                 /*
2900                     match failed, so we have to copy what matched before
2901                     falling through and copying this character.  In reality,
2902                     this will only ever be just the newline character, but
2903                     it doesn't hurt to be precise.
2904                 */
2905                 strncpy(bp, cont_seq, cmatch);
2906                 bp += cmatch;
2907                 buf_len += cmatch;
2908                 cmatch = 0;
2909             }
2910         }
2911
2912         // copy this char
2913         *bp++ = data[i];
2914         buf_len++;
2915     }
2916
2917         buf[buf_len] = NULLCHAR;
2918 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2919         next_out = 0;
2920         leftover_start = 0;
2921
2922         i = 0;
2923         while (i < buf_len) {
2924             /* Deal with part of the TELNET option negotiation
2925                protocol.  We refuse to do anything beyond the
2926                defaults, except that we allow the WILL ECHO option,
2927                which ICS uses to turn off password echoing when we are
2928                directly connected to it.  We reject this option
2929                if localLineEditing mode is on (always on in xboard)
2930                and we are talking to port 23, which might be a real
2931                telnet server that will try to keep WILL ECHO on permanently.
2932              */
2933             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2934                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2935                 unsigned char option;
2936                 oldi = i;
2937                 switch ((unsigned char) buf[++i]) {
2938                   case TN_WILL:
2939                     if (appData.debugMode)
2940                       fprintf(debugFP, "\n<WILL ");
2941                     switch (option = (unsigned char) buf[++i]) {
2942                       case TN_ECHO:
2943                         if (appData.debugMode)
2944                           fprintf(debugFP, "ECHO ");
2945                         /* Reply only if this is a change, according
2946                            to the protocol rules. */
2947                         if (remoteEchoOption) break;
2948                         if (appData.localLineEditing &&
2949                             atoi(appData.icsPort) == TN_PORT) {
2950                             TelnetRequest(TN_DONT, TN_ECHO);
2951                         } else {
2952                             EchoOff();
2953                             TelnetRequest(TN_DO, TN_ECHO);
2954                             remoteEchoOption = TRUE;
2955                         }
2956                         break;
2957                       default:
2958                         if (appData.debugMode)
2959                           fprintf(debugFP, "%d ", option);
2960                         /* Whatever this is, we don't want it. */
2961                         TelnetRequest(TN_DONT, option);
2962                         break;
2963                     }
2964                     break;
2965                   case TN_WONT:
2966                     if (appData.debugMode)
2967                       fprintf(debugFP, "\n<WONT ");
2968                     switch (option = (unsigned char) buf[++i]) {
2969                       case TN_ECHO:
2970                         if (appData.debugMode)
2971                           fprintf(debugFP, "ECHO ");
2972                         /* Reply only if this is a change, according
2973                            to the protocol rules. */
2974                         if (!remoteEchoOption) break;
2975                         EchoOn();
2976                         TelnetRequest(TN_DONT, TN_ECHO);
2977                         remoteEchoOption = FALSE;
2978                         break;
2979                       default:
2980                         if (appData.debugMode)
2981                           fprintf(debugFP, "%d ", (unsigned char) option);
2982                         /* Whatever this is, it must already be turned
2983                            off, because we never agree to turn on
2984                            anything non-default, so according to the
2985                            protocol rules, we don't reply. */
2986                         break;
2987                     }
2988                     break;
2989                   case TN_DO:
2990                     if (appData.debugMode)
2991                       fprintf(debugFP, "\n<DO ");
2992                     switch (option = (unsigned char) buf[++i]) {
2993                       default:
2994                         /* Whatever this is, we refuse to do it. */
2995                         if (appData.debugMode)
2996                           fprintf(debugFP, "%d ", option);
2997                         TelnetRequest(TN_WONT, option);
2998                         break;
2999                     }
3000                     break;
3001                   case TN_DONT:
3002                     if (appData.debugMode)
3003                       fprintf(debugFP, "\n<DONT ");
3004                     switch (option = (unsigned char) buf[++i]) {
3005                       default:
3006                         if (appData.debugMode)
3007                           fprintf(debugFP, "%d ", option);
3008                         /* Whatever this is, we are already not doing
3009                            it, because we never agree to do anything
3010                            non-default, so according to the protocol
3011                            rules, we don't reply. */
3012                         break;
3013                     }
3014                     break;
3015                   case TN_IAC:
3016                     if (appData.debugMode)
3017                       fprintf(debugFP, "\n<IAC ");
3018                     /* Doubled IAC; pass it through */
3019                     i--;
3020                     break;
3021                   default:
3022                     if (appData.debugMode)
3023                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3024                     /* Drop all other telnet commands on the floor */
3025                     break;
3026                 }
3027                 if (oldi > next_out)
3028                   SendToPlayer(&buf[next_out], oldi - next_out);
3029                 if (++i > next_out)
3030                   next_out = i;
3031                 continue;
3032             }
3033
3034             /* OK, this at least will *usually* work */
3035             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3036                 loggedOn = TRUE;
3037             }
3038
3039             if (loggedOn && !intfSet) {
3040                 if (ics_type == ICS_ICC) {
3041                   snprintf(str, MSG_SIZ,
3042                           "/set-quietly interface %s\n/set-quietly style 12\n",
3043                           programVersion);
3044                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3045                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3046                 } else if (ics_type == ICS_CHESSNET) {
3047                   snprintf(str, MSG_SIZ, "/style 12\n");
3048                 } else {
3049                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3050                   strcat(str, programVersion);
3051                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3052                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3053                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3054 #ifdef WIN32
3055                   strcat(str, "$iset nohighlight 1\n");
3056 #endif
3057                   strcat(str, "$iset lock 1\n$style 12\n");
3058                 }
3059                 SendToICS(str);
3060                 NotifyFrontendLogin();
3061                 intfSet = TRUE;
3062             }
3063
3064             if (started == STARTED_COMMENT) {
3065                 /* Accumulate characters in comment */
3066                 parse[parse_pos++] = buf[i];
3067                 if (buf[i] == '\n') {
3068                     parse[parse_pos] = NULLCHAR;
3069                     if(chattingPartner>=0) {
3070                         char mess[MSG_SIZ];
3071                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3072                         OutputChatMessage(chattingPartner, mess);
3073                         chattingPartner = -1;
3074                         next_out = i+1; // [HGM] suppress printing in ICS window
3075                     } else
3076                     if(!suppressKibitz) // [HGM] kibitz
3077                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3078                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3079                         int nrDigit = 0, nrAlph = 0, j;
3080                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3081                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3082                         parse[parse_pos] = NULLCHAR;
3083                         // try to be smart: if it does not look like search info, it should go to
3084                         // ICS interaction window after all, not to engine-output window.
3085                         for(j=0; j<parse_pos; j++) { // count letters and digits
3086                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3087                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3088                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3089                         }
3090                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3091                             int depth=0; float score;
3092                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3093                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3094                                 pvInfoList[forwardMostMove-1].depth = depth;
3095                                 pvInfoList[forwardMostMove-1].score = 100*score;
3096                             }
3097                             OutputKibitz(suppressKibitz, parse);
3098                         } else {
3099                             char tmp[MSG_SIZ];
3100                             if(gameMode == IcsObserving) // restore original ICS messages
3101                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3102                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3103                             else
3104                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3105                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3106                             SendToPlayer(tmp, strlen(tmp));
3107                         }
3108                         next_out = i+1; // [HGM] suppress printing in ICS window
3109                     }
3110                     started = STARTED_NONE;
3111                 } else {
3112                     /* Don't match patterns against characters in comment */
3113                     i++;
3114                     continue;
3115                 }
3116             }
3117             if (started == STARTED_CHATTER) {
3118                 if (buf[i] != '\n') {
3119                     /* Don't match patterns against characters in chatter */
3120                     i++;
3121                     continue;
3122                 }
3123                 started = STARTED_NONE;
3124                 if(suppressKibitz) next_out = i+1;
3125             }
3126
3127             /* Kludge to deal with rcmd protocol */
3128             if (firstTime && looking_at(buf, &i, "\001*")) {
3129                 DisplayFatalError(&buf[1], 0, 1);
3130                 continue;
3131             } else {
3132                 firstTime = FALSE;
3133             }
3134
3135             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3136                 ics_type = ICS_ICC;
3137                 ics_prefix = "/";
3138                 if (appData.debugMode)
3139                   fprintf(debugFP, "ics_type %d\n", ics_type);
3140                 continue;
3141             }
3142             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3143                 ics_type = ICS_FICS;
3144                 ics_prefix = "$";
3145                 if (appData.debugMode)
3146                   fprintf(debugFP, "ics_type %d\n", ics_type);
3147                 continue;
3148             }
3149             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3150                 ics_type = ICS_CHESSNET;
3151                 ics_prefix = "/";
3152                 if (appData.debugMode)
3153                   fprintf(debugFP, "ics_type %d\n", ics_type);
3154                 continue;
3155             }
3156
3157             if (!loggedOn &&
3158                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3159                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3160                  looking_at(buf, &i, "will be \"*\""))) {
3161               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3162               continue;
3163             }
3164
3165             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3166               char buf[MSG_SIZ];
3167               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3168               DisplayIcsInteractionTitle(buf);
3169               have_set_title = TRUE;
3170             }
3171
3172             /* skip finger notes */
3173             if (started == STARTED_NONE &&
3174                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3175                  (buf[i] == '1' && buf[i+1] == '0')) &&
3176                 buf[i+2] == ':' && buf[i+3] == ' ') {
3177               started = STARTED_CHATTER;
3178               i += 3;
3179               continue;
3180             }
3181
3182             oldi = i;
3183             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3184             if(appData.seekGraph) {
3185                 if(soughtPending && MatchSoughtLine(buf+i)) {
3186                     i = strstr(buf+i, "rated") - buf;
3187                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3188                     next_out = leftover_start = i;
3189                     started = STARTED_CHATTER;
3190                     suppressKibitz = TRUE;
3191                     continue;
3192                 }
3193                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3194                         && looking_at(buf, &i, "* ads displayed")) {
3195                     soughtPending = FALSE;
3196                     seekGraphUp = TRUE;
3197                     DrawSeekGraph();
3198                     continue;
3199                 }
3200                 if(appData.autoRefresh) {
3201                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3202                         int s = (ics_type == ICS_ICC); // ICC format differs
3203                         if(seekGraphUp)
3204                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3205                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3206                         looking_at(buf, &i, "*% "); // eat prompt
3207                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3208                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3209                         next_out = i; // suppress
3210                         continue;
3211                     }
3212                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3213                         char *p = star_match[0];
3214                         while(*p) {
3215                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3216                             while(*p && *p++ != ' '); // next
3217                         }
3218                         looking_at(buf, &i, "*% "); // eat prompt
3219                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                         next_out = i;
3221                         continue;
3222                     }
3223                 }
3224             }
3225
3226             /* skip formula vars */
3227             if (started == STARTED_NONE &&
3228                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3229               started = STARTED_CHATTER;
3230               i += 3;
3231               continue;
3232             }
3233
3234             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3235             if (appData.autoKibitz && started == STARTED_NONE &&
3236                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3237                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3238                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3239                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3240                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3241                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3242                         suppressKibitz = TRUE;
3243                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3244                         next_out = i;
3245                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3246                                 && (gameMode == IcsPlayingWhite)) ||
3247                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3248                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3249                             started = STARTED_CHATTER; // own kibitz we simply discard
3250                         else {
3251                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3252                             parse_pos = 0; parse[0] = NULLCHAR;
3253                             savingComment = TRUE;
3254                             suppressKibitz = gameMode != IcsObserving ? 2 :
3255                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3256                         }
3257                         continue;
3258                 } else
3259                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3260                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3261                          && atoi(star_match[0])) {
3262                     // suppress the acknowledgements of our own autoKibitz
3263                     char *p;
3264                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3265                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3266                     SendToPlayer(star_match[0], strlen(star_match[0]));
3267                     if(looking_at(buf, &i, "*% ")) // eat prompt
3268                         suppressKibitz = FALSE;
3269                     next_out = i;
3270                     continue;
3271                 }
3272             } // [HGM] kibitz: end of patch
3273
3274             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3275
3276             // [HGM] chat: intercept tells by users for which we have an open chat window
3277             channel = -1;
3278             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3279                                            looking_at(buf, &i, "* whispers:") ||
3280                                            looking_at(buf, &i, "* kibitzes:") ||
3281                                            looking_at(buf, &i, "* shouts:") ||
3282                                            looking_at(buf, &i, "* c-shouts:") ||
3283                                            looking_at(buf, &i, "--> * ") ||
3284                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3285                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3286                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3287                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3288                 int p;
3289                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3290                 chattingPartner = -1;
3291
3292                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3293                 for(p=0; p<MAX_CHAT; p++) {
3294                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3295                     talker[0] = '['; strcat(talker, "] ");
3296                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3297                     chattingPartner = p; break;
3298                     }
3299                 } else
3300                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3301                 for(p=0; p<MAX_CHAT; p++) {
3302                     if(!strcmp("kibitzes", chatPartner[p])) {
3303                         talker[0] = '['; strcat(talker, "] ");
3304                         chattingPartner = p; break;
3305                     }
3306                 } else
3307                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3308                 for(p=0; p<MAX_CHAT; p++) {
3309                     if(!strcmp("whispers", chatPartner[p])) {
3310                         talker[0] = '['; strcat(talker, "] ");
3311                         chattingPartner = p; break;
3312                     }
3313                 } else
3314                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3315                   if(buf[i-8] == '-' && buf[i-3] == 't')
3316                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3317                     if(!strcmp("c-shouts", chatPartner[p])) {
3318                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3319                         chattingPartner = p; break;
3320                     }
3321                   }
3322                   if(chattingPartner < 0)
3323                   for(p=0; p<MAX_CHAT; p++) {
3324                     if(!strcmp("shouts", chatPartner[p])) {
3325                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3326                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3327                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3328                         chattingPartner = p; break;
3329                     }
3330                   }
3331                 }
3332                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3333                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3334                     talker[0] = 0; Colorize(ColorTell, FALSE);
3335                     chattingPartner = p; break;
3336                 }
3337                 if(chattingPartner<0) i = oldi; else {
3338                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3339                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3340                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3341                     started = STARTED_COMMENT;
3342                     parse_pos = 0; parse[0] = NULLCHAR;
3343                     savingComment = 3 + chattingPartner; // counts as TRUE
3344                     suppressKibitz = TRUE;
3345                     continue;
3346                 }
3347             } // [HGM] chat: end of patch
3348
3349           backup = i;
3350             if (appData.zippyTalk || appData.zippyPlay) {
3351                 /* [DM] Backup address for color zippy lines */
3352 #if ZIPPY
3353                if (loggedOn == TRUE)
3354                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3355                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3356 #endif
3357             } // [DM] 'else { ' deleted
3358                 if (
3359                     /* Regular tells and says */
3360                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3361                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3362                     looking_at(buf, &i, "* says: ") ||
3363                     /* Don't color "message" or "messages" output */
3364                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3365                     looking_at(buf, &i, "*. * at *:*: ") ||
3366                     looking_at(buf, &i, "--* (*:*): ") ||
3367                     /* Message notifications (same color as tells) */
3368                     looking_at(buf, &i, "* has left a message ") ||
3369                     looking_at(buf, &i, "* just sent you a message:\n") ||
3370                     /* Whispers and kibitzes */
3371                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3372                     looking_at(buf, &i, "* kibitzes: ") ||
3373                     /* Channel tells */
3374                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3375
3376                   if (tkind == 1 && strchr(star_match[0], ':')) {
3377                       /* Avoid "tells you:" spoofs in channels */
3378                      tkind = 3;
3379                   }
3380                   if (star_match[0][0] == NULLCHAR ||
3381                       strchr(star_match[0], ' ') ||
3382                       (tkind == 3 && strchr(star_match[1], ' '))) {
3383                     /* Reject bogus matches */
3384                     i = oldi;
3385                   } else {
3386                     if (appData.colorize) {
3387                       if (oldi > next_out) {
3388                         SendToPlayer(&buf[next_out], oldi - next_out);
3389                         next_out = oldi;
3390                       }
3391                       switch (tkind) {
3392                       case 1:
3393                         Colorize(ColorTell, FALSE);
3394                         curColor = ColorTell;
3395                         break;
3396                       case 2:
3397                         Colorize(ColorKibitz, FALSE);
3398                         curColor = ColorKibitz;
3399                         break;
3400                       case 3:
3401                         p = strrchr(star_match[1], '(');
3402                         if (p == NULL) {
3403                           p = star_match[1];
3404                         } else {
3405                           p++;
3406                         }
3407                         if (atoi(p) == 1) {
3408                           Colorize(ColorChannel1, FALSE);
3409                           curColor = ColorChannel1;
3410                         } else {
3411                           Colorize(ColorChannel, FALSE);
3412                           curColor = ColorChannel;
3413                         }
3414                         break;
3415                       case 5:
3416                         curColor = ColorNormal;
3417                         break;
3418                       }
3419                     }
3420                     if (started == STARTED_NONE && appData.autoComment &&
3421                         (gameMode == IcsObserving ||
3422                          gameMode == IcsPlayingWhite ||
3423                          gameMode == IcsPlayingBlack)) {
3424                       parse_pos = i - oldi;
3425                       memcpy(parse, &buf[oldi], parse_pos);
3426                       parse[parse_pos] = NULLCHAR;
3427                       started = STARTED_COMMENT;
3428                       savingComment = TRUE;
3429                     } else {
3430                       started = STARTED_CHATTER;
3431                       savingComment = FALSE;
3432                     }
3433                     loggedOn = TRUE;
3434                     continue;
3435                   }
3436                 }
3437
3438                 if (looking_at(buf, &i, "* s-shouts: ") ||
3439                     looking_at(buf, &i, "* c-shouts: ")) {
3440                     if (appData.colorize) {
3441                         if (oldi > next_out) {
3442                             SendToPlayer(&buf[next_out], oldi - next_out);
3443                             next_out = oldi;
3444                         }
3445                         Colorize(ColorSShout, FALSE);
3446                         curColor = ColorSShout;
3447                     }
3448                     loggedOn = TRUE;
3449                     started = STARTED_CHATTER;
3450                     continue;
3451                 }
3452
3453                 if (looking_at(buf, &i, "--->")) {
3454                     loggedOn = TRUE;
3455                     continue;
3456                 }
3457
3458                 if (looking_at(buf, &i, "* shouts: ") ||
3459                     looking_at(buf, &i, "--> ")) {
3460                     if (appData.colorize) {
3461                         if (oldi > next_out) {
3462                             SendToPlayer(&buf[next_out], oldi - next_out);
3463                             next_out = oldi;
3464                         }
3465                         Colorize(ColorShout, FALSE);
3466                         curColor = ColorShout;
3467                     }
3468                     loggedOn = TRUE;
3469                     started = STARTED_CHATTER;
3470                     continue;
3471                 }
3472
3473                 if (looking_at( buf, &i, "Challenge:")) {
3474                     if (appData.colorize) {
3475                         if (oldi > next_out) {
3476                             SendToPlayer(&buf[next_out], oldi - next_out);
3477                             next_out = oldi;
3478                         }
3479                         Colorize(ColorChallenge, FALSE);
3480                         curColor = ColorChallenge;
3481                     }
3482                     loggedOn = TRUE;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "* offers you") ||
3487                     looking_at(buf, &i, "* offers to be") ||
3488                     looking_at(buf, &i, "* would like to") ||
3489                     looking_at(buf, &i, "* requests to") ||
3490                     looking_at(buf, &i, "Your opponent offers") ||
3491                     looking_at(buf, &i, "Your opponent requests")) {
3492
3493                     if (appData.colorize) {
3494                         if (oldi > next_out) {
3495                             SendToPlayer(&buf[next_out], oldi - next_out);
3496                             next_out = oldi;
3497                         }
3498                         Colorize(ColorRequest, FALSE);
3499                         curColor = ColorRequest;
3500                     }
3501                     continue;
3502                 }
3503
3504                 if (looking_at(buf, &i, "* (*) seeking")) {
3505                     if (appData.colorize) {
3506                         if (oldi > next_out) {
3507                             SendToPlayer(&buf[next_out], oldi - next_out);
3508                             next_out = oldi;
3509                         }
3510                         Colorize(ColorSeek, FALSE);
3511                         curColor = ColorSeek;
3512                     }
3513                     continue;
3514             }
3515
3516           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3517
3518             if (looking_at(buf, &i, "\\   ")) {
3519                 if (prevColor != ColorNormal) {
3520                     if (oldi > next_out) {
3521                         SendToPlayer(&buf[next_out], oldi - next_out);
3522                         next_out = oldi;
3523                     }
3524                     Colorize(prevColor, TRUE);
3525                     curColor = prevColor;
3526                 }
3527                 if (savingComment) {
3528                     parse_pos = i - oldi;
3529                     memcpy(parse, &buf[oldi], parse_pos);
3530                     parse[parse_pos] = NULLCHAR;
3531                     started = STARTED_COMMENT;
3532                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3533                         chattingPartner = savingComment - 3; // kludge to remember the box
3534                 } else {
3535                     started = STARTED_CHATTER;
3536                 }
3537                 continue;
3538             }
3539
3540             if (looking_at(buf, &i, "Black Strength :") ||
3541                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3542                 looking_at(buf, &i, "<10>") ||
3543                 looking_at(buf, &i, "#@#")) {
3544                 /* Wrong board style */
3545                 loggedOn = TRUE;
3546                 SendToICS(ics_prefix);
3547                 SendToICS("set style 12\n");
3548                 SendToICS(ics_prefix);
3549                 SendToICS("refresh\n");
3550                 continue;
3551             }
3552
3553             if (looking_at(buf, &i, "login:")) {
3554               if (!have_sent_ICS_logon) {
3555                 if(ICSInitScript())
3556                   have_sent_ICS_logon = 1;
3557                 else // no init script was found
3558                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3559               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3560                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3561               }
3562                 continue;
3563             }
3564
3565             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3566                 (looking_at(buf, &i, "\n<12> ") ||
3567                  looking_at(buf, &i, "<12> "))) {
3568                 loggedOn = TRUE;
3569                 if (oldi > next_out) {
3570                     SendToPlayer(&buf[next_out], oldi - next_out);
3571                 }
3572                 next_out = i;
3573                 started = STARTED_BOARD;
3574                 parse_pos = 0;
3575                 continue;
3576             }
3577
3578             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3579                 looking_at(buf, &i, "<b1> ")) {
3580                 if (oldi > next_out) {
3581                     SendToPlayer(&buf[next_out], oldi - next_out);
3582                 }
3583                 next_out = i;
3584                 started = STARTED_HOLDINGS;
3585                 parse_pos = 0;
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3590                 loggedOn = TRUE;
3591                 /* Header for a move list -- first line */
3592
3593                 switch (ics_getting_history) {
3594                   case H_FALSE:
3595                     switch (gameMode) {
3596                       case IcsIdle:
3597                       case BeginningOfGame:
3598                         /* User typed "moves" or "oldmoves" while we
3599                            were idle.  Pretend we asked for these
3600                            moves and soak them up so user can step
3601                            through them and/or save them.
3602                            */
3603                         Reset(FALSE, TRUE);
3604                         gameMode = IcsObserving;
3605                         ModeHighlight();
3606                         ics_gamenum = -1;
3607                         ics_getting_history = H_GOT_UNREQ_HEADER;
3608                         break;
3609                       case EditGame: /*?*/
3610                       case EditPosition: /*?*/
3611                         /* Should above feature work in these modes too? */
3612                         /* For now it doesn't */
3613                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3614                         break;
3615                       default:
3616                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3617                         break;
3618                     }
3619                     break;
3620                   case H_REQUESTED:
3621                     /* Is this the right one? */
3622                     if (gameInfo.white && gameInfo.black &&
3623                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3624                         strcmp(gameInfo.black, star_match[2]) == 0) {
3625                         /* All is well */
3626                         ics_getting_history = H_GOT_REQ_HEADER;
3627                     }
3628                     break;
3629                   case H_GOT_REQ_HEADER:
3630                   case H_GOT_UNREQ_HEADER:
3631                   case H_GOT_UNWANTED_HEADER:
3632                   case H_GETTING_MOVES:
3633                     /* Should not happen */
3634                     DisplayError(_("Error gathering move list: two headers"), 0);
3635                     ics_getting_history = H_FALSE;
3636                     break;
3637                 }
3638
3639                 /* Save player ratings into gameInfo if needed */
3640                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3641                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3642                     (gameInfo.whiteRating == -1 ||
3643                      gameInfo.blackRating == -1)) {
3644
3645                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3646                     gameInfo.blackRating = string_to_rating(star_match[3]);
3647                     if (appData.debugMode)
3648                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3649                               gameInfo.whiteRating, gameInfo.blackRating);
3650                 }
3651                 continue;
3652             }
3653
3654             if (looking_at(buf, &i,
3655               "* * match, initial time: * minute*, increment: * second")) {
3656                 /* Header for a move list -- second line */
3657                 /* Initial board will follow if this is a wild game */
3658                 if (gameInfo.event != NULL) free(gameInfo.event);
3659                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3660                 gameInfo.event = StrSave(str);
3661                 /* [HGM] we switched variant. Translate boards if needed. */
3662                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3663                 continue;
3664             }
3665
3666             if (looking_at(buf, &i, "Move  ")) {
3667                 /* Beginning of a move list */
3668                 switch (ics_getting_history) {
3669                   case H_FALSE:
3670                     /* Normally should not happen */
3671                     /* Maybe user hit reset while we were parsing */
3672                     break;
3673                   case H_REQUESTED:
3674                     /* Happens if we are ignoring a move list that is not
3675                      * the one we just requested.  Common if the user
3676                      * tries to observe two games without turning off
3677                      * getMoveList */
3678                     break;
3679                   case H_GETTING_MOVES:
3680                     /* Should not happen */
3681                     DisplayError(_("Error gathering move list: nested"), 0);
3682                     ics_getting_history = H_FALSE;
3683                     break;
3684                   case H_GOT_REQ_HEADER:
3685                     ics_getting_history = H_GETTING_MOVES;
3686                     started = STARTED_MOVES;
3687                     parse_pos = 0;
3688                     if (oldi > next_out) {
3689                         SendToPlayer(&buf[next_out], oldi - next_out);
3690                     }
3691                     break;
3692                   case H_GOT_UNREQ_HEADER:
3693                     ics_getting_history = H_GETTING_MOVES;
3694                     started = STARTED_MOVES_NOHIDE;
3695                     parse_pos = 0;
3696                     break;
3697                   case H_GOT_UNWANTED_HEADER:
3698                     ics_getting_history = H_FALSE;
3699                     break;
3700                 }
3701                 continue;
3702             }
3703
3704             if (looking_at(buf, &i, "% ") ||
3705                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3706                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3707                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3708                     soughtPending = FALSE;
3709                     seekGraphUp = TRUE;
3710                     DrawSeekGraph();
3711                 }
3712                 if(suppressKibitz) next_out = i;
3713                 savingComment = FALSE;
3714                 suppressKibitz = 0;
3715                 switch (started) {
3716                   case STARTED_MOVES:
3717                   case STARTED_MOVES_NOHIDE:
3718                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3719                     parse[parse_pos + i - oldi] = NULLCHAR;
3720                     ParseGameHistory(parse);
3721 #if ZIPPY
3722                     if (appData.zippyPlay && first.initDone) {
3723                         FeedMovesToProgram(&first, forwardMostMove);
3724                         if (gameMode == IcsPlayingWhite) {
3725                             if (WhiteOnMove(forwardMostMove)) {
3726                                 if (first.sendTime) {
3727                                   if (first.useColors) {
3728                                     SendToProgram("black\n", &first);
3729                                   }
3730                                   SendTimeRemaining(&first, TRUE);
3731                                 }
3732                                 if (first.useColors) {
3733                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3734                                 }
3735                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3736                                 first.maybeThinking = TRUE;
3737                             } else {
3738                                 if (first.usePlayother) {
3739                                   if (first.sendTime) {
3740                                     SendTimeRemaining(&first, TRUE);
3741                                   }
3742                                   SendToProgram("playother\n", &first);
3743                                   firstMove = FALSE;
3744                                 } else {
3745                                   firstMove = TRUE;
3746                                 }
3747                             }
3748                         } else if (gameMode == IcsPlayingBlack) {
3749                             if (!WhiteOnMove(forwardMostMove)) {
3750                                 if (first.sendTime) {
3751                                   if (first.useColors) {
3752                                     SendToProgram("white\n", &first);
3753                                   }
3754                                   SendTimeRemaining(&first, FALSE);
3755                                 }
3756                                 if (first.useColors) {
3757                                   SendToProgram("black\n", &first);
3758                                 }
3759                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3760                                 first.maybeThinking = TRUE;
3761                             } else {
3762                                 if (first.usePlayother) {
3763                                   if (first.sendTime) {
3764                                     SendTimeRemaining(&first, FALSE);
3765                                   }
3766                                   SendToProgram("playother\n", &first);
3767                                   firstMove = FALSE;
3768                                 } else {
3769                                   firstMove = TRUE;
3770                                 }
3771                             }
3772                         }
3773                     }
3774 #endif
3775                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3776                         /* Moves came from oldmoves or moves command
3777                            while we weren't doing anything else.
3778                            */
3779                         currentMove = forwardMostMove;
3780                         ClearHighlights();/*!!could figure this out*/
3781                         flipView = appData.flipView;
3782                         DrawPosition(TRUE, boards[currentMove]);
3783                         DisplayBothClocks();
3784                         snprintf(str, MSG_SIZ, "%s %s %s",
3785                                 gameInfo.white, _("vs."),  gameInfo.black);
3786                         DisplayTitle(str);
3787                         gameMode = IcsIdle;
3788                     } else {
3789                         /* Moves were history of an active game */
3790                         if (gameInfo.resultDetails != NULL) {
3791                             free(gameInfo.resultDetails);
3792                             gameInfo.resultDetails = NULL;
3793                         }
3794                     }
3795                     HistorySet(parseList, backwardMostMove,
3796                                forwardMostMove, currentMove-1);
3797                     DisplayMove(currentMove - 1);
3798                     if (started == STARTED_MOVES) next_out = i;
3799                     started = STARTED_NONE;
3800                     ics_getting_history = H_FALSE;
3801                     break;
3802
3803                   case STARTED_OBSERVE:
3804                     started = STARTED_NONE;
3805                     SendToICS(ics_prefix);
3806                     SendToICS("refresh\n");
3807                     break;
3808
3809                   default:
3810                     break;
3811                 }
3812                 if(bookHit) { // [HGM] book: simulate book reply
3813                     static char bookMove[MSG_SIZ]; // a bit generous?
3814
3815                     programStats.nodes = programStats.depth = programStats.time =
3816                     programStats.score = programStats.got_only_move = 0;
3817                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3818
3819                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3820                     strcat(bookMove, bookHit);
3821                     HandleMachineMove(bookMove, &first);
3822                 }
3823                 continue;
3824             }
3825
3826             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3827                  started == STARTED_HOLDINGS ||
3828                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3829                 /* Accumulate characters in move list or board */
3830                 parse[parse_pos++] = buf[i];
3831             }
3832
3833             /* Start of game messages.  Mostly we detect start of game
3834                when the first board image arrives.  On some versions
3835                of the ICS, though, we need to do a "refresh" after starting
3836                to observe in order to get the current board right away. */
3837             if (looking_at(buf, &i, "Adding game * to observation list")) {
3838                 started = STARTED_OBSERVE;
3839                 continue;
3840             }
3841
3842             /* Handle auto-observe */
3843             if (appData.autoObserve &&
3844                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3845                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3846                 char *player;
3847                 /* Choose the player that was highlighted, if any. */
3848                 if (star_match[0][0] == '\033' ||
3849                     star_match[1][0] != '\033') {
3850                     player = star_match[0];
3851                 } else {
3852                     player = star_match[2];
3853                 }
3854                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3855                         ics_prefix, StripHighlightAndTitle(player));
3856                 SendToICS(str);
3857
3858                 /* Save ratings from notify string */
3859                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3860                 player1Rating = string_to_rating(star_match[1]);
3861                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3862                 player2Rating = string_to_rating(star_match[3]);
3863
3864                 if (appData.debugMode)
3865                   fprintf(debugFP,
3866                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3867                           player1Name, player1Rating,
3868                           player2Name, player2Rating);
3869
3870                 continue;
3871             }
3872
3873             /* Deal with automatic examine mode after a game,
3874                and with IcsObserving -> IcsExamining transition */
3875             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3876                 looking_at(buf, &i, "has made you an examiner of game *")) {
3877
3878                 int gamenum = atoi(star_match[0]);
3879                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3880                     gamenum == ics_gamenum) {
3881                     /* We were already playing or observing this game;
3882                        no need to refetch history */
3883                     gameMode = IcsExamining;
3884                     if (pausing) {
3885                         pauseExamForwardMostMove = forwardMostMove;
3886                     } else if (currentMove < forwardMostMove) {
3887                         ForwardInner(forwardMostMove);
3888                     }
3889                 } else {
3890                     /* I don't think this case really can happen */
3891                     SendToICS(ics_prefix);
3892                     SendToICS("refresh\n");
3893                 }
3894                 continue;
3895             }
3896
3897             /* Error messages */
3898 //          if (ics_user_moved) {
3899             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3900                 if (looking_at(buf, &i, "Illegal move") ||
3901                     looking_at(buf, &i, "Not a legal move") ||
3902                     looking_at(buf, &i, "Your king is in check") ||
3903                     looking_at(buf, &i, "It isn't your turn") ||
3904                     looking_at(buf, &i, "It is not your move")) {
3905                     /* Illegal move */
3906                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3907                         currentMove = forwardMostMove-1;
3908                         DisplayMove(currentMove - 1); /* before DMError */
3909                         DrawPosition(FALSE, boards[currentMove]);
3910                         SwitchClocks(forwardMostMove-1); // [HGM] race
3911                         DisplayBothClocks();
3912                     }
3913                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3914                     ics_user_moved = 0;
3915                     continue;
3916                 }
3917             }
3918
3919             if (looking_at(buf, &i, "still have time") ||
3920                 looking_at(buf, &i, "not out of time") ||
3921                 looking_at(buf, &i, "either player is out of time") ||
3922                 looking_at(buf, &i, "has timeseal; checking")) {
3923                 /* We must have called his flag a little too soon */
3924                 whiteFlag = blackFlag = FALSE;
3925                 continue;
3926             }
3927
3928             if (looking_at(buf, &i, "added * seconds to") ||
3929                 looking_at(buf, &i, "seconds were added to")) {
3930                 /* Update the clocks */
3931                 SendToICS(ics_prefix);
3932                 SendToICS("refresh\n");
3933                 continue;
3934             }
3935
3936             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3937                 ics_clock_paused = TRUE;
3938                 StopClocks();
3939                 continue;
3940             }
3941
3942             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3943                 ics_clock_paused = FALSE;
3944                 StartClocks();
3945                 continue;
3946             }
3947
3948             /* Grab player ratings from the Creating: message.
3949                Note we have to check for the special case when
3950                the ICS inserts things like [white] or [black]. */
3951             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3952                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3953                 /* star_matches:
3954                    0    player 1 name (not necessarily white)
3955                    1    player 1 rating
3956                    2    empty, white, or black (IGNORED)
3957                    3    player 2 name (not necessarily black)
3958                    4    player 2 rating
3959
3960                    The names/ratings are sorted out when the game
3961                    actually starts (below).
3962                 */
3963                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3964                 player1Rating = string_to_rating(star_match[1]);
3965                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3966                 player2Rating = string_to_rating(star_match[4]);
3967
3968                 if (appData.debugMode)
3969                   fprintf(debugFP,
3970                           "Ratings from 'Creating:' %s %d, %s %d\n",
3971                           player1Name, player1Rating,
3972                           player2Name, player2Rating);
3973
3974                 continue;
3975             }
3976
3977             /* Improved generic start/end-of-game messages */
3978             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3979                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3980                 /* If tkind == 0: */
3981                 /* star_match[0] is the game number */
3982                 /*           [1] is the white player's name */
3983                 /*           [2] is the black player's name */
3984                 /* For end-of-game: */
3985                 /*           [3] is the reason for the game end */
3986                 /*           [4] is a PGN end game-token, preceded by " " */
3987                 /* For start-of-game: */
3988                 /*           [3] begins with "Creating" or "Continuing" */
3989                 /*           [4] is " *" or empty (don't care). */
3990                 int gamenum = atoi(star_match[0]);
3991                 char *whitename, *blackname, *why, *endtoken;
3992                 ChessMove endtype = EndOfFile;
3993
3994                 if (tkind == 0) {
3995                   whitename = star_match[1];
3996                   blackname = star_match[2];
3997                   why = star_match[3];
3998                   endtoken = star_match[4];
3999                 } else {
4000                   whitename = star_match[1];
4001                   blackname = star_match[3];
4002                   why = star_match[5];
4003                   endtoken = star_match[6];
4004                 }
4005
4006                 /* Game start messages */
4007                 if (strncmp(why, "Creating ", 9) == 0 ||
4008                     strncmp(why, "Continuing ", 11) == 0) {
4009                     gs_gamenum = gamenum;
4010                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4011                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4012                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4013 #if ZIPPY
4014                     if (appData.zippyPlay) {
4015                         ZippyGameStart(whitename, blackname);
4016                     }
4017 #endif /*ZIPPY*/
4018                     partnerBoardValid = FALSE; // [HGM] bughouse
4019                     continue;
4020                 }
4021
4022                 /* Game end messages */
4023                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4024                     ics_gamenum != gamenum) {
4025                     continue;
4026                 }
4027                 while (endtoken[0] == ' ') endtoken++;
4028                 switch (endtoken[0]) {
4029                   case '*':
4030                   default:
4031                     endtype = GameUnfinished;
4032                     break;
4033                   case '0':
4034                     endtype = BlackWins;
4035                     break;
4036                   case '1':
4037                     if (endtoken[1] == '/')
4038                       endtype = GameIsDrawn;
4039                     else
4040                       endtype = WhiteWins;
4041                     break;
4042                 }
4043                 GameEnds(endtype, why, GE_ICS);
4044 #if ZIPPY
4045                 if (appData.zippyPlay && first.initDone) {
4046                     ZippyGameEnd(endtype, why);
4047                     if (first.pr == NoProc) {
4048                       /* Start the next process early so that we'll
4049                          be ready for the next challenge */
4050                       StartChessProgram(&first);
4051                     }
4052                     /* Send "new" early, in case this command takes
4053                        a long time to finish, so that we'll be ready
4054                        for the next challenge. */
4055                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4056                     Reset(TRUE, TRUE);
4057                 }
4058 #endif /*ZIPPY*/
4059                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4060                 continue;
4061             }
4062
4063             if (looking_at(buf, &i, "Removing game * from observation") ||
4064                 looking_at(buf, &i, "no longer observing game *") ||
4065                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4066                 if (gameMode == IcsObserving &&
4067                     atoi(star_match[0]) == ics_gamenum)
4068                   {
4069                       /* icsEngineAnalyze */
4070                       if (appData.icsEngineAnalyze) {
4071                             ExitAnalyzeMode();
4072                             ModeHighlight();
4073                       }
4074                       StopClocks();
4075                       gameMode = IcsIdle;
4076                       ics_gamenum = -1;
4077                       ics_user_moved = FALSE;
4078                   }
4079                 continue;
4080             }
4081
4082             if (looking_at(buf, &i, "no longer examining game *")) {
4083                 if (gameMode == IcsExamining &&
4084                     atoi(star_match[0]) == ics_gamenum)
4085                   {
4086                       gameMode = IcsIdle;
4087                       ics_gamenum = -1;
4088                       ics_user_moved = FALSE;
4089                   }
4090                 continue;
4091             }
4092
4093             /* Advance leftover_start past any newlines we find,
4094                so only partial lines can get reparsed */
4095             if (looking_at(buf, &i, "\n")) {
4096                 prevColor = curColor;
4097                 if (curColor != ColorNormal) {
4098                     if (oldi > next_out) {
4099                         SendToPlayer(&buf[next_out], oldi - next_out);
4100                         next_out = oldi;
4101                     }
4102                     Colorize(ColorNormal, FALSE);
4103                     curColor = ColorNormal;
4104                 }
4105                 if (started == STARTED_BOARD) {
4106                     started = STARTED_NONE;
4107                     parse[parse_pos] = NULLCHAR;
4108                     ParseBoard12(parse);
4109                     ics_user_moved = 0;
4110
4111                     /* Send premove here */
4112                     if (appData.premove) {
4113                       char str[MSG_SIZ];
4114                       if (currentMove == 0 &&
4115                           gameMode == IcsPlayingWhite &&
4116                           appData.premoveWhite) {
4117                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4118                         if (appData.debugMode)
4119                           fprintf(debugFP, "Sending premove:\n");
4120                         SendToICS(str);
4121                       } else if (currentMove == 1 &&
4122                                  gameMode == IcsPlayingBlack &&
4123                                  appData.premoveBlack) {
4124                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4125                         if (appData.debugMode)
4126                           fprintf(debugFP, "Sending premove:\n");
4127                         SendToICS(str);
4128                       } else if (gotPremove) {
4129                         gotPremove = 0;
4130                         ClearPremoveHighlights();
4131                         if (appData.debugMode)
4132                           fprintf(debugFP, "Sending premove:\n");
4133                           UserMoveEvent(premoveFromX, premoveFromY,
4134                                         premoveToX, premoveToY,
4135                                         premovePromoChar);
4136                       }
4137                     }
4138
4139                     /* Usually suppress following prompt */
4140                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4141                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4142                         if (looking_at(buf, &i, "*% ")) {
4143                             savingComment = FALSE;
4144                             suppressKibitz = 0;
4145                         }
4146                     }
4147                     next_out = i;
4148                 } else if (started == STARTED_HOLDINGS) {
4149                     int gamenum;
4150                     char new_piece[MSG_SIZ];
4151                     started = STARTED_NONE;
4152                     parse[parse_pos] = NULLCHAR;
4153                     if (appData.debugMode)
4154                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4155                                                         parse, currentMove);
4156                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4157                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4158                         if (gameInfo.variant == VariantNormal) {
4159                           /* [HGM] We seem to switch variant during a game!
4160                            * Presumably no holdings were displayed, so we have
4161                            * to move the position two files to the right to
4162                            * create room for them!
4163                            */
4164                           VariantClass newVariant;
4165                           switch(gameInfo.boardWidth) { // base guess on board width
4166                                 case 9:  newVariant = VariantShogi; break;
4167                                 case 10: newVariant = VariantGreat; break;
4168                                 default: newVariant = VariantCrazyhouse; break;
4169                           }
4170                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4171                           /* Get a move list just to see the header, which
4172                              will tell us whether this is really bug or zh */
4173                           if (ics_getting_history == H_FALSE) {
4174                             ics_getting_history = H_REQUESTED;
4175                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4176                             SendToICS(str);
4177                           }
4178                         }
4179                         new_piece[0] = NULLCHAR;
4180                         sscanf(parse, "game %d white [%s black [%s <- %s",
4181                                &gamenum, white_holding, black_holding,
4182                                new_piece);
4183                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4184                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4185                         /* [HGM] copy holdings to board holdings area */
4186                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4187                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4188                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4189 #if ZIPPY
4190                         if (appData.zippyPlay && first.initDone) {
4191                             ZippyHoldings(white_holding, black_holding,
4192                                           new_piece);
4193                         }
4194 #endif /*ZIPPY*/
4195                         if (tinyLayout || smallLayout) {
4196                             char wh[16], bh[16];
4197                             PackHolding(wh, white_holding);
4198                             PackHolding(bh, black_holding);
4199                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4200                                     gameInfo.white, gameInfo.black);
4201                         } else {
4202                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4203                                     gameInfo.white, white_holding, _("vs."),
4204                                     gameInfo.black, black_holding);
4205                         }
4206                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4207                         DrawPosition(FALSE, boards[currentMove]);
4208                         DisplayTitle(str);
4209                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4210                         sscanf(parse, "game %d white [%s black [%s <- %s",
4211                                &gamenum, white_holding, black_holding,
4212                                new_piece);
4213                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4214                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4215                         /* [HGM] copy holdings to partner-board holdings area */
4216                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4217                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4218                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4219                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4220                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4221                       }
4222                     }
4223                     /* Suppress following prompt */
4224                     if (looking_at(buf, &i, "*% ")) {
4225                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4226                         savingComment = FALSE;
4227                         suppressKibitz = 0;
4228                     }
4229                     next_out = i;
4230                 }
4231                 continue;
4232             }
4233
4234             i++;                /* skip unparsed character and loop back */
4235         }
4236
4237         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4238 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4239 //          SendToPlayer(&buf[next_out], i - next_out);
4240             started != STARTED_HOLDINGS && leftover_start > next_out) {
4241             SendToPlayer(&buf[next_out], leftover_start - next_out);
4242             next_out = i;
4243         }
4244
4245         leftover_len = buf_len - leftover_start;
4246         /* if buffer ends with something we couldn't parse,
4247            reparse it after appending the next read */
4248
4249     } else if (count == 0) {
4250         RemoveInputSource(isr);
4251         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4252     } else {
4253         DisplayFatalError(_("Error reading from ICS"), error, 1);
4254     }
4255 }
4256
4257
4258 /* Board style 12 looks like this:
4259
4260    <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
4261
4262  * The "<12> " is stripped before it gets to this routine.  The two
4263  * trailing 0's (flip state and clock ticking) are later addition, and
4264  * some chess servers may not have them, or may have only the first.
4265  * Additional trailing fields may be added in the future.
4266  */
4267
4268 #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"
4269
4270 #define RELATION_OBSERVING_PLAYED    0
4271 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4272 #define RELATION_PLAYING_MYMOVE      1
4273 #define RELATION_PLAYING_NOTMYMOVE  -1
4274 #define RELATION_EXAMINING           2
4275 #define RELATION_ISOLATED_BOARD     -3
4276 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4277
4278 void
4279 ParseBoard12 (char *string)
4280 {
4281 #if ZIPPY
4282     int i, takeback;
4283     char *bookHit = NULL; // [HGM] book
4284 #endif
4285     GameMode newGameMode;
4286     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4287     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4288     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4289     char to_play, board_chars[200];
4290     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4291     char black[32], white[32];
4292     Board board;
4293     int prevMove = currentMove;
4294     int ticking = 2;
4295     ChessMove moveType;
4296     int fromX, fromY, toX, toY;
4297     char promoChar;
4298     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4299     Boolean weird = FALSE, reqFlag = FALSE;
4300
4301     fromX = fromY = toX = toY = -1;
4302
4303     newGame = FALSE;
4304
4305     if (appData.debugMode)
4306       fprintf(debugFP, "Parsing board: %s\n", string);
4307
4308     move_str[0] = NULLCHAR;
4309     elapsed_time[0] = NULLCHAR;
4310     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4311         int  i = 0, j;
4312         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4313             if(string[i] == ' ') { ranks++; files = 0; }
4314             else files++;
4315             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4316             i++;
4317         }
4318         for(j = 0; j <i; j++) board_chars[j] = string[j];
4319         board_chars[i] = '\0';
4320         string += i + 1;
4321     }
4322     n = sscanf(string, PATTERN, &to_play, &double_push,
4323                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4324                &gamenum, white, black, &relation, &basetime, &increment,
4325                &white_stren, &black_stren, &white_time, &black_time,
4326                &moveNum, str, elapsed_time, move_str, &ics_flip,
4327                &ticking);
4328
4329     if (n < 21) {
4330         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4331         DisplayError(str, 0);
4332         return;
4333     }
4334
4335     /* Convert the move number to internal form */
4336     moveNum = (moveNum - 1) * 2;
4337     if (to_play == 'B') moveNum++;
4338     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4339       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4340                         0, 1);
4341       return;
4342     }
4343
4344     switch (relation) {
4345       case RELATION_OBSERVING_PLAYED:
4346       case RELATION_OBSERVING_STATIC:
4347         if (gamenum == -1) {
4348             /* Old ICC buglet */
4349             relation = RELATION_OBSERVING_STATIC;
4350         }
4351         newGameMode = IcsObserving;
4352         break;
4353       case RELATION_PLAYING_MYMOVE:
4354       case RELATION_PLAYING_NOTMYMOVE:
4355         newGameMode =
4356           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4357             IcsPlayingWhite : IcsPlayingBlack;
4358         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4359         break;
4360       case RELATION_EXAMINING:
4361         newGameMode = IcsExamining;
4362         break;
4363       case RELATION_ISOLATED_BOARD:
4364       default:
4365         /* Just display this board.  If user was doing something else,
4366            we will forget about it until the next board comes. */
4367         newGameMode = IcsIdle;
4368         break;
4369       case RELATION_STARTING_POSITION:
4370         newGameMode = gameMode;
4371         break;
4372     }
4373
4374     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4375         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4376          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4377       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4378       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4379       static int lastBgGame = -1;
4380       char *toSqr;
4381       for (k = 0; k < ranks; k++) {
4382         for (j = 0; j < files; j++)
4383           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4384         if(gameInfo.holdingsWidth > 1) {
4385              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4386              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4387         }
4388       }
4389       CopyBoard(partnerBoard, board);
4390       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4391         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4392         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4393       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4394       if(toSqr = strchr(str, '-')) {
4395         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4396         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4397       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4398       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4399       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4400       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4401       if(twoBoards) {
4402           DisplayWhiteClock(white_time*fac, to_play == 'W');
4403           DisplayBlackClock(black_time*fac, to_play != 'W');
4404           activePartner = to_play;
4405           if(gamenum != lastBgGame) {
4406               char buf[MSG_SIZ];
4407               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4408               DisplayTitle(buf);
4409           }
4410           lastBgGame = gamenum;
4411           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4412                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4413       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4414                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4415       if(!twoBoards) DisplayMessage(partnerStatus, "");
4416         partnerBoardValid = TRUE;
4417       return;
4418     }
4419
4420     if(appData.dualBoard && appData.bgObserve) {
4421         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4422             SendToICS(ics_prefix), SendToICS("pobserve\n");
4423         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4424             char buf[MSG_SIZ];
4425             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4426             SendToICS(buf);
4427         }
4428     }
4429
4430     /* Modify behavior for initial board display on move listing
4431        of wild games.
4432        */
4433     switch (ics_getting_history) {
4434       case H_FALSE:
4435       case H_REQUESTED:
4436         break;
4437       case H_GOT_REQ_HEADER:
4438       case H_GOT_UNREQ_HEADER:
4439         /* This is the initial position of the current game */
4440         gamenum = ics_gamenum;
4441         moveNum = 0;            /* old ICS bug workaround */
4442         if (to_play == 'B') {
4443           startedFromSetupPosition = TRUE;
4444           blackPlaysFirst = TRUE;
4445           moveNum = 1;
4446           if (forwardMostMove == 0) forwardMostMove = 1;
4447           if (backwardMostMove == 0) backwardMostMove = 1;
4448           if (currentMove == 0) currentMove = 1;
4449         }
4450         newGameMode = gameMode;
4451         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4452         break;
4453       case H_GOT_UNWANTED_HEADER:
4454         /* This is an initial board that we don't want */
4455         return;
4456       case H_GETTING_MOVES:
4457         /* Should not happen */
4458         DisplayError(_("Error gathering move list: extra board"), 0);
4459         ics_getting_history = H_FALSE;
4460         return;
4461     }
4462
4463    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4464                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4465                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4466      /* [HGM] We seem to have switched variant unexpectedly
4467       * Try to guess new variant from board size
4468       */
4469           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4470           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4471           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4472           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4473           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4474           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4475           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4476           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4477           /* Get a move list just to see the header, which
4478              will tell us whether this is really bug or zh */
4479           if (ics_getting_history == H_FALSE) {
4480             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4481             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4482             SendToICS(str);
4483           }
4484     }
4485
4486     /* Take action if this is the first board of a new game, or of a
4487        different game than is currently being displayed.  */
4488     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4489         relation == RELATION_ISOLATED_BOARD) {
4490
4491         /* Forget the old game and get the history (if any) of the new one */
4492         if (gameMode != BeginningOfGame) {
4493           Reset(TRUE, TRUE);
4494         }
4495         newGame = TRUE;
4496         if (appData.autoRaiseBoard) BoardToTop();
4497         prevMove = -3;
4498         if (gamenum == -1) {
4499             newGameMode = IcsIdle;
4500         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4501                    appData.getMoveList && !reqFlag) {
4502             /* Need to get game history */
4503             ics_getting_history = H_REQUESTED;
4504             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4505             SendToICS(str);
4506         }
4507
4508         /* Initially flip the board to have black on the bottom if playing
4509            black or if the ICS flip flag is set, but let the user change
4510            it with the Flip View button. */
4511         flipView = appData.autoFlipView ?
4512           (newGameMode == IcsPlayingBlack) || ics_flip :
4513           appData.flipView;
4514
4515         /* Done with values from previous mode; copy in new ones */
4516         gameMode = newGameMode;
4517         ModeHighlight();
4518         ics_gamenum = gamenum;
4519         if (gamenum == gs_gamenum) {
4520             int klen = strlen(gs_kind);
4521             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4522             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4523             gameInfo.event = StrSave(str);
4524         } else {
4525             gameInfo.event = StrSave("ICS game");
4526         }
4527         gameInfo.site = StrSave(appData.icsHost);
4528         gameInfo.date = PGNDate();
4529         gameInfo.round = StrSave("-");
4530         gameInfo.white = StrSave(white);
4531         gameInfo.black = StrSave(black);
4532         timeControl = basetime * 60 * 1000;
4533         timeControl_2 = 0;
4534         timeIncrement = increment * 1000;
4535         movesPerSession = 0;
4536         gameInfo.timeControl = TimeControlTagValue();
4537         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4538   if (appData.debugMode) {
4539     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4540     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4541     setbuf(debugFP, NULL);
4542   }
4543
4544         gameInfo.outOfBook = NULL;
4545
4546         /* Do we have the ratings? */
4547         if (strcmp(player1Name, white) == 0 &&
4548             strcmp(player2Name, black) == 0) {
4549             if (appData.debugMode)
4550               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4551                       player1Rating, player2Rating);
4552             gameInfo.whiteRating = player1Rating;
4553             gameInfo.blackRating = player2Rating;
4554         } else if (strcmp(player2Name, white) == 0 &&
4555                    strcmp(player1Name, black) == 0) {
4556             if (appData.debugMode)
4557               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4558                       player2Rating, player1Rating);
4559             gameInfo.whiteRating = player2Rating;
4560             gameInfo.blackRating = player1Rating;
4561         }
4562         player1Name[0] = player2Name[0] = NULLCHAR;
4563
4564         /* Silence shouts if requested */
4565         if (appData.quietPlay &&
4566             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4567             SendToICS(ics_prefix);
4568             SendToICS("set shout 0\n");
4569         }
4570     }
4571
4572     /* Deal with midgame name changes */
4573     if (!newGame) {
4574         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4575             if (gameInfo.white) free(gameInfo.white);
4576             gameInfo.white = StrSave(white);
4577         }
4578         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4579             if (gameInfo.black) free(gameInfo.black);
4580             gameInfo.black = StrSave(black);
4581         }
4582     }
4583
4584     /* Throw away game result if anything actually changes in examine mode */
4585     if (gameMode == IcsExamining && !newGame) {
4586         gameInfo.result = GameUnfinished;
4587         if (gameInfo.resultDetails != NULL) {
4588             free(gameInfo.resultDetails);
4589             gameInfo.resultDetails = NULL;
4590         }
4591     }
4592
4593     /* In pausing && IcsExamining mode, we ignore boards coming
4594        in if they are in a different variation than we are. */
4595     if (pauseExamInvalid) return;
4596     if (pausing && gameMode == IcsExamining) {
4597         if (moveNum <= pauseExamForwardMostMove) {
4598             pauseExamInvalid = TRUE;
4599             forwardMostMove = pauseExamForwardMostMove;
4600             return;
4601         }
4602     }
4603
4604   if (appData.debugMode) {
4605     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4606   }
4607     /* Parse the board */
4608     for (k = 0; k < ranks; k++) {
4609       for (j = 0; j < files; j++)
4610         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4611       if(gameInfo.holdingsWidth > 1) {
4612            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4613            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4614       }
4615     }
4616     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4617       board[5][BOARD_RGHT+1] = WhiteAngel;
4618       board[6][BOARD_RGHT+1] = WhiteMarshall;
4619       board[1][0] = BlackMarshall;
4620       board[2][0] = BlackAngel;
4621       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4622     }
4623     CopyBoard(boards[moveNum], board);
4624     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4625     if (moveNum == 0) {
4626         startedFromSetupPosition =
4627           !CompareBoards(board, initialPosition);
4628         if(startedFromSetupPosition)
4629             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4630     }
4631
4632     /* [HGM] Set castling rights. Take the outermost Rooks,
4633        to make it also work for FRC opening positions. Note that board12
4634        is really defective for later FRC positions, as it has no way to
4635        indicate which Rook can castle if they are on the same side of King.
4636        For the initial position we grant rights to the outermost Rooks,
4637        and remember thos rights, and we then copy them on positions
4638        later in an FRC game. This means WB might not recognize castlings with
4639        Rooks that have moved back to their original position as illegal,
4640        but in ICS mode that is not its job anyway.
4641     */
4642     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4643     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4644
4645         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4646             if(board[0][i] == WhiteRook) j = i;
4647         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4648         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4649             if(board[0][i] == WhiteRook) j = i;
4650         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4651         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4652             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4653         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4654         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4655             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4656         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4657
4658         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4659         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4660         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4661             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4662         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4663             if(board[BOARD_HEIGHT-1][k] == bKing)
4664                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4665         if(gameInfo.variant == VariantTwoKings) {
4666             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4667             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4668             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4669         }
4670     } else { int r;
4671         r = boards[moveNum][CASTLING][0] = initialRights[0];
4672         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4673         r = boards[moveNum][CASTLING][1] = initialRights[1];
4674         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4675         r = boards[moveNum][CASTLING][3] = initialRights[3];
4676         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4677         r = boards[moveNum][CASTLING][4] = initialRights[4];
4678         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4679         /* wildcastle kludge: always assume King has rights */
4680         r = boards[moveNum][CASTLING][2] = initialRights[2];
4681         r = boards[moveNum][CASTLING][5] = initialRights[5];
4682     }
4683     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4684     boards[moveNum][EP_STATUS] = EP_NONE;
4685     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4686     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4687     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4688
4689
4690     if (ics_getting_history == H_GOT_REQ_HEADER ||
4691         ics_getting_history == H_GOT_UNREQ_HEADER) {
4692         /* This was an initial position from a move list, not
4693            the current position */
4694         return;
4695     }
4696
4697     /* Update currentMove and known move number limits */
4698     newMove = newGame || moveNum > forwardMostMove;
4699
4700     if (newGame) {
4701         forwardMostMove = backwardMostMove = currentMove = moveNum;
4702         if (gameMode == IcsExamining && moveNum == 0) {
4703           /* Workaround for ICS limitation: we are not told the wild
4704              type when starting to examine a game.  But if we ask for
4705              the move list, the move list header will tell us */
4706             ics_getting_history = H_REQUESTED;
4707             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4708             SendToICS(str);
4709         }
4710     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4711                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4712 #if ZIPPY
4713         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4714         /* [HGM] applied this also to an engine that is silently watching        */
4715         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4716             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4717             gameInfo.variant == currentlyInitializedVariant) {
4718           takeback = forwardMostMove - moveNum;
4719           for (i = 0; i < takeback; i++) {
4720             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4721             SendToProgram("undo\n", &first);
4722           }
4723         }
4724 #endif
4725
4726         forwardMostMove = moveNum;
4727         if (!pausing || currentMove > forwardMostMove)
4728           currentMove = forwardMostMove;
4729     } else {
4730         /* New part of history that is not contiguous with old part */
4731         if (pausing && gameMode == IcsExamining) {
4732             pauseExamInvalid = TRUE;
4733             forwardMostMove = pauseExamForwardMostMove;
4734             return;
4735         }
4736         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4737 #if ZIPPY
4738             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4739                 // [HGM] when we will receive the move list we now request, it will be
4740                 // fed to the engine from the first move on. So if the engine is not
4741                 // in the initial position now, bring it there.
4742                 InitChessProgram(&first, 0);
4743             }
4744 #endif
4745             ics_getting_history = H_REQUESTED;
4746             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4747             SendToICS(str);
4748         }
4749         forwardMostMove = backwardMostMove = currentMove = moveNum;
4750     }
4751
4752     /* Update the clocks */
4753     if (strchr(elapsed_time, '.')) {
4754       /* Time is in ms */
4755       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4756       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4757     } else {
4758       /* Time is in seconds */
4759       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4760       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4761     }
4762
4763
4764 #if ZIPPY
4765     if (appData.zippyPlay && newGame &&
4766         gameMode != IcsObserving && gameMode != IcsIdle &&
4767         gameMode != IcsExamining)
4768       ZippyFirstBoard(moveNum, basetime, increment);
4769 #endif
4770
4771     /* Put the move on the move list, first converting
4772        to canonical algebraic form. */
4773     if (moveNum > 0) {
4774   if (appData.debugMode) {
4775     int f = forwardMostMove;
4776     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4777             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4778             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4779     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4780     fprintf(debugFP, "moveNum = %d\n", moveNum);
4781     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4782     setbuf(debugFP, NULL);
4783   }
4784         if (moveNum <= backwardMostMove) {
4785             /* We don't know what the board looked like before
4786                this move.  Punt. */
4787           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4788             strcat(parseList[moveNum - 1], " ");
4789             strcat(parseList[moveNum - 1], elapsed_time);
4790             moveList[moveNum - 1][0] = NULLCHAR;
4791         } else if (strcmp(move_str, "none") == 0) {
4792             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4793             /* Again, we don't know what the board looked like;
4794                this is really the start of the game. */
4795             parseList[moveNum - 1][0] = NULLCHAR;
4796             moveList[moveNum - 1][0] = NULLCHAR;
4797             backwardMostMove = moveNum;
4798             startedFromSetupPosition = TRUE;
4799             fromX = fromY = toX = toY = -1;
4800         } else {
4801           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4802           //                 So we parse the long-algebraic move string in stead of the SAN move
4803           int valid; char buf[MSG_SIZ], *prom;
4804
4805           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4806                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4807           // str looks something like "Q/a1-a2"; kill the slash
4808           if(str[1] == '/')
4809             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4810           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4811           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4812                 strcat(buf, prom); // long move lacks promo specification!
4813           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4814                 if(appData.debugMode)
4815                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4816                 safeStrCpy(move_str, buf, MSG_SIZ);
4817           }
4818           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4819                                 &fromX, &fromY, &toX, &toY, &promoChar)
4820                || ParseOneMove(buf, moveNum - 1, &moveType,
4821                                 &fromX, &fromY, &toX, &toY, &promoChar);
4822           // end of long SAN patch
4823           if (valid) {
4824             (void) CoordsToAlgebraic(boards[moveNum - 1],
4825                                      PosFlags(moveNum - 1),
4826                                      fromY, fromX, toY, toX, promoChar,
4827                                      parseList[moveNum-1]);
4828             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4829               case MT_NONE:
4830               case MT_STALEMATE:
4831               default:
4832                 break;
4833               case MT_CHECK:
4834                 if(!IS_SHOGI(gameInfo.variant))
4835                     strcat(parseList[moveNum - 1], "+");
4836                 break;
4837               case MT_CHECKMATE:
4838               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4839                 strcat(parseList[moveNum - 1], "#");
4840                 break;
4841             }
4842             strcat(parseList[moveNum - 1], " ");
4843             strcat(parseList[moveNum - 1], elapsed_time);
4844             /* currentMoveString is set as a side-effect of ParseOneMove */
4845             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4846             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4847             strcat(moveList[moveNum - 1], "\n");
4848
4849             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4850                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4851               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4852                 ChessSquare old, new = boards[moveNum][k][j];
4853                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4854                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4855                   if(old == new) continue;
4856                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4857                   else if(new == WhiteWazir || new == BlackWazir) {
4858                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4859                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4860                       else boards[moveNum][k][j] = old; // preserve type of Gold
4861                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4862                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4863               }
4864           } else {
4865             /* Move from ICS was illegal!?  Punt. */
4866             if (appData.debugMode) {
4867               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4868               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4869             }
4870             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4871             strcat(parseList[moveNum - 1], " ");
4872             strcat(parseList[moveNum - 1], elapsed_time);
4873             moveList[moveNum - 1][0] = NULLCHAR;
4874             fromX = fromY = toX = toY = -1;
4875           }
4876         }
4877   if (appData.debugMode) {
4878     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4879     setbuf(debugFP, NULL);
4880   }
4881
4882 #if ZIPPY
4883         /* Send move to chess program (BEFORE animating it). */
4884         if (appData.zippyPlay && !newGame && newMove &&
4885            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4886
4887             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4888                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4889                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4890                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4891                             move_str);
4892                     DisplayError(str, 0);
4893                 } else {
4894                     if (first.sendTime) {
4895                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4896                     }
4897                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4898                     if (firstMove && !bookHit) {
4899                         firstMove = FALSE;
4900                         if (first.useColors) {
4901                           SendToProgram(gameMode == IcsPlayingWhite ?
4902                                         "white\ngo\n" :
4903                                         "black\ngo\n", &first);
4904                         } else {
4905                           SendToProgram("go\n", &first);
4906                         }
4907                         first.maybeThinking = TRUE;
4908                     }
4909                 }
4910             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4911               if (moveList[moveNum - 1][0] == NULLCHAR) {
4912                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4913                 DisplayError(str, 0);
4914               } else {
4915                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4916                 SendMoveToProgram(moveNum - 1, &first);
4917               }
4918             }
4919         }
4920 #endif
4921     }
4922
4923     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4924         /* If move comes from a remote source, animate it.  If it
4925            isn't remote, it will have already been animated. */
4926         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4927             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4928         }
4929         if (!pausing && appData.highlightLastMove) {
4930             SetHighlights(fromX, fromY, toX, toY);
4931         }
4932     }
4933
4934     /* Start the clocks */
4935     whiteFlag = blackFlag = FALSE;
4936     appData.clockMode = !(basetime == 0 && increment == 0);
4937     if (ticking == 0) {
4938       ics_clock_paused = TRUE;
4939       StopClocks();
4940     } else if (ticking == 1) {
4941       ics_clock_paused = FALSE;
4942     }
4943     if (gameMode == IcsIdle ||
4944         relation == RELATION_OBSERVING_STATIC ||
4945         relation == RELATION_EXAMINING ||
4946         ics_clock_paused)
4947       DisplayBothClocks();
4948     else
4949       StartClocks();
4950
4951     /* Display opponents and material strengths */
4952     if (gameInfo.variant != VariantBughouse &&
4953         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4954         if (tinyLayout || smallLayout) {
4955             if(gameInfo.variant == VariantNormal)
4956               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4957                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4958                     basetime, increment);
4959             else
4960               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4961                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4962                     basetime, increment, (int) gameInfo.variant);
4963         } else {
4964             if(gameInfo.variant == VariantNormal)
4965               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4966                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4967                     basetime, increment);
4968             else
4969               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4970                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4971                     basetime, increment, VariantName(gameInfo.variant));
4972         }
4973         DisplayTitle(str);
4974   if (appData.debugMode) {
4975     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4976   }
4977     }
4978
4979
4980     /* Display the board */
4981     if (!pausing && !appData.noGUI) {
4982
4983       if (appData.premove)
4984           if (!gotPremove ||
4985              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4986              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4987               ClearPremoveHighlights();
4988
4989       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4990         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4991       DrawPosition(j, boards[currentMove]);
4992
4993       DisplayMove(moveNum - 1);
4994       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4995             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4996               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4997         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4998       }
4999     }
5000
5001     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5002 #if ZIPPY
5003     if(bookHit) { // [HGM] book: simulate book reply
5004         static char bookMove[MSG_SIZ]; // a bit generous?
5005
5006         programStats.nodes = programStats.depth = programStats.time =
5007         programStats.score = programStats.got_only_move = 0;
5008         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5009
5010         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5011         strcat(bookMove, bookHit);
5012         HandleMachineMove(bookMove, &first);
5013     }
5014 #endif
5015 }
5016
5017 void
5018 GetMoveListEvent ()
5019 {
5020     char buf[MSG_SIZ];
5021     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5022         ics_getting_history = H_REQUESTED;
5023         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5024         SendToICS(buf);
5025     }
5026 }
5027
5028 void
5029 SendToBoth (char *msg)
5030 {   // to make it easy to keep two engines in step in dual analysis
5031     SendToProgram(msg, &first);
5032     if(second.analyzing) SendToProgram(msg, &second);
5033 }
5034
5035 void
5036 AnalysisPeriodicEvent (int force)
5037 {
5038     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5039          && !force) || !appData.periodicUpdates)
5040       return;
5041
5042     /* Send . command to Crafty to collect stats */
5043     SendToBoth(".\n");
5044
5045     /* Don't send another until we get a response (this makes
5046        us stop sending to old Crafty's which don't understand
5047        the "." command (sending illegal cmds resets node count & time,
5048        which looks bad)) */
5049     programStats.ok_to_send = 0;
5050 }
5051
5052 void
5053 ics_update_width (int new_width)
5054 {
5055         ics_printf("set width %d\n", new_width);
5056 }
5057
5058 void
5059 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5060 {
5061     char buf[MSG_SIZ];
5062
5063     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5064         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5065             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5066             SendToProgram(buf, cps);
5067             return;
5068         }
5069         // null move in variant where engine does not understand it (for analysis purposes)
5070         SendBoard(cps, moveNum + 1); // send position after move in stead.
5071         return;
5072     }
5073     if (cps->useUsermove) {
5074       SendToProgram("usermove ", cps);
5075     }
5076     if (cps->useSAN) {
5077       char *space;
5078       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5079         int len = space - parseList[moveNum];
5080         memcpy(buf, parseList[moveNum], len);
5081         buf[len++] = '\n';
5082         buf[len] = NULLCHAR;
5083       } else {
5084         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5085       }
5086       SendToProgram(buf, cps);
5087     } else {
5088       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5089         AlphaRank(moveList[moveNum], 4);
5090         SendToProgram(moveList[moveNum], cps);
5091         AlphaRank(moveList[moveNum], 4); // and back
5092       } else
5093       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5094        * the engine. It would be nice to have a better way to identify castle
5095        * moves here. */
5096       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5097                                                                          && cps->useOOCastle) {
5098         int fromX = moveList[moveNum][0] - AAA;
5099         int fromY = moveList[moveNum][1] - ONE;
5100         int toX = moveList[moveNum][2] - AAA;
5101         int toY = moveList[moveNum][3] - ONE;
5102         if((boards[moveNum][fromY][fromX] == WhiteKing
5103             && boards[moveNum][toY][toX] == WhiteRook)
5104            || (boards[moveNum][fromY][fromX] == BlackKing
5105                && boards[moveNum][toY][toX] == BlackRook)) {
5106           if(toX > fromX) SendToProgram("O-O\n", cps);
5107           else SendToProgram("O-O-O\n", cps);
5108         }
5109         else SendToProgram(moveList[moveNum], cps);
5110       } else
5111       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5112           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5113                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5114                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5115                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5116           SendToProgram(buf, cps);
5117       } else
5118       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5119         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5120           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5121           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5122                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5123         } else
5124           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5125                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5126         SendToProgram(buf, cps);
5127       }
5128       else SendToProgram(moveList[moveNum], cps);
5129       /* End of additions by Tord */
5130     }
5131
5132     /* [HGM] setting up the opening has brought engine in force mode! */
5133     /*       Send 'go' if we are in a mode where machine should play. */
5134     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5135         (gameMode == TwoMachinesPlay   ||
5136 #if ZIPPY
5137          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5138 #endif
5139          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5140         SendToProgram("go\n", cps);
5141   if (appData.debugMode) {
5142     fprintf(debugFP, "(extra)\n");
5143   }
5144     }
5145     setboardSpoiledMachineBlack = 0;
5146 }
5147
5148 void
5149 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5150 {
5151     char user_move[MSG_SIZ];
5152     char suffix[4];
5153
5154     if(gameInfo.variant == VariantSChess && promoChar) {
5155         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5156         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5157     } else suffix[0] = NULLCHAR;
5158
5159     switch (moveType) {
5160       default:
5161         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5162                 (int)moveType, fromX, fromY, toX, toY);
5163         DisplayError(user_move + strlen("say "), 0);
5164         break;
5165       case WhiteKingSideCastle:
5166       case BlackKingSideCastle:
5167       case WhiteQueenSideCastleWild:
5168       case BlackQueenSideCastleWild:
5169       /* PUSH Fabien */
5170       case WhiteHSideCastleFR:
5171       case BlackHSideCastleFR:
5172       /* POP Fabien */
5173         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5174         break;
5175       case WhiteQueenSideCastle:
5176       case BlackQueenSideCastle:
5177       case WhiteKingSideCastleWild:
5178       case BlackKingSideCastleWild:
5179       /* PUSH Fabien */
5180       case WhiteASideCastleFR:
5181       case BlackASideCastleFR:
5182       /* POP Fabien */
5183         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5184         break;
5185       case WhiteNonPromotion:
5186       case BlackNonPromotion:
5187         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5188         break;
5189       case WhitePromotion:
5190       case BlackPromotion:
5191         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5192            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5193           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5194                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5195                 PieceToChar(WhiteFerz));
5196         else if(gameInfo.variant == VariantGreat)
5197           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5198                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5199                 PieceToChar(WhiteMan));
5200         else
5201           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5202                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5203                 promoChar);
5204         break;
5205       case WhiteDrop:
5206       case BlackDrop:
5207       drop:
5208         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5209                  ToUpper(PieceToChar((ChessSquare) fromX)),
5210                  AAA + toX, ONE + toY);
5211         break;
5212       case IllegalMove:  /* could be a variant we don't quite understand */
5213         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5214       case NormalMove:
5215       case WhiteCapturesEnPassant:
5216       case BlackCapturesEnPassant:
5217         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5218                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5219         break;
5220     }
5221     SendToICS(user_move);
5222     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5223         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5224 }
5225
5226 void
5227 UploadGameEvent ()
5228 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5229     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5230     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5231     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5232       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5233       return;
5234     }
5235     if(gameMode != IcsExamining) { // is this ever not the case?
5236         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5237
5238         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5239           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5240         } else { // on FICS we must first go to general examine mode
5241           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5242         }
5243         if(gameInfo.variant != VariantNormal) {
5244             // try figure out wild number, as xboard names are not always valid on ICS
5245             for(i=1; i<=36; i++) {
5246               snprintf(buf, MSG_SIZ, "wild/%d", i);
5247                 if(StringToVariant(buf) == gameInfo.variant) break;
5248             }
5249             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5250             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5251             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5252         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5253         SendToICS(ics_prefix);
5254         SendToICS(buf);
5255         if(startedFromSetupPosition || backwardMostMove != 0) {
5256           fen = PositionToFEN(backwardMostMove, NULL, 1);
5257           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5258             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5259             SendToICS(buf);
5260           } else { // FICS: everything has to set by separate bsetup commands
5261             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5262             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5263             SendToICS(buf);
5264             if(!WhiteOnMove(backwardMostMove)) {
5265                 SendToICS("bsetup tomove black\n");
5266             }
5267             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5268             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5269             SendToICS(buf);
5270             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5271             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5272             SendToICS(buf);
5273             i = boards[backwardMostMove][EP_STATUS];
5274             if(i >= 0) { // set e.p.
5275               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5276                 SendToICS(buf);
5277             }
5278             bsetup++;
5279           }
5280         }
5281       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5282             SendToICS("bsetup done\n"); // switch to normal examining.
5283     }
5284     for(i = backwardMostMove; i<last; i++) {
5285         char buf[20];
5286         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5287         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5288             int len = strlen(moveList[i]);
5289             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5290             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5291         }
5292         SendToICS(buf);
5293     }
5294     SendToICS(ics_prefix);
5295     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5296 }
5297
5298 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5299
5300 void
5301 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5302 {
5303     if (rf == DROP_RANK) {
5304       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5305       sprintf(move, "%c@%c%c\n",
5306                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5307     } else {
5308         if (promoChar == 'x' || promoChar == NULLCHAR) {
5309           sprintf(move, "%c%c%c%c\n",
5310                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5311           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5312         } else {
5313             sprintf(move, "%c%c%c%c%c\n",
5314                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5315         }
5316     }
5317 }
5318
5319 void
5320 ProcessICSInitScript (FILE *f)
5321 {
5322     char buf[MSG_SIZ];
5323
5324     while (fgets(buf, MSG_SIZ, f)) {
5325         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5326     }
5327
5328     fclose(f);
5329 }
5330
5331
5332 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5333 int dragging;
5334 static ClickType lastClickType;
5335
5336 int
5337 Partner (ChessSquare *p)
5338 { // change piece into promotion partner if one shogi-promotes to the other
5339   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5340   ChessSquare partner;
5341   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5342   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5343   *p = partner;
5344   return 1;
5345 }
5346
5347 void
5348 Sweep (int step)
5349 {
5350     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5351     static int toggleFlag;
5352     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5353     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5354     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5355     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5356     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5357     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5358     do {
5359         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5360         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5361         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5362         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5363         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5364         if(!step) step = -1;
5365     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5366             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5367             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5368             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5369     if(toX >= 0) {
5370         int victim = boards[currentMove][toY][toX];
5371         boards[currentMove][toY][toX] = promoSweep;
5372         DrawPosition(FALSE, boards[currentMove]);
5373         boards[currentMove][toY][toX] = victim;
5374     } else
5375     ChangeDragPiece(promoSweep);
5376 }
5377
5378 int
5379 PromoScroll (int x, int y)
5380 {
5381   int step = 0;
5382
5383   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5384   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5385   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5386   if(!step) return FALSE;
5387   lastX = x; lastY = y;
5388   if((promoSweep < BlackPawn) == flipView) step = -step;
5389   if(step > 0) selectFlag = 1;
5390   if(!selectFlag) Sweep(step);
5391   return FALSE;
5392 }
5393
5394 void
5395 NextPiece (int step)
5396 {
5397     ChessSquare piece = boards[currentMove][toY][toX];
5398     do {
5399         pieceSweep -= step;
5400         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5401         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5402         if(!step) step = -1;
5403     } while(PieceToChar(pieceSweep) == '.');
5404     boards[currentMove][toY][toX] = pieceSweep;
5405     DrawPosition(FALSE, boards[currentMove]);
5406     boards[currentMove][toY][toX] = piece;
5407 }
5408 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5409 void
5410 AlphaRank (char *move, int n)
5411 {
5412 //    char *p = move, c; int x, y;
5413
5414     if (appData.debugMode) {
5415         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5416     }
5417
5418     if(move[1]=='*' &&
5419        move[2]>='0' && move[2]<='9' &&
5420        move[3]>='a' && move[3]<='x'    ) {
5421         move[1] = '@';
5422         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5423         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5424     } else
5425     if(move[0]>='0' && move[0]<='9' &&
5426        move[1]>='a' && move[1]<='x' &&
5427        move[2]>='0' && move[2]<='9' &&
5428        move[3]>='a' && move[3]<='x'    ) {
5429         /* input move, Shogi -> normal */
5430         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5431         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5432         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5433         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5434     } else
5435     if(move[1]=='@' &&
5436        move[3]>='0' && move[3]<='9' &&
5437        move[2]>='a' && move[2]<='x'    ) {
5438         move[1] = '*';
5439         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5440         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5441     } else
5442     if(
5443        move[0]>='a' && move[0]<='x' &&
5444        move[3]>='0' && move[3]<='9' &&
5445        move[2]>='a' && move[2]<='x'    ) {
5446          /* output move, normal -> Shogi */
5447         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5448         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5449         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5450         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5451         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5452     }
5453     if (appData.debugMode) {
5454         fprintf(debugFP, "   out = '%s'\n", move);
5455     }
5456 }
5457
5458 char yy_textstr[8000];
5459
5460 /* Parser for moves from gnuchess, ICS, or user typein box */
5461 Boolean
5462 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5463 {
5464     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5465
5466     switch (*moveType) {
5467       case WhitePromotion:
5468       case BlackPromotion:
5469       case WhiteNonPromotion:
5470       case BlackNonPromotion:
5471       case NormalMove:
5472       case FirstLeg:
5473       case WhiteCapturesEnPassant:
5474       case BlackCapturesEnPassant:
5475       case WhiteKingSideCastle:
5476       case WhiteQueenSideCastle:
5477       case BlackKingSideCastle:
5478       case BlackQueenSideCastle:
5479       case WhiteKingSideCastleWild:
5480       case WhiteQueenSideCastleWild:
5481       case BlackKingSideCastleWild:
5482       case BlackQueenSideCastleWild:
5483       /* Code added by Tord: */
5484       case WhiteHSideCastleFR:
5485       case WhiteASideCastleFR:
5486       case BlackHSideCastleFR:
5487       case BlackASideCastleFR:
5488       /* End of code added by Tord */
5489       case IllegalMove:         /* bug or odd chess variant */
5490         *fromX = currentMoveString[0] - AAA;
5491         *fromY = currentMoveString[1] - ONE;
5492         *toX = currentMoveString[2] - AAA;
5493         *toY = currentMoveString[3] - ONE;
5494         *promoChar = currentMoveString[4];
5495         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5496             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5497     if (appData.debugMode) {
5498         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5499     }
5500             *fromX = *fromY = *toX = *toY = 0;
5501             return FALSE;
5502         }
5503         if (appData.testLegality) {
5504           return (*moveType != IllegalMove);
5505         } else {
5506           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5507                          // [HGM] lion: if this is a double move we are less critical
5508                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5509         }
5510
5511       case WhiteDrop:
5512       case BlackDrop:
5513         *fromX = *moveType == WhiteDrop ?
5514           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5515           (int) CharToPiece(ToLower(currentMoveString[0]));
5516         *fromY = DROP_RANK;
5517         *toX = currentMoveString[2] - AAA;
5518         *toY = currentMoveString[3] - ONE;
5519         *promoChar = NULLCHAR;
5520         return TRUE;
5521
5522       case AmbiguousMove:
5523       case ImpossibleMove:
5524       case EndOfFile:
5525       case ElapsedTime:
5526       case Comment:
5527       case PGNTag:
5528       case NAG:
5529       case WhiteWins:
5530       case BlackWins:
5531       case GameIsDrawn:
5532       default:
5533     if (appData.debugMode) {
5534         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5535     }
5536         /* bug? */
5537         *fromX = *fromY = *toX = *toY = 0;
5538         *promoChar = NULLCHAR;
5539         return FALSE;
5540     }
5541 }
5542
5543 Boolean pushed = FALSE;
5544 char *lastParseAttempt;
5545
5546 void
5547 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5548 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5549   int fromX, fromY, toX, toY; char promoChar;
5550   ChessMove moveType;
5551   Boolean valid;
5552   int nr = 0;
5553
5554   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5555   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5556     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5557     pushed = TRUE;
5558   }
5559   endPV = forwardMostMove;
5560   do {
5561     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5562     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5563     lastParseAttempt = pv;
5564     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5565     if(!valid && nr == 0 &&
5566        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5567         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5568         // Hande case where played move is different from leading PV move
5569         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5570         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5571         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5572         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5573           endPV += 2; // if position different, keep this
5574           moveList[endPV-1][0] = fromX + AAA;
5575           moveList[endPV-1][1] = fromY + ONE;
5576           moveList[endPV-1][2] = toX + AAA;
5577           moveList[endPV-1][3] = toY + ONE;
5578           parseList[endPV-1][0] = NULLCHAR;
5579           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5580         }
5581       }
5582     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5583     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5584     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5585     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5586         valid++; // allow comments in PV
5587         continue;
5588     }
5589     nr++;
5590     if(endPV+1 > framePtr) break; // no space, truncate
5591     if(!valid) break;
5592     endPV++;
5593     CopyBoard(boards[endPV], boards[endPV-1]);
5594     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5595     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5596     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5597     CoordsToAlgebraic(boards[endPV - 1],
5598                              PosFlags(endPV - 1),
5599                              fromY, fromX, toY, toX, promoChar,
5600                              parseList[endPV - 1]);
5601   } while(valid);
5602   if(atEnd == 2) return; // used hidden, for PV conversion
5603   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5604   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5605   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5606                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5607   DrawPosition(TRUE, boards[currentMove]);
5608 }
5609
5610 int
5611 MultiPV (ChessProgramState *cps)
5612 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5613         int i;
5614         for(i=0; i<cps->nrOptions; i++)
5615             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5616                 return i;
5617         return -1;
5618 }
5619
5620 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5621
5622 Boolean
5623 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5624 {
5625         int startPV, multi, lineStart, origIndex = index;
5626         char *p, buf2[MSG_SIZ];
5627         ChessProgramState *cps = (pane ? &second : &first);
5628
5629         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5630         lastX = x; lastY = y;
5631         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5632         lineStart = startPV = index;
5633         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5634         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5635         index = startPV;
5636         do{ while(buf[index] && buf[index] != '\n') index++;
5637         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5638         buf[index] = 0;
5639         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5640                 int n = cps->option[multi].value;
5641                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5642                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5643                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5644                 cps->option[multi].value = n;
5645                 *start = *end = 0;
5646                 return FALSE;
5647         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5648                 ExcludeClick(origIndex - lineStart);
5649                 return FALSE;
5650         }
5651         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5652         *start = startPV; *end = index-1;
5653         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5654         return TRUE;
5655 }
5656
5657 char *
5658 PvToSAN (char *pv)
5659 {
5660         static char buf[10*MSG_SIZ];
5661         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5662         *buf = NULLCHAR;
5663         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5664         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5665         for(i = forwardMostMove; i<endPV; i++){
5666             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5667             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5668             k += strlen(buf+k);
5669         }
5670         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5671         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5672         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5673         endPV = savedEnd;
5674         return buf;
5675 }
5676
5677 Boolean
5678 LoadPV (int x, int y)
5679 { // called on right mouse click to load PV
5680   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5681   lastX = x; lastY = y;
5682   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5683   extendGame = FALSE;
5684   return TRUE;
5685 }
5686
5687 void
5688 UnLoadPV ()
5689 {
5690   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5691   if(endPV < 0) return;
5692   if(appData.autoCopyPV) CopyFENToClipboard();
5693   endPV = -1;
5694   if(extendGame && currentMove > forwardMostMove) {
5695         Boolean saveAnimate = appData.animate;
5696         if(pushed) {
5697             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5698                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5699             } else storedGames--; // abandon shelved tail of original game
5700         }
5701         pushed = FALSE;
5702         forwardMostMove = currentMove;
5703         currentMove = oldFMM;
5704         appData.animate = FALSE;
5705         ToNrEvent(forwardMostMove);
5706         appData.animate = saveAnimate;
5707   }
5708   currentMove = forwardMostMove;
5709   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5710   ClearPremoveHighlights();
5711   DrawPosition(TRUE, boards[currentMove]);
5712 }
5713
5714 void
5715 MovePV (int x, int y, int h)
5716 { // step through PV based on mouse coordinates (called on mouse move)
5717   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5718
5719   // we must somehow check if right button is still down (might be released off board!)
5720   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5721   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5722   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5723   if(!step) return;
5724   lastX = x; lastY = y;
5725
5726   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5727   if(endPV < 0) return;
5728   if(y < margin) step = 1; else
5729   if(y > h - margin) step = -1;
5730   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5731   currentMove += step;
5732   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5733   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5734                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5735   DrawPosition(FALSE, boards[currentMove]);
5736 }
5737
5738
5739 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5740 // All positions will have equal probability, but the current method will not provide a unique
5741 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5742 #define DARK 1
5743 #define LITE 2
5744 #define ANY 3
5745
5746 int squaresLeft[4];
5747 int piecesLeft[(int)BlackPawn];
5748 int seed, nrOfShuffles;
5749
5750 void
5751 GetPositionNumber ()
5752 {       // sets global variable seed
5753         int i;
5754
5755         seed = appData.defaultFrcPosition;
5756         if(seed < 0) { // randomize based on time for negative FRC position numbers
5757                 for(i=0; i<50; i++) seed += random();
5758                 seed = random() ^ random() >> 8 ^ random() << 8;
5759                 if(seed<0) seed = -seed;
5760         }
5761 }
5762
5763 int
5764 put (Board board, int pieceType, int rank, int n, int shade)
5765 // put the piece on the (n-1)-th empty squares of the given shade
5766 {
5767         int i;
5768
5769         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5770                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5771                         board[rank][i] = (ChessSquare) pieceType;
5772                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5773                         squaresLeft[ANY]--;
5774                         piecesLeft[pieceType]--;
5775                         return i;
5776                 }
5777         }
5778         return -1;
5779 }
5780
5781
5782 void
5783 AddOnePiece (Board board, int pieceType, int rank, int shade)
5784 // calculate where the next piece goes, (any empty square), and put it there
5785 {
5786         int i;
5787
5788         i = seed % squaresLeft[shade];
5789         nrOfShuffles *= squaresLeft[shade];
5790         seed /= squaresLeft[shade];
5791         put(board, pieceType, rank, i, shade);
5792 }
5793
5794 void
5795 AddTwoPieces (Board board, int pieceType, int rank)
5796 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5797 {
5798         int i, n=squaresLeft[ANY], j=n-1, k;
5799
5800         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5801         i = seed % k;  // pick one
5802         nrOfShuffles *= k;
5803         seed /= k;
5804         while(i >= j) i -= j--;
5805         j = n - 1 - j; i += j;
5806         put(board, pieceType, rank, j, ANY);
5807         put(board, pieceType, rank, i, ANY);
5808 }
5809
5810 void
5811 SetUpShuffle (Board board, int number)
5812 {
5813         int i, p, first=1;
5814
5815         GetPositionNumber(); nrOfShuffles = 1;
5816
5817         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5818         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5819         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5820
5821         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5822
5823         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5824             p = (int) board[0][i];
5825             if(p < (int) BlackPawn) piecesLeft[p] ++;
5826             board[0][i] = EmptySquare;
5827         }
5828
5829         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5830             // shuffles restricted to allow normal castling put KRR first
5831             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5832                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5833             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5834                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5835             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5836                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5837             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5838                 put(board, WhiteRook, 0, 0, ANY);
5839             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5840         }
5841
5842         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5843             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5844             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5845                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5846                 while(piecesLeft[p] >= 2) {
5847                     AddOnePiece(board, p, 0, LITE);
5848                     AddOnePiece(board, p, 0, DARK);
5849                 }
5850                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5851             }
5852
5853         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5854             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5855             // but we leave King and Rooks for last, to possibly obey FRC restriction
5856             if(p == (int)WhiteRook) continue;
5857             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5858             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5859         }
5860
5861         // now everything is placed, except perhaps King (Unicorn) and Rooks
5862
5863         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5864             // Last King gets castling rights
5865             while(piecesLeft[(int)WhiteUnicorn]) {
5866                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5867                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5868             }
5869
5870             while(piecesLeft[(int)WhiteKing]) {
5871                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5872                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5873             }
5874
5875
5876         } else {
5877             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5878             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5879         }
5880
5881         // Only Rooks can be left; simply place them all
5882         while(piecesLeft[(int)WhiteRook]) {
5883                 i = put(board, WhiteRook, 0, 0, ANY);
5884                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5885                         if(first) {
5886                                 first=0;
5887                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5888                         }
5889                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5890                 }
5891         }
5892         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5893             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5894         }
5895
5896         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5897 }
5898
5899 int
5900 SetCharTable (char *table, const char * map)
5901 /* [HGM] moved here from winboard.c because of its general usefulness */
5902 /*       Basically a safe strcpy that uses the last character as King */
5903 {
5904     int result = FALSE; int NrPieces;
5905
5906     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5907                     && NrPieces >= 12 && !(NrPieces&1)) {
5908         int i; /* [HGM] Accept even length from 12 to 34 */
5909
5910         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5911         for( i=0; i<NrPieces/2-1; i++ ) {
5912             table[i] = map[i];
5913             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5914         }
5915         table[(int) WhiteKing]  = map[NrPieces/2-1];
5916         table[(int) BlackKing]  = map[NrPieces-1];
5917
5918         result = TRUE;
5919     }
5920
5921     return result;
5922 }
5923
5924 void
5925 Prelude (Board board)
5926 {       // [HGM] superchess: random selection of exo-pieces
5927         int i, j, k; ChessSquare p;
5928         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5929
5930         GetPositionNumber(); // use FRC position number
5931
5932         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5933             SetCharTable(pieceToChar, appData.pieceToCharTable);
5934             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5935                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5936         }
5937
5938         j = seed%4;                 seed /= 4;
5939         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5940         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5941         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5942         j = seed%3 + (seed%3 >= j); seed /= 3;
5943         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5944         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5945         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5946         j = seed%3;                 seed /= 3;
5947         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5948         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5949         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5950         j = seed%2 + (seed%2 >= j); seed /= 2;
5951         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5952         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5953         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5954         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5955         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5956         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5957         put(board, exoPieces[0],    0, 0, ANY);
5958         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5959 }
5960
5961 void
5962 InitPosition (int redraw)
5963 {
5964     ChessSquare (* pieces)[BOARD_FILES];
5965     int i, j, pawnRow=1, pieceRows=1, overrule,
5966     oldx = gameInfo.boardWidth,
5967     oldy = gameInfo.boardHeight,
5968     oldh = gameInfo.holdingsWidth;
5969     static int oldv;
5970
5971     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5972
5973     /* [AS] Initialize pv info list [HGM] and game status */
5974     {
5975         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5976             pvInfoList[i].depth = 0;
5977             boards[i][EP_STATUS] = EP_NONE;
5978             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5979         }
5980
5981         initialRulePlies = 0; /* 50-move counter start */
5982
5983         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5984         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5985     }
5986
5987
5988     /* [HGM] logic here is completely changed. In stead of full positions */
5989     /* the initialized data only consist of the two backranks. The switch */
5990     /* selects which one we will use, which is than copied to the Board   */
5991     /* initialPosition, which for the rest is initialized by Pawns and    */
5992     /* empty squares. This initial position is then copied to boards[0],  */
5993     /* possibly after shuffling, so that it remains available.            */
5994
5995     gameInfo.holdingsWidth = 0; /* default board sizes */
5996     gameInfo.boardWidth    = 8;
5997     gameInfo.boardHeight   = 8;
5998     gameInfo.holdingsSize  = 0;
5999     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6000     for(i=0; i<BOARD_FILES-2; i++)
6001       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6002     initialPosition[EP_STATUS] = EP_NONE;
6003     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6004     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6005          SetCharTable(pieceNickName, appData.pieceNickNames);
6006     else SetCharTable(pieceNickName, "............");
6007     pieces = FIDEArray;
6008
6009     switch (gameInfo.variant) {
6010     case VariantFischeRandom:
6011       shuffleOpenings = TRUE;
6012     default:
6013       break;
6014     case VariantShatranj:
6015       pieces = ShatranjArray;
6016       nrCastlingRights = 0;
6017       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6018       break;
6019     case VariantMakruk:
6020       pieces = makrukArray;
6021       nrCastlingRights = 0;
6022       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6023       break;
6024     case VariantASEAN:
6025       pieces = aseanArray;
6026       nrCastlingRights = 0;
6027       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6028       break;
6029     case VariantTwoKings:
6030       pieces = twoKingsArray;
6031       break;
6032     case VariantGrand:
6033       pieces = GrandArray;
6034       nrCastlingRights = 0;
6035       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6036       gameInfo.boardWidth = 10;
6037       gameInfo.boardHeight = 10;
6038       gameInfo.holdingsSize = 7;
6039       break;
6040     case VariantCapaRandom:
6041       shuffleOpenings = TRUE;
6042     case VariantCapablanca:
6043       pieces = CapablancaArray;
6044       gameInfo.boardWidth = 10;
6045       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6046       break;
6047     case VariantGothic:
6048       pieces = GothicArray;
6049       gameInfo.boardWidth = 10;
6050       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6051       break;
6052     case VariantSChess:
6053       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6054       gameInfo.holdingsSize = 7;
6055       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6056       break;
6057     case VariantJanus:
6058       pieces = JanusArray;
6059       gameInfo.boardWidth = 10;
6060       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6061       nrCastlingRights = 6;
6062         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6063         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6064         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6065         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6066         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6067         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6068       break;
6069     case VariantFalcon:
6070       pieces = FalconArray;
6071       gameInfo.boardWidth = 10;
6072       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6073       break;
6074     case VariantXiangqi:
6075       pieces = XiangqiArray;
6076       gameInfo.boardWidth  = 9;
6077       gameInfo.boardHeight = 10;
6078       nrCastlingRights = 0;
6079       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6080       break;
6081     case VariantShogi:
6082       pieces = ShogiArray;
6083       gameInfo.boardWidth  = 9;
6084       gameInfo.boardHeight = 9;
6085       gameInfo.holdingsSize = 7;
6086       nrCastlingRights = 0;
6087       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6088       break;
6089     case VariantChu:
6090       pieces = ChuArray; pieceRows = 3;
6091       gameInfo.boardWidth  = 12;
6092       gameInfo.boardHeight = 12;
6093       nrCastlingRights = 0;
6094       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6095                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6096       break;
6097     case VariantCourier:
6098       pieces = CourierArray;
6099       gameInfo.boardWidth  = 12;
6100       nrCastlingRights = 0;
6101       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6102       break;
6103     case VariantKnightmate:
6104       pieces = KnightmateArray;
6105       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6106       break;
6107     case VariantSpartan:
6108       pieces = SpartanArray;
6109       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6110       break;
6111     case VariantLion:
6112       pieces = lionArray;
6113       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6114       break;
6115     case VariantChuChess:
6116       pieces = ChuChessArray;
6117       gameInfo.boardWidth = 10;
6118       gameInfo.boardHeight = 10;
6119       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6120       break;
6121     case VariantFairy:
6122       pieces = fairyArray;
6123       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6124       break;
6125     case VariantGreat:
6126       pieces = GreatArray;
6127       gameInfo.boardWidth = 10;
6128       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6129       gameInfo.holdingsSize = 8;
6130       break;
6131     case VariantSuper:
6132       pieces = FIDEArray;
6133       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6134       gameInfo.holdingsSize = 8;
6135       startedFromSetupPosition = TRUE;
6136       break;
6137     case VariantCrazyhouse:
6138     case VariantBughouse:
6139       pieces = FIDEArray;
6140       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6141       gameInfo.holdingsSize = 5;
6142       break;
6143     case VariantWildCastle:
6144       pieces = FIDEArray;
6145       /* !!?shuffle with kings guaranteed to be on d or e file */
6146       shuffleOpenings = 1;
6147       break;
6148     case VariantNoCastle:
6149       pieces = FIDEArray;
6150       nrCastlingRights = 0;
6151       /* !!?unconstrained back-rank shuffle */
6152       shuffleOpenings = 1;
6153       break;
6154     }
6155
6156     overrule = 0;
6157     if(appData.NrFiles >= 0) {
6158         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6159         gameInfo.boardWidth = appData.NrFiles;
6160     }
6161     if(appData.NrRanks >= 0) {
6162         gameInfo.boardHeight = appData.NrRanks;
6163     }
6164     if(appData.holdingsSize >= 0) {
6165         i = appData.holdingsSize;
6166         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6167         gameInfo.holdingsSize = i;
6168     }
6169     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6170     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6171         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6172
6173     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6174     if(pawnRow < 1) pawnRow = 1;
6175     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6176        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6177     if(gameInfo.variant == VariantChu) pawnRow = 3;
6178
6179     /* User pieceToChar list overrules defaults */
6180     if(appData.pieceToCharTable != NULL)
6181         SetCharTable(pieceToChar, appData.pieceToCharTable);
6182
6183     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6184
6185         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6186             s = (ChessSquare) 0; /* account holding counts in guard band */
6187         for( i=0; i<BOARD_HEIGHT; i++ )
6188             initialPosition[i][j] = s;
6189
6190         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6191         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6192         initialPosition[pawnRow][j] = WhitePawn;
6193         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6194         if(gameInfo.variant == VariantXiangqi) {
6195             if(j&1) {
6196                 initialPosition[pawnRow][j] =
6197                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6198                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6199                    initialPosition[2][j] = WhiteCannon;
6200                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6201                 }
6202             }
6203         }
6204         if(gameInfo.variant == VariantChu) {
6205              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6206                initialPosition[pawnRow+1][j] = WhiteCobra,
6207                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6208              for(i=1; i<pieceRows; i++) {
6209                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6210                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6211              }
6212         }
6213         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6214             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6215                initialPosition[0][j] = WhiteRook;
6216                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6217             }
6218         }
6219         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6220     }
6221     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6222     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6223
6224             j=BOARD_LEFT+1;
6225             initialPosition[1][j] = WhiteBishop;
6226             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6227             j=BOARD_RGHT-2;
6228             initialPosition[1][j] = WhiteRook;
6229             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6230     }
6231
6232     if( nrCastlingRights == -1) {
6233         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6234         /*       This sets default castling rights from none to normal corners   */
6235         /* Variants with other castling rights must set them themselves above    */
6236         nrCastlingRights = 6;
6237
6238         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6239         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6240         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6241         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6242         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6243         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6244      }
6245
6246      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6247      if(gameInfo.variant == VariantGreat) { // promotion commoners
6248         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6249         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6250         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6251         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6252      }
6253      if( gameInfo.variant == VariantSChess ) {
6254       initialPosition[1][0] = BlackMarshall;
6255       initialPosition[2][0] = BlackAngel;
6256       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6257       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6258       initialPosition[1][1] = initialPosition[2][1] =
6259       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6260      }
6261   if (appData.debugMode) {
6262     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6263   }
6264     if(shuffleOpenings) {
6265         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6266         startedFromSetupPosition = TRUE;
6267     }
6268     if(startedFromPositionFile) {
6269       /* [HGM] loadPos: use PositionFile for every new game */
6270       CopyBoard(initialPosition, filePosition);
6271       for(i=0; i<nrCastlingRights; i++)
6272           initialRights[i] = filePosition[CASTLING][i];
6273       startedFromSetupPosition = TRUE;
6274     }
6275
6276     CopyBoard(boards[0], initialPosition);
6277
6278     if(oldx != gameInfo.boardWidth ||
6279        oldy != gameInfo.boardHeight ||
6280        oldv != gameInfo.variant ||
6281        oldh != gameInfo.holdingsWidth
6282                                          )
6283             InitDrawingSizes(-2 ,0);
6284
6285     oldv = gameInfo.variant;
6286     if (redraw)
6287       DrawPosition(TRUE, boards[currentMove]);
6288 }
6289
6290 void
6291 SendBoard (ChessProgramState *cps, int moveNum)
6292 {
6293     char message[MSG_SIZ];
6294
6295     if (cps->useSetboard) {
6296       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6297       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6298       SendToProgram(message, cps);
6299       free(fen);
6300
6301     } else {
6302       ChessSquare *bp;
6303       int i, j, left=0, right=BOARD_WIDTH;
6304       /* Kludge to set black to move, avoiding the troublesome and now
6305        * deprecated "black" command.
6306        */
6307       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6308         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6309
6310       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6311
6312       SendToProgram("edit\n", cps);
6313       SendToProgram("#\n", cps);
6314       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6315         bp = &boards[moveNum][i][left];
6316         for (j = left; j < right; j++, bp++) {
6317           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6318           if ((int) *bp < (int) BlackPawn) {
6319             if(j == BOARD_RGHT+1)
6320                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6321             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6322             if(message[0] == '+' || message[0] == '~') {
6323               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6324                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6325                         AAA + j, ONE + i);
6326             }
6327             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6328                 message[1] = BOARD_RGHT   - 1 - j + '1';
6329                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6330             }
6331             SendToProgram(message, cps);
6332           }
6333         }
6334       }
6335
6336       SendToProgram("c\n", cps);
6337       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6338         bp = &boards[moveNum][i][left];
6339         for (j = left; j < right; j++, bp++) {
6340           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6341           if (((int) *bp != (int) EmptySquare)
6342               && ((int) *bp >= (int) BlackPawn)) {
6343             if(j == BOARD_LEFT-2)
6344                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6345             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6346                     AAA + j, ONE + i);
6347             if(message[0] == '+' || message[0] == '~') {
6348               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6349                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6350                         AAA + j, ONE + i);
6351             }
6352             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6353                 message[1] = BOARD_RGHT   - 1 - j + '1';
6354                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6355             }
6356             SendToProgram(message, cps);
6357           }
6358         }
6359       }
6360
6361       SendToProgram(".\n", cps);
6362     }
6363     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6364 }
6365
6366 char exclusionHeader[MSG_SIZ];
6367 int exCnt, excludePtr;
6368 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6369 static Exclusion excluTab[200];
6370 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6371
6372 static void
6373 WriteMap (int s)
6374 {
6375     int j;
6376     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6377     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6378 }
6379
6380 static void
6381 ClearMap ()
6382 {
6383     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6384     excludePtr = 24; exCnt = 0;
6385     WriteMap(0);
6386 }
6387
6388 static void
6389 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6390 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6391     char buf[2*MOVE_LEN], *p;
6392     Exclusion *e = excluTab;
6393     int i;
6394     for(i=0; i<exCnt; i++)
6395         if(e[i].ff == fromX && e[i].fr == fromY &&
6396            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6397     if(i == exCnt) { // was not in exclude list; add it
6398         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6399         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6400             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6401             return; // abort
6402         }
6403         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6404         excludePtr++; e[i].mark = excludePtr++;
6405         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6406         exCnt++;
6407     }
6408     exclusionHeader[e[i].mark] = state;
6409 }
6410
6411 static int
6412 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6413 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6414     char buf[MSG_SIZ];
6415     int j, k;
6416     ChessMove moveType;
6417     if((signed char)promoChar == -1) { // kludge to indicate best move
6418         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6419             return 1; // if unparsable, abort
6420     }
6421     // update exclusion map (resolving toggle by consulting existing state)
6422     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6423     j = k%8; k >>= 3;
6424     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6425     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6426          excludeMap[k] |=   1<<j;
6427     else excludeMap[k] &= ~(1<<j);
6428     // update header
6429     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6430     // inform engine
6431     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6432     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6433     SendToBoth(buf);
6434     return (state == '+');
6435 }
6436
6437 static void
6438 ExcludeClick (int index)
6439 {
6440     int i, j;
6441     Exclusion *e = excluTab;
6442     if(index < 25) { // none, best or tail clicked
6443         if(index < 13) { // none: include all
6444             WriteMap(0); // clear map
6445             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6446             SendToBoth("include all\n"); // and inform engine
6447         } else if(index > 18) { // tail
6448             if(exclusionHeader[19] == '-') { // tail was excluded
6449                 SendToBoth("include all\n");
6450                 WriteMap(0); // clear map completely
6451                 // now re-exclude selected moves
6452                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6453                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6454             } else { // tail was included or in mixed state
6455                 SendToBoth("exclude all\n");
6456                 WriteMap(0xFF); // fill map completely
6457                 // now re-include selected moves
6458                 j = 0; // count them
6459                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6460                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6461                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6462             }
6463         } else { // best
6464             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6465         }
6466     } else {
6467         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6468             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6469             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6470             break;
6471         }
6472     }
6473 }
6474
6475 ChessSquare
6476 DefaultPromoChoice (int white)
6477 {
6478     ChessSquare result;
6479     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6480        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6481         result = WhiteFerz; // no choice
6482     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6483         result= WhiteKing; // in Suicide Q is the last thing we want
6484     else if(gameInfo.variant == VariantSpartan)
6485         result = white ? WhiteQueen : WhiteAngel;
6486     else result = WhiteQueen;
6487     if(!white) result = WHITE_TO_BLACK result;
6488     return result;
6489 }
6490
6491 static int autoQueen; // [HGM] oneclick
6492
6493 int
6494 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6495 {
6496     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6497     /* [HGM] add Shogi promotions */
6498     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6499     ChessSquare piece, partner;
6500     ChessMove moveType;
6501     Boolean premove;
6502
6503     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6504     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6505
6506     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6507       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6508         return FALSE;
6509
6510     piece = boards[currentMove][fromY][fromX];
6511     if(gameInfo.variant == VariantChu) {
6512         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6513         promotionZoneSize = BOARD_HEIGHT/3;
6514         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6515     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6516         promotionZoneSize = BOARD_HEIGHT/3;
6517         highestPromotingPiece = (int)WhiteAlfil;
6518     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6519         promotionZoneSize = 3;
6520     }
6521
6522     // Treat Lance as Pawn when it is not representing Amazon or Lance
6523     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6524         if(piece == WhiteLance) piece = WhitePawn; else
6525         if(piece == BlackLance) piece = BlackPawn;
6526     }
6527
6528     // next weed out all moves that do not touch the promotion zone at all
6529     if((int)piece >= BlackPawn) {
6530         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6531              return FALSE;
6532         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6533         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6534     } else {
6535         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6536            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6537         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6538              return FALSE;
6539     }
6540
6541     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6542
6543     // weed out mandatory Shogi promotions
6544     if(gameInfo.variant == VariantShogi) {
6545         if(piece >= BlackPawn) {
6546             if(toY == 0 && piece == BlackPawn ||
6547                toY == 0 && piece == BlackQueen ||
6548                toY <= 1 && piece == BlackKnight) {
6549                 *promoChoice = '+';
6550                 return FALSE;
6551             }
6552         } else {
6553             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6554                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6555                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6556                 *promoChoice = '+';
6557                 return FALSE;
6558             }
6559         }
6560     }
6561
6562     // weed out obviously illegal Pawn moves
6563     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6564         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6565         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6566         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6567         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6568         // note we are not allowed to test for valid (non-)capture, due to premove
6569     }
6570
6571     // we either have a choice what to promote to, or (in Shogi) whether to promote
6572     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6573        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6574         ChessSquare p=BlackFerz;  // no choice
6575         while(p < EmptySquare) {  //but make sure we use piece that exists
6576             *promoChoice = PieceToChar(p++);
6577             if(*promoChoice != '.') break;
6578         }
6579         return FALSE;
6580     }
6581     // no sense asking what we must promote to if it is going to explode...
6582     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6583         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6584         return FALSE;
6585     }
6586     // give caller the default choice even if we will not make it
6587     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6588     partner = piece; // pieces can promote if the pieceToCharTable says so
6589     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6590     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6591     if(        sweepSelect && gameInfo.variant != VariantGreat
6592                            && gameInfo.variant != VariantGrand
6593                            && gameInfo.variant != VariantSuper) return FALSE;
6594     if(autoQueen) return FALSE; // predetermined
6595
6596     // suppress promotion popup on illegal moves that are not premoves
6597     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6598               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6599     if(appData.testLegality && !premove) {
6600         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6601                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6602         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6603         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6604             return FALSE;
6605     }
6606
6607     return TRUE;
6608 }
6609
6610 int
6611 InPalace (int row, int column)
6612 {   /* [HGM] for Xiangqi */
6613     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6614          column < (BOARD_WIDTH + 4)/2 &&
6615          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6616     return FALSE;
6617 }
6618
6619 int
6620 PieceForSquare (int x, int y)
6621 {
6622   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6623      return -1;
6624   else
6625      return boards[currentMove][y][x];
6626 }
6627
6628 int
6629 OKToStartUserMove (int x, int y)
6630 {
6631     ChessSquare from_piece;
6632     int white_piece;
6633
6634     if (matchMode) return FALSE;
6635     if (gameMode == EditPosition) return TRUE;
6636
6637     if (x >= 0 && y >= 0)
6638       from_piece = boards[currentMove][y][x];
6639     else
6640       from_piece = EmptySquare;
6641
6642     if (from_piece == EmptySquare) return FALSE;
6643
6644     white_piece = (int)from_piece >= (int)WhitePawn &&
6645       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6646
6647     switch (gameMode) {
6648       case AnalyzeFile:
6649       case TwoMachinesPlay:
6650       case EndOfGame:
6651         return FALSE;
6652
6653       case IcsObserving:
6654       case IcsIdle:
6655         return FALSE;
6656
6657       case MachinePlaysWhite:
6658       case IcsPlayingBlack:
6659         if (appData.zippyPlay) return FALSE;
6660         if (white_piece) {
6661             DisplayMoveError(_("You are playing Black"));
6662             return FALSE;
6663         }
6664         break;
6665
6666       case MachinePlaysBlack:
6667       case IcsPlayingWhite:
6668         if (appData.zippyPlay) return FALSE;
6669         if (!white_piece) {
6670             DisplayMoveError(_("You are playing White"));
6671             return FALSE;
6672         }
6673         break;
6674
6675       case PlayFromGameFile:
6676             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6677       case EditGame:
6678         if (!white_piece && WhiteOnMove(currentMove)) {
6679             DisplayMoveError(_("It is White's turn"));
6680             return FALSE;
6681         }
6682         if (white_piece && !WhiteOnMove(currentMove)) {
6683             DisplayMoveError(_("It is Black's turn"));
6684             return FALSE;
6685         }
6686         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6687             /* Editing correspondence game history */
6688             /* Could disallow this or prompt for confirmation */
6689             cmailOldMove = -1;
6690         }
6691         break;
6692
6693       case BeginningOfGame:
6694         if (appData.icsActive) return FALSE;
6695         if (!appData.noChessProgram) {
6696             if (!white_piece) {
6697                 DisplayMoveError(_("You are playing White"));
6698                 return FALSE;
6699             }
6700         }
6701         break;
6702
6703       case Training:
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         break;
6713
6714       default:
6715       case IcsExamining:
6716         break;
6717     }
6718     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6719         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6720         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6721         && gameMode != AnalyzeFile && gameMode != Training) {
6722         DisplayMoveError(_("Displayed position is not current"));
6723         return FALSE;
6724     }
6725     return TRUE;
6726 }
6727
6728 Boolean
6729 OnlyMove (int *x, int *y, Boolean captures)
6730 {
6731     DisambiguateClosure cl;
6732     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6733     switch(gameMode) {
6734       case MachinePlaysBlack:
6735       case IcsPlayingWhite:
6736       case BeginningOfGame:
6737         if(!WhiteOnMove(currentMove)) return FALSE;
6738         break;
6739       case MachinePlaysWhite:
6740       case IcsPlayingBlack:
6741         if(WhiteOnMove(currentMove)) return FALSE;
6742         break;
6743       case EditGame:
6744         break;
6745       default:
6746         return FALSE;
6747     }
6748     cl.pieceIn = EmptySquare;
6749     cl.rfIn = *y;
6750     cl.ffIn = *x;
6751     cl.rtIn = -1;
6752     cl.ftIn = -1;
6753     cl.promoCharIn = NULLCHAR;
6754     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6755     if( cl.kind == NormalMove ||
6756         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6757         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6758         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6759       fromX = cl.ff;
6760       fromY = cl.rf;
6761       *x = cl.ft;
6762       *y = cl.rt;
6763       return TRUE;
6764     }
6765     if(cl.kind != ImpossibleMove) return FALSE;
6766     cl.pieceIn = EmptySquare;
6767     cl.rfIn = -1;
6768     cl.ffIn = -1;
6769     cl.rtIn = *y;
6770     cl.ftIn = *x;
6771     cl.promoCharIn = NULLCHAR;
6772     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6773     if( cl.kind == NormalMove ||
6774         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6775         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6776         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6777       fromX = cl.ff;
6778       fromY = cl.rf;
6779       *x = cl.ft;
6780       *y = cl.rt;
6781       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6782       return TRUE;
6783     }
6784     return FALSE;
6785 }
6786
6787 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6788 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6789 int lastLoadGameUseList = FALSE;
6790 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6791 ChessMove lastLoadGameStart = EndOfFile;
6792 int doubleClick;
6793
6794 void
6795 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6796 {
6797     ChessMove moveType;
6798     ChessSquare pup;
6799     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6800
6801     /* Check if the user is playing in turn.  This is complicated because we
6802        let the user "pick up" a piece before it is his turn.  So the piece he
6803        tried to pick up may have been captured by the time he puts it down!
6804        Therefore we use the color the user is supposed to be playing in this
6805        test, not the color of the piece that is currently on the starting
6806        square---except in EditGame mode, where the user is playing both
6807        sides; fortunately there the capture race can't happen.  (It can
6808        now happen in IcsExamining mode, but that's just too bad.  The user
6809        will get a somewhat confusing message in that case.)
6810        */
6811
6812     switch (gameMode) {
6813       case AnalyzeFile:
6814       case TwoMachinesPlay:
6815       case EndOfGame:
6816       case IcsObserving:
6817       case IcsIdle:
6818         /* We switched into a game mode where moves are not accepted,
6819            perhaps while the mouse button was down. */
6820         return;
6821
6822       case MachinePlaysWhite:
6823         /* User is moving for Black */
6824         if (WhiteOnMove(currentMove)) {
6825             DisplayMoveError(_("It is White's turn"));
6826             return;
6827         }
6828         break;
6829
6830       case MachinePlaysBlack:
6831         /* User is moving for White */
6832         if (!WhiteOnMove(currentMove)) {
6833             DisplayMoveError(_("It is Black's turn"));
6834             return;
6835         }
6836         break;
6837
6838       case PlayFromGameFile:
6839             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6840       case EditGame:
6841       case IcsExamining:
6842       case BeginningOfGame:
6843       case AnalyzeMode:
6844       case Training:
6845         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6846         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6847             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6848             /* User is moving for Black */
6849             if (WhiteOnMove(currentMove)) {
6850                 DisplayMoveError(_("It is White's turn"));
6851                 return;
6852             }
6853         } else {
6854             /* User is moving for White */
6855             if (!WhiteOnMove(currentMove)) {
6856                 DisplayMoveError(_("It is Black's turn"));
6857                 return;
6858             }
6859         }
6860         break;
6861
6862       case IcsPlayingBlack:
6863         /* User is moving for Black */
6864         if (WhiteOnMove(currentMove)) {
6865             if (!appData.premove) {
6866                 DisplayMoveError(_("It is White's turn"));
6867             } else if (toX >= 0 && toY >= 0) {
6868                 premoveToX = toX;
6869                 premoveToY = toY;
6870                 premoveFromX = fromX;
6871                 premoveFromY = fromY;
6872                 premovePromoChar = promoChar;
6873                 gotPremove = 1;
6874                 if (appData.debugMode)
6875                     fprintf(debugFP, "Got premove: fromX %d,"
6876                             "fromY %d, toX %d, toY %d\n",
6877                             fromX, fromY, toX, toY);
6878             }
6879             return;
6880         }
6881         break;
6882
6883       case IcsPlayingWhite:
6884         /* User is moving for White */
6885         if (!WhiteOnMove(currentMove)) {
6886             if (!appData.premove) {
6887                 DisplayMoveError(_("It is Black's turn"));
6888             } else if (toX >= 0 && toY >= 0) {
6889                 premoveToX = toX;
6890                 premoveToY = toY;
6891                 premoveFromX = fromX;
6892                 premoveFromY = fromY;
6893                 premovePromoChar = promoChar;
6894                 gotPremove = 1;
6895                 if (appData.debugMode)
6896                     fprintf(debugFP, "Got premove: fromX %d,"
6897                             "fromY %d, toX %d, toY %d\n",
6898                             fromX, fromY, toX, toY);
6899             }
6900             return;
6901         }
6902         break;
6903
6904       default:
6905         break;
6906
6907       case EditPosition:
6908         /* EditPosition, empty square, or different color piece;
6909            click-click move is possible */
6910         if (toX == -2 || toY == -2) {
6911             boards[0][fromY][fromX] = EmptySquare;
6912             DrawPosition(FALSE, boards[currentMove]);
6913             return;
6914         } else if (toX >= 0 && toY >= 0) {
6915             boards[0][toY][toX] = boards[0][fromY][fromX];
6916             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6917                 if(boards[0][fromY][0] != EmptySquare) {
6918                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6919                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6920                 }
6921             } else
6922             if(fromX == BOARD_RGHT+1) {
6923                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6924                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6925                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6926                 }
6927             } else
6928             boards[0][fromY][fromX] = gatingPiece;
6929             DrawPosition(FALSE, boards[currentMove]);
6930             return;
6931         }
6932         return;
6933     }
6934
6935     if(toX < 0 || toY < 0) return;
6936     pup = boards[currentMove][toY][toX];
6937
6938     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6939     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6940          if( pup != EmptySquare ) return;
6941          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6942            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6943                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6944            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6945            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6946            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6947            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6948          fromY = DROP_RANK;
6949     }
6950
6951     /* [HGM] always test for legality, to get promotion info */
6952     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6953                                          fromY, fromX, toY, toX, promoChar);
6954
6955     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6956
6957     /* [HGM] but possibly ignore an IllegalMove result */
6958     if (appData.testLegality) {
6959         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6960             DisplayMoveError(_("Illegal move"));
6961             return;
6962         }
6963     }
6964
6965     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6966         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6967              ClearPremoveHighlights(); // was included
6968         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6969         return;
6970     }
6971
6972     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6973 }
6974
6975 /* Common tail of UserMoveEvent and DropMenuEvent */
6976 int
6977 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6978 {
6979     char *bookHit = 0;
6980
6981     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6982         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6983         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6984         if(WhiteOnMove(currentMove)) {
6985             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6986         } else {
6987             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6988         }
6989     }
6990
6991     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6992        move type in caller when we know the move is a legal promotion */
6993     if(moveType == NormalMove && promoChar)
6994         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6995
6996     /* [HGM] <popupFix> The following if has been moved here from
6997        UserMoveEvent(). Because it seemed to belong here (why not allow
6998        piece drops in training games?), and because it can only be
6999        performed after it is known to what we promote. */
7000     if (gameMode == Training) {
7001       /* compare the move played on the board to the next move in the
7002        * game. If they match, display the move and the opponent's response.
7003        * If they don't match, display an error message.
7004        */
7005       int saveAnimate;
7006       Board testBoard;
7007       CopyBoard(testBoard, boards[currentMove]);
7008       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7009
7010       if (CompareBoards(testBoard, boards[currentMove+1])) {
7011         ForwardInner(currentMove+1);
7012
7013         /* Autoplay the opponent's response.
7014          * if appData.animate was TRUE when Training mode was entered,
7015          * the response will be animated.
7016          */
7017         saveAnimate = appData.animate;
7018         appData.animate = animateTraining;
7019         ForwardInner(currentMove+1);
7020         appData.animate = saveAnimate;
7021
7022         /* check for the end of the game */
7023         if (currentMove >= forwardMostMove) {
7024           gameMode = PlayFromGameFile;
7025           ModeHighlight();
7026           SetTrainingModeOff();
7027           DisplayInformation(_("End of game"));
7028         }
7029       } else {
7030         DisplayError(_("Incorrect move"), 0);
7031       }
7032       return 1;
7033     }
7034
7035   /* Ok, now we know that the move is good, so we can kill
7036      the previous line in Analysis Mode */
7037   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7038                                 && currentMove < forwardMostMove) {
7039     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7040     else forwardMostMove = currentMove;
7041   }
7042
7043   ClearMap();
7044
7045   /* If we need the chess program but it's dead, restart it */
7046   ResurrectChessProgram();
7047
7048   /* A user move restarts a paused game*/
7049   if (pausing)
7050     PauseEvent();
7051
7052   thinkOutput[0] = NULLCHAR;
7053
7054   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7055
7056   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7057     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7058     return 1;
7059   }
7060
7061   if (gameMode == BeginningOfGame) {
7062     if (appData.noChessProgram) {
7063       gameMode = EditGame;
7064       SetGameInfo();
7065     } else {
7066       char buf[MSG_SIZ];
7067       gameMode = MachinePlaysBlack;
7068       StartClocks();
7069       SetGameInfo();
7070       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7071       DisplayTitle(buf);
7072       if (first.sendName) {
7073         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7074         SendToProgram(buf, &first);
7075       }
7076       StartClocks();
7077     }
7078     ModeHighlight();
7079   }
7080
7081   /* Relay move to ICS or chess engine */
7082   if (appData.icsActive) {
7083     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7084         gameMode == IcsExamining) {
7085       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7086         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7087         SendToICS("draw ");
7088         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7089       }
7090       // also send plain move, in case ICS does not understand atomic claims
7091       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7092       ics_user_moved = 1;
7093     }
7094   } else {
7095     if (first.sendTime && (gameMode == BeginningOfGame ||
7096                            gameMode == MachinePlaysWhite ||
7097                            gameMode == MachinePlaysBlack)) {
7098       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7099     }
7100     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7101          // [HGM] book: if program might be playing, let it use book
7102         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7103         first.maybeThinking = TRUE;
7104     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7105         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7106         SendBoard(&first, currentMove+1);
7107         if(second.analyzing) {
7108             if(!second.useSetboard) SendToProgram("undo\n", &second);
7109             SendBoard(&second, currentMove+1);
7110         }
7111     } else {
7112         SendMoveToProgram(forwardMostMove-1, &first);
7113         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7114     }
7115     if (currentMove == cmailOldMove + 1) {
7116       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7117     }
7118   }
7119
7120   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7121
7122   switch (gameMode) {
7123   case EditGame:
7124     if(appData.testLegality)
7125     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7126     case MT_NONE:
7127     case MT_CHECK:
7128       break;
7129     case MT_CHECKMATE:
7130     case MT_STAINMATE:
7131       if (WhiteOnMove(currentMove)) {
7132         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7133       } else {
7134         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7135       }
7136       break;
7137     case MT_STALEMATE:
7138       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7139       break;
7140     }
7141     break;
7142
7143   case MachinePlaysBlack:
7144   case MachinePlaysWhite:
7145     /* disable certain menu options while machine is thinking */
7146     SetMachineThinkingEnables();
7147     break;
7148
7149   default:
7150     break;
7151   }
7152
7153   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7154   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7155
7156   if(bookHit) { // [HGM] book: simulate book reply
7157         static char bookMove[MSG_SIZ]; // a bit generous?
7158
7159         programStats.nodes = programStats.depth = programStats.time =
7160         programStats.score = programStats.got_only_move = 0;
7161         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7162
7163         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7164         strcat(bookMove, bookHit);
7165         HandleMachineMove(bookMove, &first);
7166   }
7167   return 1;
7168 }
7169
7170 void
7171 MarkByFEN(char *fen)
7172 {
7173         int r, f;
7174         if(!appData.markers || !appData.highlightDragging) return;
7175         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7176         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7177         while(*fen) {
7178             int s = 0;
7179             marker[r][f] = 0;
7180             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7181             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7182             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7183             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7184             if(*fen == 'T') marker[r][f++] = 0; else
7185             if(*fen == 'Y') marker[r][f++] = 1; else
7186             if(*fen == 'G') marker[r][f++] = 3; else
7187             if(*fen == 'B') marker[r][f++] = 4; else
7188             if(*fen == 'C') marker[r][f++] = 5; else
7189             if(*fen == 'M') marker[r][f++] = 6; else
7190             if(*fen == 'W') marker[r][f++] = 7; else
7191             if(*fen == 'D') marker[r][f++] = 8; else
7192             if(*fen == 'R') marker[r][f++] = 2; else {
7193                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7194               f += s; fen -= s>0;
7195             }
7196             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7197             if(r < 0) break;
7198             fen++;
7199         }
7200         DrawPosition(TRUE, NULL);
7201 }
7202
7203 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7204
7205 void
7206 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7207 {
7208     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7209     Markers *m = (Markers *) closure;
7210     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7211         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7212                          || kind == WhiteCapturesEnPassant
7213                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7214     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7215 }
7216
7217 static int hoverSavedValid;
7218
7219 void
7220 MarkTargetSquares (int clear)
7221 {
7222   int x, y, sum=0;
7223   if(clear) { // no reason to ever suppress clearing
7224     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7225     hoverSavedValid = 0;
7226     if(!sum) return; // nothing was cleared,no redraw needed
7227   } else {
7228     int capt = 0;
7229     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7230        !appData.testLegality || gameMode == EditPosition) return;
7231     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7232     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7233       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7234       if(capt)
7235       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7236     }
7237   }
7238   DrawPosition(FALSE, NULL);
7239 }
7240
7241 int
7242 Explode (Board board, int fromX, int fromY, int toX, int toY)
7243 {
7244     if(gameInfo.variant == VariantAtomic &&
7245        (board[toY][toX] != EmptySquare ||                     // capture?
7246         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7247                          board[fromY][fromX] == BlackPawn   )
7248       )) {
7249         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7250         return TRUE;
7251     }
7252     return FALSE;
7253 }
7254
7255 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7256
7257 int
7258 CanPromote (ChessSquare piece, int y)
7259 {
7260         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7261         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7262         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7263         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7264            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7265            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7266          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7267         return (piece == BlackPawn && y <= zone ||
7268                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7269                 piece == BlackLance && y == 1 ||
7270                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7271 }
7272
7273 void
7274 HoverEvent (int xPix, int yPix, int x, int y)
7275 {
7276         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7277         int r, f;
7278         if(!first.highlight) return;
7279         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7280         if(x == oldX && y == oldY) return; // only do something if we enter new square
7281         oldFromX = fromX; oldFromY = fromY;
7282         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7283           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7284             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7285           hoverSavedValid = 1;
7286         } else if(oldX != x || oldY != y) {
7287           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7288           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7289           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7290             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7291           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7292             char buf[MSG_SIZ];
7293             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7294             SendToProgram(buf, &first);
7295           }
7296           oldX = x; oldY = y;
7297 //        SetHighlights(fromX, fromY, x, y);
7298         }
7299 }
7300
7301 void ReportClick(char *action, int x, int y)
7302 {
7303         char buf[MSG_SIZ]; // Inform engine of what user does
7304         int r, f;
7305         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7306           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7307         if(!first.highlight || gameMode == EditPosition) return;
7308         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7309         SendToProgram(buf, &first);
7310 }
7311
7312 void
7313 LeftClick (ClickType clickType, int xPix, int yPix)
7314 {
7315     int x, y;
7316     Boolean saveAnimate;
7317     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7318     char promoChoice = NULLCHAR;
7319     ChessSquare piece;
7320     static TimeMark lastClickTime, prevClickTime;
7321
7322     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7323
7324     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7325
7326     if (clickType == Press) ErrorPopDown();
7327     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7328
7329     x = EventToSquare(xPix, BOARD_WIDTH);
7330     y = EventToSquare(yPix, BOARD_HEIGHT);
7331     if (!flipView && y >= 0) {
7332         y = BOARD_HEIGHT - 1 - y;
7333     }
7334     if (flipView && x >= 0) {
7335         x = BOARD_WIDTH - 1 - x;
7336     }
7337
7338     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7339         defaultPromoChoice = promoSweep;
7340         promoSweep = EmptySquare;   // terminate sweep
7341         promoDefaultAltered = TRUE;
7342         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7343     }
7344
7345     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7346         if(clickType == Release) return; // ignore upclick of click-click destination
7347         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7348         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7349         if(gameInfo.holdingsWidth &&
7350                 (WhiteOnMove(currentMove)
7351                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7352                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7353             // click in right holdings, for determining promotion piece
7354             ChessSquare p = boards[currentMove][y][x];
7355             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7356             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7357             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7358                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7359                 fromX = fromY = -1;
7360                 return;
7361             }
7362         }
7363         DrawPosition(FALSE, boards[currentMove]);
7364         return;
7365     }
7366
7367     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7368     if(clickType == Press
7369             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7370               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7371               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7372         return;
7373
7374     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7375         // could be static click on premove from-square: abort premove
7376         gotPremove = 0;
7377         ClearPremoveHighlights();
7378     }
7379
7380     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7381         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7382
7383     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7384         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7385                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7386         defaultPromoChoice = DefaultPromoChoice(side);
7387     }
7388
7389     autoQueen = appData.alwaysPromoteToQueen;
7390
7391     if (fromX == -1) {
7392       int originalY = y;
7393       gatingPiece = EmptySquare;
7394       if (clickType != Press) {
7395         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7396             DragPieceEnd(xPix, yPix); dragging = 0;
7397             DrawPosition(FALSE, NULL);
7398         }
7399         return;
7400       }
7401       doubleClick = FALSE;
7402       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7403         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7404       }
7405       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7406       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7407          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7408          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7409             /* First square */
7410             if (OKToStartUserMove(fromX, fromY)) {
7411                 second = 0;
7412                 ReportClick("lift", x, y);
7413                 MarkTargetSquares(0);
7414                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7415                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7416                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7417                     promoSweep = defaultPromoChoice;
7418                     selectFlag = 0; lastX = xPix; lastY = yPix;
7419                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7420                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7421                 }
7422                 if (appData.highlightDragging) {
7423                     SetHighlights(fromX, fromY, -1, -1);
7424                 } else {
7425                     ClearHighlights();
7426                 }
7427             } else fromX = fromY = -1;
7428             return;
7429         }
7430     }
7431
7432     /* fromX != -1 */
7433     if (clickType == Press && gameMode != EditPosition) {
7434         ChessSquare fromP;
7435         ChessSquare toP;
7436         int frc;
7437
7438         // ignore off-board to clicks
7439         if(y < 0 || x < 0) return;
7440
7441         /* Check if clicking again on the same color piece */
7442         fromP = boards[currentMove][fromY][fromX];
7443         toP = boards[currentMove][y][x];
7444         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7445         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7446            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7447              WhitePawn <= toP && toP <= WhiteKing &&
7448              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7449              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7450             (BlackPawn <= fromP && fromP <= BlackKing &&
7451              BlackPawn <= toP && toP <= BlackKing &&
7452              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7453              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7454             /* Clicked again on same color piece -- changed his mind */
7455             second = (x == fromX && y == fromY);
7456             killX = killY = -1;
7457             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7458                 second = FALSE; // first double-click rather than scond click
7459                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7460             }
7461             promoDefaultAltered = FALSE;
7462             MarkTargetSquares(1);
7463            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7464             if (appData.highlightDragging) {
7465                 SetHighlights(x, y, -1, -1);
7466             } else {
7467                 ClearHighlights();
7468             }
7469             if (OKToStartUserMove(x, y)) {
7470                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7471                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7472                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7473                  gatingPiece = boards[currentMove][fromY][fromX];
7474                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7475                 fromX = x;
7476                 fromY = y; dragging = 1;
7477                 ReportClick("lift", x, y);
7478                 MarkTargetSquares(0);
7479                 DragPieceBegin(xPix, yPix, FALSE);
7480                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7481                     promoSweep = defaultPromoChoice;
7482                     selectFlag = 0; lastX = xPix; lastY = yPix;
7483                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7484                 }
7485             }
7486            }
7487            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7488            second = FALSE;
7489         }
7490         // ignore clicks on holdings
7491         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7492     }
7493
7494     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7495         DragPieceEnd(xPix, yPix); dragging = 0;
7496         if(clearFlag) {
7497             // a deferred attempt to click-click move an empty square on top of a piece
7498             boards[currentMove][y][x] = EmptySquare;
7499             ClearHighlights();
7500             DrawPosition(FALSE, boards[currentMove]);
7501             fromX = fromY = -1; clearFlag = 0;
7502             return;
7503         }
7504         if (appData.animateDragging) {
7505             /* Undo animation damage if any */
7506             DrawPosition(FALSE, NULL);
7507         }
7508         if (second || sweepSelecting) {
7509             /* Second up/down in same square; just abort move */
7510             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7511             second = sweepSelecting = 0;
7512             fromX = fromY = -1;
7513             gatingPiece = EmptySquare;
7514             MarkTargetSquares(1);
7515             ClearHighlights();
7516             gotPremove = 0;
7517             ClearPremoveHighlights();
7518         } else {
7519             /* First upclick in same square; start click-click mode */
7520             SetHighlights(x, y, -1, -1);
7521         }
7522         return;
7523     }
7524
7525     clearFlag = 0;
7526
7527     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7528         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7529         DisplayMessage(_("only marked squares are legal"),"");
7530         DrawPosition(TRUE, NULL);
7531         return; // ignore to-click
7532     }
7533
7534     /* we now have a different from- and (possibly off-board) to-square */
7535     /* Completed move */
7536     if(!sweepSelecting) {
7537         toX = x;
7538         toY = y;
7539     }
7540
7541     piece = boards[currentMove][fromY][fromX];
7542
7543     saveAnimate = appData.animate;
7544     if (clickType == Press) {
7545         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7546         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7547             // must be Edit Position mode with empty-square selected
7548             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7549             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7550             return;
7551         }
7552         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7553             return;
7554         }
7555         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7556             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7557         } else
7558         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7559         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7560           if(appData.sweepSelect) {
7561             promoSweep = defaultPromoChoice;
7562             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7563             selectFlag = 0; lastX = xPix; lastY = yPix;
7564             Sweep(0); // Pawn that is going to promote: preview promotion piece
7565             sweepSelecting = 1;
7566             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7567             MarkTargetSquares(1);
7568           }
7569           return; // promo popup appears on up-click
7570         }
7571         /* Finish clickclick move */
7572         if (appData.animate || appData.highlightLastMove) {
7573             SetHighlights(fromX, fromY, toX, toY);
7574         } else {
7575             ClearHighlights();
7576         }
7577     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7578         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7579         if (appData.animate || appData.highlightLastMove) {
7580             SetHighlights(fromX, fromY, toX, toY);
7581         } else {
7582             ClearHighlights();
7583         }
7584     } else {
7585 #if 0
7586 // [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
7587         /* Finish drag move */
7588         if (appData.highlightLastMove) {
7589             SetHighlights(fromX, fromY, toX, toY);
7590         } else {
7591             ClearHighlights();
7592         }
7593 #endif
7594         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7595         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7596           dragging *= 2;            // flag button-less dragging if we are dragging
7597           MarkTargetSquares(1);
7598           if(x == killX && y == killY) killX = killY = -1; else {
7599             killX = x; killY = y;     //remeber this square as intermediate
7600             ReportClick("put", x, y); // and inform engine
7601             ReportClick("lift", x, y);
7602             MarkTargetSquares(0);
7603             return;
7604           }
7605         }
7606         DragPieceEnd(xPix, yPix); dragging = 0;
7607         /* Don't animate move and drag both */
7608         appData.animate = FALSE;
7609     }
7610
7611     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7612     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7613         ChessSquare piece = boards[currentMove][fromY][fromX];
7614         if(gameMode == EditPosition && piece != EmptySquare &&
7615            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7616             int n;
7617
7618             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7619                 n = PieceToNumber(piece - (int)BlackPawn);
7620                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7621                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7622                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7623             } else
7624             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7625                 n = PieceToNumber(piece);
7626                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7627                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7628                 boards[currentMove][n][BOARD_WIDTH-2]++;
7629             }
7630             boards[currentMove][fromY][fromX] = EmptySquare;
7631         }
7632         ClearHighlights();
7633         fromX = fromY = -1;
7634         MarkTargetSquares(1);
7635         DrawPosition(TRUE, boards[currentMove]);
7636         return;
7637     }
7638
7639     // off-board moves should not be highlighted
7640     if(x < 0 || y < 0) ClearHighlights();
7641     else ReportClick("put", x, y);
7642
7643     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7644
7645     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7646         SetHighlights(fromX, fromY, toX, toY);
7647         MarkTargetSquares(1);
7648         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7649             // [HGM] super: promotion to captured piece selected from holdings
7650             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7651             promotionChoice = TRUE;
7652             // kludge follows to temporarily execute move on display, without promoting yet
7653             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7654             boards[currentMove][toY][toX] = p;
7655             DrawPosition(FALSE, boards[currentMove]);
7656             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7657             boards[currentMove][toY][toX] = q;
7658             DisplayMessage("Click in holdings to choose piece", "");
7659             return;
7660         }
7661         PromotionPopUp(promoChoice);
7662     } else {
7663         int oldMove = currentMove;
7664         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7665         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7666         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7667         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7668            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7669             DrawPosition(TRUE, boards[currentMove]);
7670         MarkTargetSquares(1);
7671         fromX = fromY = -1;
7672     }
7673     appData.animate = saveAnimate;
7674     if (appData.animate || appData.animateDragging) {
7675         /* Undo animation damage if needed */
7676         DrawPosition(FALSE, NULL);
7677     }
7678 }
7679
7680 int
7681 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7682 {   // front-end-free part taken out of PieceMenuPopup
7683     int whichMenu; int xSqr, ySqr;
7684
7685     if(seekGraphUp) { // [HGM] seekgraph
7686         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7687         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7688         return -2;
7689     }
7690
7691     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7692          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7693         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7694         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7695         if(action == Press)   {
7696             originalFlip = flipView;
7697             flipView = !flipView; // temporarily flip board to see game from partners perspective
7698             DrawPosition(TRUE, partnerBoard);
7699             DisplayMessage(partnerStatus, "");
7700             partnerUp = TRUE;
7701         } else if(action == Release) {
7702             flipView = originalFlip;
7703             DrawPosition(TRUE, boards[currentMove]);
7704             partnerUp = FALSE;
7705         }
7706         return -2;
7707     }
7708
7709     xSqr = EventToSquare(x, BOARD_WIDTH);
7710     ySqr = EventToSquare(y, BOARD_HEIGHT);
7711     if (action == Release) {
7712         if(pieceSweep != EmptySquare) {
7713             EditPositionMenuEvent(pieceSweep, toX, toY);
7714             pieceSweep = EmptySquare;
7715         } else UnLoadPV(); // [HGM] pv
7716     }
7717     if (action != Press) return -2; // return code to be ignored
7718     switch (gameMode) {
7719       case IcsExamining:
7720         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7721       case EditPosition:
7722         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7723         if (xSqr < 0 || ySqr < 0) return -1;
7724         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7725         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7726         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7727         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7728         NextPiece(0);
7729         return 2; // grab
7730       case IcsObserving:
7731         if(!appData.icsEngineAnalyze) return -1;
7732       case IcsPlayingWhite:
7733       case IcsPlayingBlack:
7734         if(!appData.zippyPlay) goto noZip;
7735       case AnalyzeMode:
7736       case AnalyzeFile:
7737       case MachinePlaysWhite:
7738       case MachinePlaysBlack:
7739       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7740         if (!appData.dropMenu) {
7741           LoadPV(x, y);
7742           return 2; // flag front-end to grab mouse events
7743         }
7744         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7745            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7746       case EditGame:
7747       noZip:
7748         if (xSqr < 0 || ySqr < 0) return -1;
7749         if (!appData.dropMenu || appData.testLegality &&
7750             gameInfo.variant != VariantBughouse &&
7751             gameInfo.variant != VariantCrazyhouse) return -1;
7752         whichMenu = 1; // drop menu
7753         break;
7754       default:
7755         return -1;
7756     }
7757
7758     if (((*fromX = xSqr) < 0) ||
7759         ((*fromY = ySqr) < 0)) {
7760         *fromX = *fromY = -1;
7761         return -1;
7762     }
7763     if (flipView)
7764       *fromX = BOARD_WIDTH - 1 - *fromX;
7765     else
7766       *fromY = BOARD_HEIGHT - 1 - *fromY;
7767
7768     return whichMenu;
7769 }
7770
7771 void
7772 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7773 {
7774 //    char * hint = lastHint;
7775     FrontEndProgramStats stats;
7776
7777     stats.which = cps == &first ? 0 : 1;
7778     stats.depth = cpstats->depth;
7779     stats.nodes = cpstats->nodes;
7780     stats.score = cpstats->score;
7781     stats.time = cpstats->time;
7782     stats.pv = cpstats->movelist;
7783     stats.hint = lastHint;
7784     stats.an_move_index = 0;
7785     stats.an_move_count = 0;
7786
7787     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7788         stats.hint = cpstats->move_name;
7789         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7790         stats.an_move_count = cpstats->nr_moves;
7791     }
7792
7793     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
7794
7795     SetProgramStats( &stats );
7796 }
7797
7798 void
7799 ClearEngineOutputPane (int which)
7800 {
7801     static FrontEndProgramStats dummyStats;
7802     dummyStats.which = which;
7803     dummyStats.pv = "#";
7804     SetProgramStats( &dummyStats );
7805 }
7806
7807 #define MAXPLAYERS 500
7808
7809 char *
7810 TourneyStandings (int display)
7811 {
7812     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7813     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7814     char result, *p, *names[MAXPLAYERS];
7815
7816     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7817         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7818     names[0] = p = strdup(appData.participants);
7819     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7820
7821     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7822
7823     while(result = appData.results[nr]) {
7824         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7825         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7826         wScore = bScore = 0;
7827         switch(result) {
7828           case '+': wScore = 2; break;
7829           case '-': bScore = 2; break;
7830           case '=': wScore = bScore = 1; break;
7831           case ' ':
7832           case '*': return strdup("busy"); // tourney not finished
7833         }
7834         score[w] += wScore;
7835         score[b] += bScore;
7836         games[w]++;
7837         games[b]++;
7838         nr++;
7839     }
7840     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7841     for(w=0; w<nPlayers; w++) {
7842         bScore = -1;
7843         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7844         ranking[w] = b; points[w] = bScore; score[b] = -2;
7845     }
7846     p = malloc(nPlayers*34+1);
7847     for(w=0; w<nPlayers && w<display; w++)
7848         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7849     free(names[0]);
7850     return p;
7851 }
7852
7853 void
7854 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7855 {       // count all piece types
7856         int p, f, r;
7857         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7858         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7859         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7860                 p = board[r][f];
7861                 pCnt[p]++;
7862                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7863                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7864                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7865                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7866                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7867                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7868         }
7869 }
7870
7871 int
7872 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7873 {
7874         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7875         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7876
7877         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7878         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7879         if(myPawns == 2 && nMine == 3) // KPP
7880             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7881         if(myPawns == 1 && nMine == 2) // KP
7882             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7883         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7884             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7885         if(myPawns) return FALSE;
7886         if(pCnt[WhiteRook+side])
7887             return pCnt[BlackRook-side] ||
7888                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7889                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7890                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7891         if(pCnt[WhiteCannon+side]) {
7892             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7893             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7894         }
7895         if(pCnt[WhiteKnight+side])
7896             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7897         return FALSE;
7898 }
7899
7900 int
7901 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7902 {
7903         VariantClass v = gameInfo.variant;
7904
7905         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7906         if(v == VariantShatranj) return TRUE; // always winnable through baring
7907         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7908         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7909
7910         if(v == VariantXiangqi) {
7911                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7912
7913                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7914                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7915                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7916                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7917                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7918                 if(stale) // we have at least one last-rank P plus perhaps C
7919                     return majors // KPKX
7920                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7921                 else // KCA*E*
7922                     return pCnt[WhiteFerz+side] // KCAK
7923                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7924                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7925                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7926
7927         } else if(v == VariantKnightmate) {
7928                 if(nMine == 1) return FALSE;
7929                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7930         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7931                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7932
7933                 if(nMine == 1) return FALSE; // bare King
7934                 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
7935                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7936                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7937                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7938                 if(pCnt[WhiteKnight+side])
7939                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7940                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7941                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7942                 if(nBishops)
7943                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7944                 if(pCnt[WhiteAlfil+side])
7945                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7946                 if(pCnt[WhiteWazir+side])
7947                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7948         }
7949
7950         return TRUE;
7951 }
7952
7953 int
7954 CompareWithRights (Board b1, Board b2)
7955 {
7956     int rights = 0;
7957     if(!CompareBoards(b1, b2)) return FALSE;
7958     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7959     /* compare castling rights */
7960     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7961            rights++; /* King lost rights, while rook still had them */
7962     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7963         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7964            rights++; /* but at least one rook lost them */
7965     }
7966     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7967            rights++;
7968     if( b1[CASTLING][5] != NoRights ) {
7969         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7970            rights++;
7971     }
7972     return rights == 0;
7973 }
7974
7975 int
7976 Adjudicate (ChessProgramState *cps)
7977 {       // [HGM] some adjudications useful with buggy engines
7978         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7979         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7980         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7981         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7982         int k, drop, count = 0; static int bare = 1;
7983         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7984         Boolean canAdjudicate = !appData.icsActive;
7985
7986         // most tests only when we understand the game, i.e. legality-checking on
7987             if( appData.testLegality )
7988             {   /* [HGM] Some more adjudications for obstinate engines */
7989                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7990                 static int moveCount = 6;
7991                 ChessMove result;
7992                 char *reason = NULL;
7993
7994                 /* Count what is on board. */
7995                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7996
7997                 /* Some material-based adjudications that have to be made before stalemate test */
7998                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7999                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8000                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8001                      if(canAdjudicate && appData.checkMates) {
8002                          if(engineOpponent)
8003                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8004                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8005                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8006                          return 1;
8007                      }
8008                 }
8009
8010                 /* Bare King in Shatranj (loses) or Losers (wins) */
8011                 if( nrW == 1 || nrB == 1) {
8012                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8013                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8014                      if(canAdjudicate && appData.checkMates) {
8015                          if(engineOpponent)
8016                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8017                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8018                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8019                          return 1;
8020                      }
8021                   } else
8022                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8023                   {    /* bare King */
8024                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8025                         if(canAdjudicate && appData.checkMates) {
8026                             /* but only adjudicate if adjudication enabled */
8027                             if(engineOpponent)
8028                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8029                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8030                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8031                             return 1;
8032                         }
8033                   }
8034                 } else bare = 1;
8035
8036
8037             // don't wait for engine to announce game end if we can judge ourselves
8038             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8039               case MT_CHECK:
8040                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8041                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8042                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8043                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8044                             checkCnt++;
8045                         if(checkCnt >= 2) {
8046                             reason = "Xboard adjudication: 3rd check";
8047                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8048                             break;
8049                         }
8050                     }
8051                 }
8052               case MT_NONE:
8053               default:
8054                 break;
8055               case MT_STEALMATE:
8056               case MT_STALEMATE:
8057               case MT_STAINMATE:
8058                 reason = "Xboard adjudication: Stalemate";
8059                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8060                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8061                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8062                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8063                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8064                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8065                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8066                                                                         EP_CHECKMATE : EP_WINS);
8067                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8068                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8069                 }
8070                 break;
8071               case MT_CHECKMATE:
8072                 reason = "Xboard adjudication: Checkmate";
8073                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8074                 if(gameInfo.variant == VariantShogi) {
8075                     if(forwardMostMove > backwardMostMove
8076                        && moveList[forwardMostMove-1][1] == '@'
8077                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8078                         reason = "XBoard adjudication: pawn-drop mate";
8079                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8080                     }
8081                 }
8082                 break;
8083             }
8084
8085                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8086                     case EP_STALEMATE:
8087                         result = GameIsDrawn; break;
8088                     case EP_CHECKMATE:
8089                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8090                     case EP_WINS:
8091                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8092                     default:
8093                         result = EndOfFile;
8094                 }
8095                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8096                     if(engineOpponent)
8097                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8098                     GameEnds( result, reason, GE_XBOARD );
8099                     return 1;
8100                 }
8101
8102                 /* Next absolutely insufficient mating material. */
8103                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8104                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8105                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8106
8107                      /* always flag draws, for judging claims */
8108                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8109
8110                      if(canAdjudicate && appData.materialDraws) {
8111                          /* but only adjudicate them if adjudication enabled */
8112                          if(engineOpponent) {
8113                            SendToProgram("force\n", engineOpponent); // suppress reply
8114                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8115                          }
8116                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8117                          return 1;
8118                      }
8119                 }
8120
8121                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8122                 if(gameInfo.variant == VariantXiangqi ?
8123                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8124                  : nrW + nrB == 4 &&
8125                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8126                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8127                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8128                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8129                    ) ) {
8130                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8131                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8132                           if(engineOpponent) {
8133                             SendToProgram("force\n", engineOpponent); // suppress reply
8134                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8135                           }
8136                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8137                           return 1;
8138                      }
8139                 } else moveCount = 6;
8140             }
8141
8142         // Repetition draws and 50-move rule can be applied independently of legality testing
8143
8144                 /* Check for rep-draws */
8145                 count = 0;
8146                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8147                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8148                 for(k = forwardMostMove-2;
8149                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8150                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8151                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8152                     k-=2)
8153                 {   int rights=0;
8154                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8155                         /* compare castling rights */
8156                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8157                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8158                                 rights++; /* King lost rights, while rook still had them */
8159                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8160                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8161                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8162                                    rights++; /* but at least one rook lost them */
8163                         }
8164                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8165                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8166                                 rights++;
8167                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8168                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8169                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8170                                    rights++;
8171                         }
8172                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8173                             && appData.drawRepeats > 1) {
8174                              /* adjudicate after user-specified nr of repeats */
8175                              int result = GameIsDrawn;
8176                              char *details = "XBoard adjudication: repetition draw";
8177                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8178                                 // [HGM] xiangqi: check for forbidden perpetuals
8179                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8180                                 for(m=forwardMostMove; m>k; m-=2) {
8181                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8182                                         ourPerpetual = 0; // the current mover did not always check
8183                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8184                                         hisPerpetual = 0; // the opponent did not always check
8185                                 }
8186                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8187                                                                         ourPerpetual, hisPerpetual);
8188                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8189                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8190                                     details = "Xboard adjudication: perpetual checking";
8191                                 } else
8192                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8193                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8194                                 } else
8195                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8196                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8197                                         result = BlackWins;
8198                                         details = "Xboard adjudication: repetition";
8199                                     }
8200                                 } else // it must be XQ
8201                                 // Now check for perpetual chases
8202                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8203                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8204                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8205                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8206                                         static char resdet[MSG_SIZ];
8207                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8208                                         details = resdet;
8209                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8210                                     } else
8211                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8212                                         break; // Abort repetition-checking loop.
8213                                 }
8214                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8215                              }
8216                              if(engineOpponent) {
8217                                SendToProgram("force\n", engineOpponent); // suppress reply
8218                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8219                              }
8220                              GameEnds( result, details, GE_XBOARD );
8221                              return 1;
8222                         }
8223                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8224                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8225                     }
8226                 }
8227
8228                 /* Now we test for 50-move draws. Determine ply count */
8229                 count = forwardMostMove;
8230                 /* look for last irreversble move */
8231                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8232                     count--;
8233                 /* if we hit starting position, add initial plies */
8234                 if( count == backwardMostMove )
8235                     count -= initialRulePlies;
8236                 count = forwardMostMove - count;
8237                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8238                         // adjust reversible move counter for checks in Xiangqi
8239                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8240                         if(i < backwardMostMove) i = backwardMostMove;
8241                         while(i <= forwardMostMove) {
8242                                 lastCheck = inCheck; // check evasion does not count
8243                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8244                                 if(inCheck || lastCheck) count--; // check does not count
8245                                 i++;
8246                         }
8247                 }
8248                 if( count >= 100)
8249                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8250                          /* this is used to judge if draw claims are legal */
8251                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8252                          if(engineOpponent) {
8253                            SendToProgram("force\n", engineOpponent); // suppress reply
8254                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8255                          }
8256                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8257                          return 1;
8258                 }
8259
8260                 /* if draw offer is pending, treat it as a draw claim
8261                  * when draw condition present, to allow engines a way to
8262                  * claim draws before making their move to avoid a race
8263                  * condition occurring after their move
8264                  */
8265                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8266                          char *p = NULL;
8267                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8268                              p = "Draw claim: 50-move rule";
8269                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8270                              p = "Draw claim: 3-fold repetition";
8271                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8272                              p = "Draw claim: insufficient mating material";
8273                          if( p != NULL && canAdjudicate) {
8274                              if(engineOpponent) {
8275                                SendToProgram("force\n", engineOpponent); // suppress reply
8276                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8277                              }
8278                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8279                              return 1;
8280                          }
8281                 }
8282
8283                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8284                     if(engineOpponent) {
8285                       SendToProgram("force\n", engineOpponent); // suppress reply
8286                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8287                     }
8288                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8289                     return 1;
8290                 }
8291         return 0;
8292 }
8293
8294 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8295 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8296 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8297
8298 static int
8299 BitbaseProbe ()
8300 {
8301     int pieces[10], squares[10], cnt=0, r, f, res;
8302     static int loaded;
8303     static PPROBE_EGBB probeBB;
8304     if(!appData.testLegality) return 10;
8305     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8306     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8307     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8308     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8309         ChessSquare piece = boards[forwardMostMove][r][f];
8310         int black = (piece >= BlackPawn);
8311         int type = piece - black*BlackPawn;
8312         if(piece == EmptySquare) continue;
8313         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8314         if(type == WhiteKing) type = WhiteQueen + 1;
8315         type = egbbCode[type];
8316         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8317         pieces[cnt] = type + black*6;
8318         if(++cnt > 5) return 11;
8319     }
8320     pieces[cnt] = squares[cnt] = 0;
8321     // probe EGBB
8322     if(loaded == 2) return 13; // loading failed before
8323     if(loaded == 0) {
8324         loaded = 2; // prepare for failure
8325         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8326         HMODULE lib;
8327         PLOAD_EGBB loadBB;
8328         if(!path) return 13; // no egbb installed
8329         strncpy(buf, path + 8, MSG_SIZ);
8330         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8331         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8332         lib = LoadLibrary(buf);
8333         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8334         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8335         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8336         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8337         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8338         loaded = 1; // success!
8339     }
8340     res = probeBB(forwardMostMove & 1, pieces, squares);
8341     return res > 0 ? 1 : res < 0 ? -1 : 0;
8342 }
8343
8344 char *
8345 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8346 {   // [HGM] book: this routine intercepts moves to simulate book replies
8347     char *bookHit = NULL;
8348
8349     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8350         char buf[MSG_SIZ];
8351         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8352         SendToProgram(buf, cps);
8353     }
8354     //first determine if the incoming move brings opponent into his book
8355     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8356         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8357     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8358     if(bookHit != NULL && !cps->bookSuspend) {
8359         // make sure opponent is not going to reply after receiving move to book position
8360         SendToProgram("force\n", cps);
8361         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8362     }
8363     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8364     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8365     // now arrange restart after book miss
8366     if(bookHit) {
8367         // after a book hit we never send 'go', and the code after the call to this routine
8368         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8369         char buf[MSG_SIZ], *move = bookHit;
8370         if(cps->useSAN) {
8371             int fromX, fromY, toX, toY;
8372             char promoChar;
8373             ChessMove moveType;
8374             move = buf + 30;
8375             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8376                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8377                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8378                                     PosFlags(forwardMostMove),
8379                                     fromY, fromX, toY, toX, promoChar, move);
8380             } else {
8381                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8382                 bookHit = NULL;
8383             }
8384         }
8385         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8386         SendToProgram(buf, cps);
8387         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8388     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8389         SendToProgram("go\n", cps);
8390         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8391     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8392         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8393             SendToProgram("go\n", cps);
8394         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8395     }
8396     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8397 }
8398
8399 int
8400 LoadError (char *errmess, ChessProgramState *cps)
8401 {   // unloads engine and switches back to -ncp mode if it was first
8402     if(cps->initDone) return FALSE;
8403     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8404     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8405     cps->pr = NoProc;
8406     if(cps == &first) {
8407         appData.noChessProgram = TRUE;
8408         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8409         gameMode = BeginningOfGame; ModeHighlight();
8410         SetNCPMode();
8411     }
8412     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8413     DisplayMessage("", ""); // erase waiting message
8414     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8415     return TRUE;
8416 }
8417
8418 char *savedMessage;
8419 ChessProgramState *savedState;
8420 void
8421 DeferredBookMove (void)
8422 {
8423         if(savedState->lastPing != savedState->lastPong)
8424                     ScheduleDelayedEvent(DeferredBookMove, 10);
8425         else
8426         HandleMachineMove(savedMessage, savedState);
8427 }
8428
8429 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8430 static ChessProgramState *stalledEngine;
8431 static char stashedInputMove[MSG_SIZ];
8432
8433 void
8434 HandleMachineMove (char *message, ChessProgramState *cps)
8435 {
8436     static char firstLeg[20];
8437     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8438     char realname[MSG_SIZ];
8439     int fromX, fromY, toX, toY;
8440     ChessMove moveType;
8441     char promoChar, roar;
8442     char *p, *pv=buf1;
8443     int machineWhite, oldError;
8444     char *bookHit;
8445
8446     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8447         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8448         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8449             DisplayError(_("Invalid pairing from pairing engine"), 0);
8450             return;
8451         }
8452         pairingReceived = 1;
8453         NextMatchGame();
8454         return; // Skim the pairing messages here.
8455     }
8456
8457     oldError = cps->userError; cps->userError = 0;
8458
8459 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8460     /*
8461      * Kludge to ignore BEL characters
8462      */
8463     while (*message == '\007') message++;
8464
8465     /*
8466      * [HGM] engine debug message: ignore lines starting with '#' character
8467      */
8468     if(cps->debug && *message == '#') return;
8469
8470     /*
8471      * Look for book output
8472      */
8473     if (cps == &first && bookRequested) {
8474         if (message[0] == '\t' || message[0] == ' ') {
8475             /* Part of the book output is here; append it */
8476             strcat(bookOutput, message);
8477             strcat(bookOutput, "  \n");
8478             return;
8479         } else if (bookOutput[0] != NULLCHAR) {
8480             /* All of book output has arrived; display it */
8481             char *p = bookOutput;
8482             while (*p != NULLCHAR) {
8483                 if (*p == '\t') *p = ' ';
8484                 p++;
8485             }
8486             DisplayInformation(bookOutput);
8487             bookRequested = FALSE;
8488             /* Fall through to parse the current output */
8489         }
8490     }
8491
8492     /*
8493      * Look for machine move.
8494      */
8495     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8496         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8497     {
8498         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8499             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8500             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8501             stalledEngine = cps;
8502             if(appData.ponderNextMove) { // bring opponent out of ponder
8503                 if(gameMode == TwoMachinesPlay) {
8504                     if(cps->other->pause)
8505                         PauseEngine(cps->other);
8506                     else
8507                         SendToProgram("easy\n", cps->other);
8508                 }
8509             }
8510             StopClocks();
8511             return;
8512         }
8513
8514         /* This method is only useful on engines that support ping */
8515         if (cps->lastPing != cps->lastPong) {
8516           if (gameMode == BeginningOfGame) {
8517             /* Extra move from before last new; ignore */
8518             if (appData.debugMode) {
8519                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8520             }
8521           } else {
8522             if (appData.debugMode) {
8523                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8524                         cps->which, gameMode);
8525             }
8526
8527             SendToProgram("undo\n", cps);
8528           }
8529           return;
8530         }
8531
8532         switch (gameMode) {
8533           case BeginningOfGame:
8534             /* Extra move from before last reset; ignore */
8535             if (appData.debugMode) {
8536                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8537             }
8538             return;
8539
8540           case EndOfGame:
8541           case IcsIdle:
8542           default:
8543             /* Extra move after we tried to stop.  The mode test is
8544                not a reliable way of detecting this problem, but it's
8545                the best we can do on engines that don't support ping.
8546             */
8547             if (appData.debugMode) {
8548                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8549                         cps->which, gameMode);
8550             }
8551             SendToProgram("undo\n", cps);
8552             return;
8553
8554           case MachinePlaysWhite:
8555           case IcsPlayingWhite:
8556             machineWhite = TRUE;
8557             break;
8558
8559           case MachinePlaysBlack:
8560           case IcsPlayingBlack:
8561             machineWhite = FALSE;
8562             break;
8563
8564           case TwoMachinesPlay:
8565             machineWhite = (cps->twoMachinesColor[0] == 'w');
8566             break;
8567         }
8568         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8569             if (appData.debugMode) {
8570                 fprintf(debugFP,
8571                         "Ignoring move out of turn by %s, gameMode %d"
8572                         ", forwardMost %d\n",
8573                         cps->which, gameMode, forwardMostMove);
8574             }
8575             return;
8576         }
8577
8578         if(cps->alphaRank) AlphaRank(machineMove, 4);
8579
8580         // [HGM] lion: (some very limited) support for Alien protocol
8581         killX = killY = -1;
8582         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8583             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8584             return;
8585         } else if(firstLeg[0]) { // there was a previous leg;
8586             // only support case where same piece makes two step (and don't even test that!)
8587             char buf[20], *p = machineMove+1, *q = buf+1, f;
8588             safeStrCpy(buf, machineMove, 20);
8589             while(isdigit(*q)) q++; // find start of to-square
8590             safeStrCpy(machineMove, firstLeg, 20);
8591             while(isdigit(*p)) p++;
8592             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8593             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8594             firstLeg[0] = NULLCHAR;
8595         }
8596
8597         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8598                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8599             /* Machine move could not be parsed; ignore it. */
8600           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8601                     machineMove, _(cps->which));
8602             DisplayMoveError(buf1);
8603             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8604                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8605             if (gameMode == TwoMachinesPlay) {
8606               GameEnds(machineWhite ? BlackWins : WhiteWins,
8607                        buf1, GE_XBOARD);
8608             }
8609             return;
8610         }
8611
8612         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8613         /* So we have to redo legality test with true e.p. status here,  */
8614         /* to make sure an illegal e.p. capture does not slip through,   */
8615         /* to cause a forfeit on a justified illegal-move complaint      */
8616         /* of the opponent.                                              */
8617         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8618            ChessMove moveType;
8619            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8620                              fromY, fromX, toY, toX, promoChar);
8621             if(moveType == IllegalMove) {
8622               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8623                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8624                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8625                            buf1, GE_XBOARD);
8626                 return;
8627            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8628            /* [HGM] Kludge to handle engines that send FRC-style castling
8629               when they shouldn't (like TSCP-Gothic) */
8630            switch(moveType) {
8631              case WhiteASideCastleFR:
8632              case BlackASideCastleFR:
8633                toX+=2;
8634                currentMoveString[2]++;
8635                break;
8636              case WhiteHSideCastleFR:
8637              case BlackHSideCastleFR:
8638                toX--;
8639                currentMoveString[2]--;
8640                break;
8641              default: ; // nothing to do, but suppresses warning of pedantic compilers
8642            }
8643         }
8644         hintRequested = FALSE;
8645         lastHint[0] = NULLCHAR;
8646         bookRequested = FALSE;
8647         /* Program may be pondering now */
8648         cps->maybeThinking = TRUE;
8649         if (cps->sendTime == 2) cps->sendTime = 1;
8650         if (cps->offeredDraw) cps->offeredDraw--;
8651
8652         /* [AS] Save move info*/
8653         pvInfoList[ forwardMostMove ].score = programStats.score;
8654         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8655         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8656
8657         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8658
8659         /* Test suites abort the 'game' after one move */
8660         if(*appData.finger) {
8661            static FILE *f;
8662            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8663            if(!f) f = fopen(appData.finger, "w");
8664            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8665            else { DisplayFatalError("Bad output file", errno, 0); return; }
8666            free(fen);
8667            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8668         }
8669
8670         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8671         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8672             int count = 0;
8673
8674             while( count < adjudicateLossPlies ) {
8675                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8676
8677                 if( count & 1 ) {
8678                     score = -score; /* Flip score for winning side */
8679                 }
8680
8681                 if( score > adjudicateLossThreshold ) {
8682                     break;
8683                 }
8684
8685                 count++;
8686             }
8687
8688             if( count >= adjudicateLossPlies ) {
8689                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8690
8691                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8692                     "Xboard adjudication",
8693                     GE_XBOARD );
8694
8695                 return;
8696             }
8697         }
8698
8699         if(Adjudicate(cps)) {
8700             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8701             return; // [HGM] adjudicate: for all automatic game ends
8702         }
8703
8704 #if ZIPPY
8705         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8706             first.initDone) {
8707           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8708                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8709                 SendToICS("draw ");
8710                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8711           }
8712           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8713           ics_user_moved = 1;
8714           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8715                 char buf[3*MSG_SIZ];
8716
8717                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8718                         programStats.score / 100.,
8719                         programStats.depth,
8720                         programStats.time / 100.,
8721                         (unsigned int)programStats.nodes,
8722                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8723                         programStats.movelist);
8724                 SendToICS(buf);
8725 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8726           }
8727         }
8728 #endif
8729
8730         /* [AS] Clear stats for next move */
8731         ClearProgramStats();
8732         thinkOutput[0] = NULLCHAR;
8733         hiddenThinkOutputState = 0;
8734
8735         bookHit = NULL;
8736         if (gameMode == TwoMachinesPlay) {
8737             /* [HGM] relaying draw offers moved to after reception of move */
8738             /* and interpreting offer as claim if it brings draw condition */
8739             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8740                 SendToProgram("draw\n", cps->other);
8741             }
8742             if (cps->other->sendTime) {
8743                 SendTimeRemaining(cps->other,
8744                                   cps->other->twoMachinesColor[0] == 'w');
8745             }
8746             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8747             if (firstMove && !bookHit) {
8748                 firstMove = FALSE;
8749                 if (cps->other->useColors) {
8750                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8751                 }
8752                 SendToProgram("go\n", cps->other);
8753             }
8754             cps->other->maybeThinking = TRUE;
8755         }
8756
8757         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8758
8759         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8760
8761         if (!pausing && appData.ringBellAfterMoves) {
8762             if(!roar) RingBell();
8763         }
8764
8765         /*
8766          * Reenable menu items that were disabled while
8767          * machine was thinking
8768          */
8769         if (gameMode != TwoMachinesPlay)
8770             SetUserThinkingEnables();
8771
8772         // [HGM] book: after book hit opponent has received move and is now in force mode
8773         // force the book reply into it, and then fake that it outputted this move by jumping
8774         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8775         if(bookHit) {
8776                 static char bookMove[MSG_SIZ]; // a bit generous?
8777
8778                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8779                 strcat(bookMove, bookHit);
8780                 message = bookMove;
8781                 cps = cps->other;
8782                 programStats.nodes = programStats.depth = programStats.time =
8783                 programStats.score = programStats.got_only_move = 0;
8784                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8785
8786                 if(cps->lastPing != cps->lastPong) {
8787                     savedMessage = message; // args for deferred call
8788                     savedState = cps;
8789                     ScheduleDelayedEvent(DeferredBookMove, 10);
8790                     return;
8791                 }
8792                 goto FakeBookMove;
8793         }
8794
8795         return;
8796     }
8797
8798     /* Set special modes for chess engines.  Later something general
8799      *  could be added here; for now there is just one kludge feature,
8800      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8801      *  when "xboard" is given as an interactive command.
8802      */
8803     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8804         cps->useSigint = FALSE;
8805         cps->useSigterm = FALSE;
8806     }
8807     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8808       ParseFeatures(message+8, cps);
8809       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8810     }
8811
8812     if (!strncmp(message, "setup ", 6) && 
8813         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8814           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8815                                         ) { // [HGM] allow first engine to define opening position
8816       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8817       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8818       *buf = NULLCHAR;
8819       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8820       if(startedFromSetupPosition) return;
8821       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8822       if(dummy >= 3) {
8823         while(message[s] && message[s++] != ' ');
8824         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8825            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8826             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8827             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8828           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8829           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8830         }
8831       }
8832       ParseFEN(boards[0], &dummy, message+s, FALSE);
8833       DrawPosition(TRUE, boards[0]);
8834       startedFromSetupPosition = TRUE;
8835       return;
8836     }
8837     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8838      * want this, I was asked to put it in, and obliged.
8839      */
8840     if (!strncmp(message, "setboard ", 9)) {
8841         Board initial_position;
8842
8843         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8844
8845         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8846             DisplayError(_("Bad FEN received from engine"), 0);
8847             return ;
8848         } else {
8849            Reset(TRUE, FALSE);
8850            CopyBoard(boards[0], initial_position);
8851            initialRulePlies = FENrulePlies;
8852            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8853            else gameMode = MachinePlaysBlack;
8854            DrawPosition(FALSE, boards[currentMove]);
8855         }
8856         return;
8857     }
8858
8859     /*
8860      * Look for communication commands
8861      */
8862     if (!strncmp(message, "telluser ", 9)) {
8863         if(message[9] == '\\' && message[10] == '\\')
8864             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8865         PlayTellSound();
8866         DisplayNote(message + 9);
8867         return;
8868     }
8869     if (!strncmp(message, "tellusererror ", 14)) {
8870         cps->userError = 1;
8871         if(message[14] == '\\' && message[15] == '\\')
8872             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8873         PlayTellSound();
8874         DisplayError(message + 14, 0);
8875         return;
8876     }
8877     if (!strncmp(message, "tellopponent ", 13)) {
8878       if (appData.icsActive) {
8879         if (loggedOn) {
8880           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8881           SendToICS(buf1);
8882         }
8883       } else {
8884         DisplayNote(message + 13);
8885       }
8886       return;
8887     }
8888     if (!strncmp(message, "tellothers ", 11)) {
8889       if (appData.icsActive) {
8890         if (loggedOn) {
8891           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8892           SendToICS(buf1);
8893         }
8894       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8895       return;
8896     }
8897     if (!strncmp(message, "tellall ", 8)) {
8898       if (appData.icsActive) {
8899         if (loggedOn) {
8900           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8901           SendToICS(buf1);
8902         }
8903       } else {
8904         DisplayNote(message + 8);
8905       }
8906       return;
8907     }
8908     if (strncmp(message, "warning", 7) == 0) {
8909         /* Undocumented feature, use tellusererror in new code */
8910         DisplayError(message, 0);
8911         return;
8912     }
8913     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8914         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8915         strcat(realname, " query");
8916         AskQuestion(realname, buf2, buf1, cps->pr);
8917         return;
8918     }
8919     /* Commands from the engine directly to ICS.  We don't allow these to be
8920      *  sent until we are logged on. Crafty kibitzes have been known to
8921      *  interfere with the login process.
8922      */
8923     if (loggedOn) {
8924         if (!strncmp(message, "tellics ", 8)) {
8925             SendToICS(message + 8);
8926             SendToICS("\n");
8927             return;
8928         }
8929         if (!strncmp(message, "tellicsnoalias ", 15)) {
8930             SendToICS(ics_prefix);
8931             SendToICS(message + 15);
8932             SendToICS("\n");
8933             return;
8934         }
8935         /* The following are for backward compatibility only */
8936         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8937             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8938             SendToICS(ics_prefix);
8939             SendToICS(message);
8940             SendToICS("\n");
8941             return;
8942         }
8943     }
8944     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8945         if(initPing == cps->lastPong) {
8946             if(gameInfo.variant == VariantUnknown) {
8947                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8948                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8949                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8950             }
8951             initPing = -1;
8952         }
8953         return;
8954     }
8955     if(!strncmp(message, "highlight ", 10)) {
8956         if(appData.testLegality && appData.markers) return;
8957         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8958         return;
8959     }
8960     if(!strncmp(message, "click ", 6)) {
8961         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8962         if(appData.testLegality || !appData.oneClick) return;
8963         sscanf(message+6, "%c%d%c", &f, &y, &c);
8964         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8965         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8966         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8967         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8968         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8969         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8970             LeftClick(Release, lastLeftX, lastLeftY);
8971         controlKey  = (c == ',');
8972         LeftClick(Press, x, y);
8973         LeftClick(Release, x, y);
8974         first.highlight = f;
8975         return;
8976     }
8977     /*
8978      * If the move is illegal, cancel it and redraw the board.
8979      * Also deal with other error cases.  Matching is rather loose
8980      * here to accommodate engines written before the spec.
8981      */
8982     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8983         strncmp(message, "Error", 5) == 0) {
8984         if (StrStr(message, "name") ||
8985             StrStr(message, "rating") || StrStr(message, "?") ||
8986             StrStr(message, "result") || StrStr(message, "board") ||
8987             StrStr(message, "bk") || StrStr(message, "computer") ||
8988             StrStr(message, "variant") || StrStr(message, "hint") ||
8989             StrStr(message, "random") || StrStr(message, "depth") ||
8990             StrStr(message, "accepted")) {
8991             return;
8992         }
8993         if (StrStr(message, "protover")) {
8994           /* Program is responding to input, so it's apparently done
8995              initializing, and this error message indicates it is
8996              protocol version 1.  So we don't need to wait any longer
8997              for it to initialize and send feature commands. */
8998           FeatureDone(cps, 1);
8999           cps->protocolVersion = 1;
9000           return;
9001         }
9002         cps->maybeThinking = FALSE;
9003
9004         if (StrStr(message, "draw")) {
9005             /* Program doesn't have "draw" command */
9006             cps->sendDrawOffers = 0;
9007             return;
9008         }
9009         if (cps->sendTime != 1 &&
9010             (StrStr(message, "time") || StrStr(message, "otim"))) {
9011           /* Program apparently doesn't have "time" or "otim" command */
9012           cps->sendTime = 0;
9013           return;
9014         }
9015         if (StrStr(message, "analyze")) {
9016             cps->analysisSupport = FALSE;
9017             cps->analyzing = FALSE;
9018 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9019             EditGameEvent(); // [HGM] try to preserve loaded game
9020             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9021             DisplayError(buf2, 0);
9022             return;
9023         }
9024         if (StrStr(message, "(no matching move)st")) {
9025           /* Special kludge for GNU Chess 4 only */
9026           cps->stKludge = TRUE;
9027           SendTimeControl(cps, movesPerSession, timeControl,
9028                           timeIncrement, appData.searchDepth,
9029                           searchTime);
9030           return;
9031         }
9032         if (StrStr(message, "(no matching move)sd")) {
9033           /* Special kludge for GNU Chess 4 only */
9034           cps->sdKludge = TRUE;
9035           SendTimeControl(cps, movesPerSession, timeControl,
9036                           timeIncrement, appData.searchDepth,
9037                           searchTime);
9038           return;
9039         }
9040         if (!StrStr(message, "llegal")) {
9041             return;
9042         }
9043         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9044             gameMode == IcsIdle) return;
9045         if (forwardMostMove <= backwardMostMove) return;
9046         if (pausing) PauseEvent();
9047       if(appData.forceIllegal) {
9048             // [HGM] illegal: machine refused move; force position after move into it
9049           SendToProgram("force\n", cps);
9050           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9051                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9052                 // when black is to move, while there might be nothing on a2 or black
9053                 // might already have the move. So send the board as if white has the move.
9054                 // But first we must change the stm of the engine, as it refused the last move
9055                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9056                 if(WhiteOnMove(forwardMostMove)) {
9057                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9058                     SendBoard(cps, forwardMostMove); // kludgeless board
9059                 } else {
9060                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9061                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9062                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9063                 }
9064           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9065             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9066                  gameMode == TwoMachinesPlay)
9067               SendToProgram("go\n", cps);
9068             return;
9069       } else
9070         if (gameMode == PlayFromGameFile) {
9071             /* Stop reading this game file */
9072             gameMode = EditGame;
9073             ModeHighlight();
9074         }
9075         /* [HGM] illegal-move claim should forfeit game when Xboard */
9076         /* only passes fully legal moves                            */
9077         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9078             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9079                                 "False illegal-move claim", GE_XBOARD );
9080             return; // do not take back move we tested as valid
9081         }
9082         currentMove = forwardMostMove-1;
9083         DisplayMove(currentMove-1); /* before DisplayMoveError */
9084         SwitchClocks(forwardMostMove-1); // [HGM] race
9085         DisplayBothClocks();
9086         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9087                 parseList[currentMove], _(cps->which));
9088         DisplayMoveError(buf1);
9089         DrawPosition(FALSE, boards[currentMove]);
9090
9091         SetUserThinkingEnables();
9092         return;
9093     }
9094     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9095         /* Program has a broken "time" command that
9096            outputs a string not ending in newline.
9097            Don't use it. */
9098         cps->sendTime = 0;
9099     }
9100
9101     /*
9102      * If chess program startup fails, exit with an error message.
9103      * Attempts to recover here are futile. [HGM] Well, we try anyway
9104      */
9105     if ((StrStr(message, "unknown host") != NULL)
9106         || (StrStr(message, "No remote directory") != NULL)
9107         || (StrStr(message, "not found") != NULL)
9108         || (StrStr(message, "No such file") != NULL)
9109         || (StrStr(message, "can't alloc") != NULL)
9110         || (StrStr(message, "Permission denied") != NULL)) {
9111
9112         cps->maybeThinking = FALSE;
9113         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9114                 _(cps->which), cps->program, cps->host, message);
9115         RemoveInputSource(cps->isr);
9116         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9117             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9118             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9119         }
9120         return;
9121     }
9122
9123     /*
9124      * Look for hint output
9125      */
9126     if (sscanf(message, "Hint: %s", buf1) == 1) {
9127         if (cps == &first && hintRequested) {
9128             hintRequested = FALSE;
9129             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9130                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9131                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9132                                     PosFlags(forwardMostMove),
9133                                     fromY, fromX, toY, toX, promoChar, buf1);
9134                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9135                 DisplayInformation(buf2);
9136             } else {
9137                 /* Hint move could not be parsed!? */
9138               snprintf(buf2, sizeof(buf2),
9139                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9140                         buf1, _(cps->which));
9141                 DisplayError(buf2, 0);
9142             }
9143         } else {
9144           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9145         }
9146         return;
9147     }
9148
9149     /*
9150      * Ignore other messages if game is not in progress
9151      */
9152     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9153         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9154
9155     /*
9156      * look for win, lose, draw, or draw offer
9157      */
9158     if (strncmp(message, "1-0", 3) == 0) {
9159         char *p, *q, *r = "";
9160         p = strchr(message, '{');
9161         if (p) {
9162             q = strchr(p, '}');
9163             if (q) {
9164                 *q = NULLCHAR;
9165                 r = p + 1;
9166             }
9167         }
9168         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9169         return;
9170     } else if (strncmp(message, "0-1", 3) == 0) {
9171         char *p, *q, *r = "";
9172         p = strchr(message, '{');
9173         if (p) {
9174             q = strchr(p, '}');
9175             if (q) {
9176                 *q = NULLCHAR;
9177                 r = p + 1;
9178             }
9179         }
9180         /* Kludge for Arasan 4.1 bug */
9181         if (strcmp(r, "Black resigns") == 0) {
9182             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9183             return;
9184         }
9185         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9186         return;
9187     } else if (strncmp(message, "1/2", 3) == 0) {
9188         char *p, *q, *r = "";
9189         p = strchr(message, '{');
9190         if (p) {
9191             q = strchr(p, '}');
9192             if (q) {
9193                 *q = NULLCHAR;
9194                 r = p + 1;
9195             }
9196         }
9197
9198         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9199         return;
9200
9201     } else if (strncmp(message, "White resign", 12) == 0) {
9202         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9203         return;
9204     } else if (strncmp(message, "Black resign", 12) == 0) {
9205         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9206         return;
9207     } else if (strncmp(message, "White matches", 13) == 0 ||
9208                strncmp(message, "Black matches", 13) == 0   ) {
9209         /* [HGM] ignore GNUShogi noises */
9210         return;
9211     } else if (strncmp(message, "White", 5) == 0 &&
9212                message[5] != '(' &&
9213                StrStr(message, "Black") == NULL) {
9214         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9215         return;
9216     } else if (strncmp(message, "Black", 5) == 0 &&
9217                message[5] != '(') {
9218         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9219         return;
9220     } else if (strcmp(message, "resign") == 0 ||
9221                strcmp(message, "computer resigns") == 0) {
9222         switch (gameMode) {
9223           case MachinePlaysBlack:
9224           case IcsPlayingBlack:
9225             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9226             break;
9227           case MachinePlaysWhite:
9228           case IcsPlayingWhite:
9229             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9230             break;
9231           case TwoMachinesPlay:
9232             if (cps->twoMachinesColor[0] == 'w')
9233               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9234             else
9235               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9236             break;
9237           default:
9238             /* can't happen */
9239             break;
9240         }
9241         return;
9242     } else if (strncmp(message, "opponent mates", 14) == 0) {
9243         switch (gameMode) {
9244           case MachinePlaysBlack:
9245           case IcsPlayingBlack:
9246             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9247             break;
9248           case MachinePlaysWhite:
9249           case IcsPlayingWhite:
9250             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9251             break;
9252           case TwoMachinesPlay:
9253             if (cps->twoMachinesColor[0] == 'w')
9254               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9255             else
9256               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9257             break;
9258           default:
9259             /* can't happen */
9260             break;
9261         }
9262         return;
9263     } else if (strncmp(message, "computer mates", 14) == 0) {
9264         switch (gameMode) {
9265           case MachinePlaysBlack:
9266           case IcsPlayingBlack:
9267             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9268             break;
9269           case MachinePlaysWhite:
9270           case IcsPlayingWhite:
9271             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9272             break;
9273           case TwoMachinesPlay:
9274             if (cps->twoMachinesColor[0] == 'w')
9275               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9276             else
9277               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9278             break;
9279           default:
9280             /* can't happen */
9281             break;
9282         }
9283         return;
9284     } else if (strncmp(message, "checkmate", 9) == 0) {
9285         if (WhiteOnMove(forwardMostMove)) {
9286             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9287         } else {
9288             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9289         }
9290         return;
9291     } else if (strstr(message, "Draw") != NULL ||
9292                strstr(message, "game is a draw") != NULL) {
9293         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9294         return;
9295     } else if (strstr(message, "offer") != NULL &&
9296                strstr(message, "draw") != NULL) {
9297 #if ZIPPY
9298         if (appData.zippyPlay && first.initDone) {
9299             /* Relay offer to ICS */
9300             SendToICS(ics_prefix);
9301             SendToICS("draw\n");
9302         }
9303 #endif
9304         cps->offeredDraw = 2; /* valid until this engine moves twice */
9305         if (gameMode == TwoMachinesPlay) {
9306             if (cps->other->offeredDraw) {
9307                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9308             /* [HGM] in two-machine mode we delay relaying draw offer      */
9309             /* until after we also have move, to see if it is really claim */
9310             }
9311         } else if (gameMode == MachinePlaysWhite ||
9312                    gameMode == MachinePlaysBlack) {
9313           if (userOfferedDraw) {
9314             DisplayInformation(_("Machine accepts your draw offer"));
9315             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9316           } else {
9317             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9318           }
9319         }
9320     }
9321
9322
9323     /*
9324      * Look for thinking output
9325      */
9326     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9327           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9328                                 ) {
9329         int plylev, mvleft, mvtot, curscore, time;
9330         char mvname[MOVE_LEN];
9331         u64 nodes; // [DM]
9332         char plyext;
9333         int ignore = FALSE;
9334         int prefixHint = FALSE;
9335         mvname[0] = NULLCHAR;
9336
9337         switch (gameMode) {
9338           case MachinePlaysBlack:
9339           case IcsPlayingBlack:
9340             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9341             break;
9342           case MachinePlaysWhite:
9343           case IcsPlayingWhite:
9344             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9345             break;
9346           case AnalyzeMode:
9347           case AnalyzeFile:
9348             break;
9349           case IcsObserving: /* [DM] icsEngineAnalyze */
9350             if (!appData.icsEngineAnalyze) ignore = TRUE;
9351             break;
9352           case TwoMachinesPlay:
9353             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9354                 ignore = TRUE;
9355             }
9356             break;
9357           default:
9358             ignore = TRUE;
9359             break;
9360         }
9361
9362         if (!ignore) {
9363             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9364             buf1[0] = NULLCHAR;
9365             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9366                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9367
9368                 if (plyext != ' ' && plyext != '\t') {
9369                     time *= 100;
9370                 }
9371
9372                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9373                 if( cps->scoreIsAbsolute &&
9374                     ( gameMode == MachinePlaysBlack ||
9375                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9376                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9377                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9378                      !WhiteOnMove(currentMove)
9379                     ) )
9380                 {
9381                     curscore = -curscore;
9382                 }
9383
9384                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9385
9386                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9387                         char buf[MSG_SIZ];
9388                         FILE *f;
9389                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9390                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9391                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9392                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9393                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9394                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9395                                 fclose(f);
9396                         }
9397                         else
9398                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9399                           DisplayError(_("failed writing PV"), 0);
9400                 }
9401
9402                 tempStats.depth = plylev;
9403                 tempStats.nodes = nodes;
9404                 tempStats.time = time;
9405                 tempStats.score = curscore;
9406                 tempStats.got_only_move = 0;
9407
9408                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9409                         int ticklen;
9410
9411                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9412                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9413                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9414                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9415                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9416                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9417                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9418                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9419                 }
9420
9421                 /* Buffer overflow protection */
9422                 if (pv[0] != NULLCHAR) {
9423                     if (strlen(pv) >= sizeof(tempStats.movelist)
9424                         && appData.debugMode) {
9425                         fprintf(debugFP,
9426                                 "PV is too long; using the first %u bytes.\n",
9427                                 (unsigned) sizeof(tempStats.movelist) - 1);
9428                     }
9429
9430                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9431                 } else {
9432                     sprintf(tempStats.movelist, " no PV\n");
9433                 }
9434
9435                 if (tempStats.seen_stat) {
9436                     tempStats.ok_to_send = 1;
9437                 }
9438
9439                 if (strchr(tempStats.movelist, '(') != NULL) {
9440                     tempStats.line_is_book = 1;
9441                     tempStats.nr_moves = 0;
9442                     tempStats.moves_left = 0;
9443                 } else {
9444                     tempStats.line_is_book = 0;
9445                 }
9446
9447                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9448                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9449
9450                 SendProgramStatsToFrontend( cps, &tempStats );
9451
9452                 /*
9453                     [AS] Protect the thinkOutput buffer from overflow... this
9454                     is only useful if buf1 hasn't overflowed first!
9455                 */
9456                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9457                          plylev,
9458                          (gameMode == TwoMachinesPlay ?
9459                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9460                          ((double) curscore) / 100.0,
9461                          prefixHint ? lastHint : "",
9462                          prefixHint ? " " : "" );
9463
9464                 if( buf1[0] != NULLCHAR ) {
9465                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9466
9467                     if( strlen(pv) > max_len ) {
9468                         if( appData.debugMode) {
9469                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9470                         }
9471                         pv[max_len+1] = '\0';
9472                     }
9473
9474                     strcat( thinkOutput, pv);
9475                 }
9476
9477                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9478                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9479                     DisplayMove(currentMove - 1);
9480                 }
9481                 return;
9482
9483             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9484                 /* crafty (9.25+) says "(only move) <move>"
9485                  * if there is only 1 legal move
9486                  */
9487                 sscanf(p, "(only move) %s", buf1);
9488                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9489                 sprintf(programStats.movelist, "%s (only move)", buf1);
9490                 programStats.depth = 1;
9491                 programStats.nr_moves = 1;
9492                 programStats.moves_left = 1;
9493                 programStats.nodes = 1;
9494                 programStats.time = 1;
9495                 programStats.got_only_move = 1;
9496
9497                 /* Not really, but we also use this member to
9498                    mean "line isn't going to change" (Crafty
9499                    isn't searching, so stats won't change) */
9500                 programStats.line_is_book = 1;
9501
9502                 SendProgramStatsToFrontend( cps, &programStats );
9503
9504                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9505                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9506                     DisplayMove(currentMove - 1);
9507                 }
9508                 return;
9509             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9510                               &time, &nodes, &plylev, &mvleft,
9511                               &mvtot, mvname) >= 5) {
9512                 /* The stat01: line is from Crafty (9.29+) in response
9513                    to the "." command */
9514                 programStats.seen_stat = 1;
9515                 cps->maybeThinking = TRUE;
9516
9517                 if (programStats.got_only_move || !appData.periodicUpdates)
9518                   return;
9519
9520                 programStats.depth = plylev;
9521                 programStats.time = time;
9522                 programStats.nodes = nodes;
9523                 programStats.moves_left = mvleft;
9524                 programStats.nr_moves = mvtot;
9525                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9526                 programStats.ok_to_send = 1;
9527                 programStats.movelist[0] = '\0';
9528
9529                 SendProgramStatsToFrontend( cps, &programStats );
9530
9531                 return;
9532
9533             } else if (strncmp(message,"++",2) == 0) {
9534                 /* Crafty 9.29+ outputs this */
9535                 programStats.got_fail = 2;
9536                 return;
9537
9538             } else if (strncmp(message,"--",2) == 0) {
9539                 /* Crafty 9.29+ outputs this */
9540                 programStats.got_fail = 1;
9541                 return;
9542
9543             } else if (thinkOutput[0] != NULLCHAR &&
9544                        strncmp(message, "    ", 4) == 0) {
9545                 unsigned message_len;
9546
9547                 p = message;
9548                 while (*p && *p == ' ') p++;
9549
9550                 message_len = strlen( p );
9551
9552                 /* [AS] Avoid buffer overflow */
9553                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9554                     strcat(thinkOutput, " ");
9555                     strcat(thinkOutput, p);
9556                 }
9557
9558                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9559                     strcat(programStats.movelist, " ");
9560                     strcat(programStats.movelist, p);
9561                 }
9562
9563                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9564                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9565                     DisplayMove(currentMove - 1);
9566                 }
9567                 return;
9568             }
9569         }
9570         else {
9571             buf1[0] = NULLCHAR;
9572
9573             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9574                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9575             {
9576                 ChessProgramStats cpstats;
9577
9578                 if (plyext != ' ' && plyext != '\t') {
9579                     time *= 100;
9580                 }
9581
9582                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9583                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9584                     curscore = -curscore;
9585                 }
9586
9587                 cpstats.depth = plylev;
9588                 cpstats.nodes = nodes;
9589                 cpstats.time = time;
9590                 cpstats.score = curscore;
9591                 cpstats.got_only_move = 0;
9592                 cpstats.movelist[0] = '\0';
9593
9594                 if (buf1[0] != NULLCHAR) {
9595                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9596                 }
9597
9598                 cpstats.ok_to_send = 0;
9599                 cpstats.line_is_book = 0;
9600                 cpstats.nr_moves = 0;
9601                 cpstats.moves_left = 0;
9602
9603                 SendProgramStatsToFrontend( cps, &cpstats );
9604             }
9605         }
9606     }
9607 }
9608
9609
9610 /* Parse a game score from the character string "game", and
9611    record it as the history of the current game.  The game
9612    score is NOT assumed to start from the standard position.
9613    The display is not updated in any way.
9614    */
9615 void
9616 ParseGameHistory (char *game)
9617 {
9618     ChessMove moveType;
9619     int fromX, fromY, toX, toY, boardIndex;
9620     char promoChar;
9621     char *p, *q;
9622     char buf[MSG_SIZ];
9623
9624     if (appData.debugMode)
9625       fprintf(debugFP, "Parsing game history: %s\n", game);
9626
9627     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9628     gameInfo.site = StrSave(appData.icsHost);
9629     gameInfo.date = PGNDate();
9630     gameInfo.round = StrSave("-");
9631
9632     /* Parse out names of players */
9633     while (*game == ' ') game++;
9634     p = buf;
9635     while (*game != ' ') *p++ = *game++;
9636     *p = NULLCHAR;
9637     gameInfo.white = StrSave(buf);
9638     while (*game == ' ') game++;
9639     p = buf;
9640     while (*game != ' ' && *game != '\n') *p++ = *game++;
9641     *p = NULLCHAR;
9642     gameInfo.black = StrSave(buf);
9643
9644     /* Parse moves */
9645     boardIndex = blackPlaysFirst ? 1 : 0;
9646     yynewstr(game);
9647     for (;;) {
9648         yyboardindex = boardIndex;
9649         moveType = (ChessMove) Myylex();
9650         switch (moveType) {
9651           case IllegalMove:             /* maybe suicide chess, etc. */
9652   if (appData.debugMode) {
9653     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9654     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9655     setbuf(debugFP, NULL);
9656   }
9657           case WhitePromotion:
9658           case BlackPromotion:
9659           case WhiteNonPromotion:
9660           case BlackNonPromotion:
9661           case NormalMove:
9662           case FirstLeg:
9663           case WhiteCapturesEnPassant:
9664           case BlackCapturesEnPassant:
9665           case WhiteKingSideCastle:
9666           case WhiteQueenSideCastle:
9667           case BlackKingSideCastle:
9668           case BlackQueenSideCastle:
9669           case WhiteKingSideCastleWild:
9670           case WhiteQueenSideCastleWild:
9671           case BlackKingSideCastleWild:
9672           case BlackQueenSideCastleWild:
9673           /* PUSH Fabien */
9674           case WhiteHSideCastleFR:
9675           case WhiteASideCastleFR:
9676           case BlackHSideCastleFR:
9677           case BlackASideCastleFR:
9678           /* POP Fabien */
9679             fromX = currentMoveString[0] - AAA;
9680             fromY = currentMoveString[1] - ONE;
9681             toX = currentMoveString[2] - AAA;
9682             toY = currentMoveString[3] - ONE;
9683             promoChar = currentMoveString[4];
9684             break;
9685           case WhiteDrop:
9686           case BlackDrop:
9687             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9688             fromX = moveType == WhiteDrop ?
9689               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9690             (int) CharToPiece(ToLower(currentMoveString[0]));
9691             fromY = DROP_RANK;
9692             toX = currentMoveString[2] - AAA;
9693             toY = currentMoveString[3] - ONE;
9694             promoChar = NULLCHAR;
9695             break;
9696           case AmbiguousMove:
9697             /* bug? */
9698             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9699   if (appData.debugMode) {
9700     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9701     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9702     setbuf(debugFP, NULL);
9703   }
9704             DisplayError(buf, 0);
9705             return;
9706           case ImpossibleMove:
9707             /* bug? */
9708             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9709   if (appData.debugMode) {
9710     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9711     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9712     setbuf(debugFP, NULL);
9713   }
9714             DisplayError(buf, 0);
9715             return;
9716           case EndOfFile:
9717             if (boardIndex < backwardMostMove) {
9718                 /* Oops, gap.  How did that happen? */
9719                 DisplayError(_("Gap in move list"), 0);
9720                 return;
9721             }
9722             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9723             if (boardIndex > forwardMostMove) {
9724                 forwardMostMove = boardIndex;
9725             }
9726             return;
9727           case ElapsedTime:
9728             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9729                 strcat(parseList[boardIndex-1], " ");
9730                 strcat(parseList[boardIndex-1], yy_text);
9731             }
9732             continue;
9733           case Comment:
9734           case PGNTag:
9735           case NAG:
9736           default:
9737             /* ignore */
9738             continue;
9739           case WhiteWins:
9740           case BlackWins:
9741           case GameIsDrawn:
9742           case GameUnfinished:
9743             if (gameMode == IcsExamining) {
9744                 if (boardIndex < backwardMostMove) {
9745                     /* Oops, gap.  How did that happen? */
9746                     return;
9747                 }
9748                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9749                 return;
9750             }
9751             gameInfo.result = moveType;
9752             p = strchr(yy_text, '{');
9753             if (p == NULL) p = strchr(yy_text, '(');
9754             if (p == NULL) {
9755                 p = yy_text;
9756                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9757             } else {
9758                 q = strchr(p, *p == '{' ? '}' : ')');
9759                 if (q != NULL) *q = NULLCHAR;
9760                 p++;
9761             }
9762             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9763             gameInfo.resultDetails = StrSave(p);
9764             continue;
9765         }
9766         if (boardIndex >= forwardMostMove &&
9767             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9768             backwardMostMove = blackPlaysFirst ? 1 : 0;
9769             return;
9770         }
9771         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9772                                  fromY, fromX, toY, toX, promoChar,
9773                                  parseList[boardIndex]);
9774         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9775         /* currentMoveString is set as a side-effect of yylex */
9776         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9777         strcat(moveList[boardIndex], "\n");
9778         boardIndex++;
9779         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9780         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9781           case MT_NONE:
9782           case MT_STALEMATE:
9783           default:
9784             break;
9785           case MT_CHECK:
9786             if(!IS_SHOGI(gameInfo.variant))
9787                 strcat(parseList[boardIndex - 1], "+");
9788             break;
9789           case MT_CHECKMATE:
9790           case MT_STAINMATE:
9791             strcat(parseList[boardIndex - 1], "#");
9792             break;
9793         }
9794     }
9795 }
9796
9797
9798 /* Apply a move to the given board  */
9799 void
9800 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9801 {
9802   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9803   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9804
9805     /* [HGM] compute & store e.p. status and castling rights for new position */
9806     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9807
9808       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9809       oldEP = (signed char)board[EP_STATUS];
9810       board[EP_STATUS] = EP_NONE;
9811
9812   if (fromY == DROP_RANK) {
9813         /* must be first */
9814         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9815             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9816             return;
9817         }
9818         piece = board[toY][toX] = (ChessSquare) fromX;
9819   } else {
9820       ChessSquare victim;
9821       int i;
9822
9823       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9824            victim = board[killY][killX],
9825            board[killY][killX] = EmptySquare,
9826            board[EP_STATUS] = EP_CAPTURE;
9827
9828       if( board[toY][toX] != EmptySquare ) {
9829            board[EP_STATUS] = EP_CAPTURE;
9830            if( (fromX != toX || fromY != toY) && // not igui!
9831                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9832                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9833                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9834            }
9835       }
9836
9837       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9838            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9839                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9840       } else
9841       if( board[fromY][fromX] == WhitePawn ) {
9842            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9843                board[EP_STATUS] = EP_PAWN_MOVE;
9844            if( toY-fromY==2) {
9845                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9846                         gameInfo.variant != VariantBerolina || toX < fromX)
9847                       board[EP_STATUS] = toX | berolina;
9848                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9849                         gameInfo.variant != VariantBerolina || toX > fromX)
9850                       board[EP_STATUS] = toX;
9851            }
9852       } else
9853       if( board[fromY][fromX] == BlackPawn ) {
9854            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9855                board[EP_STATUS] = EP_PAWN_MOVE;
9856            if( toY-fromY== -2) {
9857                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9858                         gameInfo.variant != VariantBerolina || toX < fromX)
9859                       board[EP_STATUS] = toX | berolina;
9860                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9861                         gameInfo.variant != VariantBerolina || toX > fromX)
9862                       board[EP_STATUS] = toX;
9863            }
9864        }
9865
9866        for(i=0; i<nrCastlingRights; i++) {
9867            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9868               board[CASTLING][i] == toX   && castlingRank[i] == toY
9869              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9870        }
9871
9872        if(gameInfo.variant == VariantSChess) { // update virginity
9873            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9874            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9875            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9876            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9877        }
9878
9879      if (fromX == toX && fromY == toY) return;
9880
9881      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9882      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9883      if(gameInfo.variant == VariantKnightmate)
9884          king += (int) WhiteUnicorn - (int) WhiteKing;
9885
9886     /* Code added by Tord: */
9887     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9888     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9889         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9890       board[fromY][fromX] = EmptySquare;
9891       board[toY][toX] = EmptySquare;
9892       if((toX > fromX) != (piece == WhiteRook)) {
9893         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9894       } else {
9895         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9896       }
9897     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9898                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9899       board[fromY][fromX] = EmptySquare;
9900       board[toY][toX] = EmptySquare;
9901       if((toX > fromX) != (piece == BlackRook)) {
9902         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9903       } else {
9904         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9905       }
9906     /* End of code added by Tord */
9907
9908     } else if (board[fromY][fromX] == king
9909         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9910         && toY == fromY && toX > fromX+1) {
9911         board[fromY][fromX] = EmptySquare;
9912         board[toY][toX] = king;
9913         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9914         board[fromY][BOARD_RGHT-1] = EmptySquare;
9915     } else if (board[fromY][fromX] == king
9916         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9917                && toY == fromY && toX < fromX-1) {
9918         board[fromY][fromX] = EmptySquare;
9919         board[toY][toX] = king;
9920         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9921         board[fromY][BOARD_LEFT] = EmptySquare;
9922     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9923                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9924                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9925                ) {
9926         /* white pawn promotion */
9927         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9928         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9929             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9930         board[fromY][fromX] = EmptySquare;
9931     } else if ((fromY >= BOARD_HEIGHT>>1)
9932                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9933                && (toX != fromX)
9934                && gameInfo.variant != VariantXiangqi
9935                && gameInfo.variant != VariantBerolina
9936                && (board[fromY][fromX] == WhitePawn)
9937                && (board[toY][toX] == EmptySquare)) {
9938         board[fromY][fromX] = EmptySquare;
9939         board[toY][toX] = WhitePawn;
9940         captured = board[toY - 1][toX];
9941         board[toY - 1][toX] = EmptySquare;
9942     } else if ((fromY == BOARD_HEIGHT-4)
9943                && (toX == fromX)
9944                && gameInfo.variant == VariantBerolina
9945                && (board[fromY][fromX] == WhitePawn)
9946                && (board[toY][toX] == EmptySquare)) {
9947         board[fromY][fromX] = EmptySquare;
9948         board[toY][toX] = WhitePawn;
9949         if(oldEP & EP_BEROLIN_A) {
9950                 captured = board[fromY][fromX-1];
9951                 board[fromY][fromX-1] = EmptySquare;
9952         }else{  captured = board[fromY][fromX+1];
9953                 board[fromY][fromX+1] = EmptySquare;
9954         }
9955     } else if (board[fromY][fromX] == king
9956         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9957                && toY == fromY && toX > fromX+1) {
9958         board[fromY][fromX] = EmptySquare;
9959         board[toY][toX] = king;
9960         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9961         board[fromY][BOARD_RGHT-1] = EmptySquare;
9962     } else if (board[fromY][fromX] == king
9963         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9964                && toY == fromY && toX < fromX-1) {
9965         board[fromY][fromX] = EmptySquare;
9966         board[toY][toX] = king;
9967         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9968         board[fromY][BOARD_LEFT] = EmptySquare;
9969     } else if (fromY == 7 && fromX == 3
9970                && board[fromY][fromX] == BlackKing
9971                && toY == 7 && toX == 5) {
9972         board[fromY][fromX] = EmptySquare;
9973         board[toY][toX] = BlackKing;
9974         board[fromY][7] = EmptySquare;
9975         board[toY][4] = BlackRook;
9976     } else if (fromY == 7 && fromX == 3
9977                && board[fromY][fromX] == BlackKing
9978                && toY == 7 && toX == 1) {
9979         board[fromY][fromX] = EmptySquare;
9980         board[toY][toX] = BlackKing;
9981         board[fromY][0] = EmptySquare;
9982         board[toY][2] = BlackRook;
9983     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9984                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9985                && toY < promoRank && promoChar
9986                ) {
9987         /* black pawn promotion */
9988         board[toY][toX] = CharToPiece(ToLower(promoChar));
9989         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9990             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9991         board[fromY][fromX] = EmptySquare;
9992     } else if ((fromY < BOARD_HEIGHT>>1)
9993                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9994                && (toX != fromX)
9995                && gameInfo.variant != VariantXiangqi
9996                && gameInfo.variant != VariantBerolina
9997                && (board[fromY][fromX] == BlackPawn)
9998                && (board[toY][toX] == EmptySquare)) {
9999         board[fromY][fromX] = EmptySquare;
10000         board[toY][toX] = BlackPawn;
10001         captured = board[toY + 1][toX];
10002         board[toY + 1][toX] = EmptySquare;
10003     } else if ((fromY == 3)
10004                && (toX == fromX)
10005                && gameInfo.variant == VariantBerolina
10006                && (board[fromY][fromX] == BlackPawn)
10007                && (board[toY][toX] == EmptySquare)) {
10008         board[fromY][fromX] = EmptySquare;
10009         board[toY][toX] = BlackPawn;
10010         if(oldEP & EP_BEROLIN_A) {
10011                 captured = board[fromY][fromX-1];
10012                 board[fromY][fromX-1] = EmptySquare;
10013         }else{  captured = board[fromY][fromX+1];
10014                 board[fromY][fromX+1] = EmptySquare;
10015         }
10016     } else {
10017         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10018         board[fromY][fromX] = EmptySquare;
10019         board[toY][toX] = piece;
10020     }
10021   }
10022
10023     if (gameInfo.holdingsWidth != 0) {
10024
10025       /* !!A lot more code needs to be written to support holdings  */
10026       /* [HGM] OK, so I have written it. Holdings are stored in the */
10027       /* penultimate board files, so they are automaticlly stored   */
10028       /* in the game history.                                       */
10029       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10030                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10031         /* Delete from holdings, by decreasing count */
10032         /* and erasing image if necessary            */
10033         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10034         if(p < (int) BlackPawn) { /* white drop */
10035              p -= (int)WhitePawn;
10036                  p = PieceToNumber((ChessSquare)p);
10037              if(p >= gameInfo.holdingsSize) p = 0;
10038              if(--board[p][BOARD_WIDTH-2] <= 0)
10039                   board[p][BOARD_WIDTH-1] = EmptySquare;
10040              if((int)board[p][BOARD_WIDTH-2] < 0)
10041                         board[p][BOARD_WIDTH-2] = 0;
10042         } else {                  /* black drop */
10043              p -= (int)BlackPawn;
10044                  p = PieceToNumber((ChessSquare)p);
10045              if(p >= gameInfo.holdingsSize) p = 0;
10046              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10047                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10048              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10049                         board[BOARD_HEIGHT-1-p][1] = 0;
10050         }
10051       }
10052       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10053           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10054         /* [HGM] holdings: Add to holdings, if holdings exist */
10055         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10056                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10057                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10058         }
10059         p = (int) captured;
10060         if (p >= (int) BlackPawn) {
10061           p -= (int)BlackPawn;
10062           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10063                   /* in Shogi restore piece to its original  first */
10064                   captured = (ChessSquare) (DEMOTED captured);
10065                   p = DEMOTED p;
10066           }
10067           p = PieceToNumber((ChessSquare)p);
10068           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10069           board[p][BOARD_WIDTH-2]++;
10070           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10071         } else {
10072           p -= (int)WhitePawn;
10073           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10074                   captured = (ChessSquare) (DEMOTED captured);
10075                   p = DEMOTED p;
10076           }
10077           p = PieceToNumber((ChessSquare)p);
10078           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10079           board[BOARD_HEIGHT-1-p][1]++;
10080           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10081         }
10082       }
10083     } else if (gameInfo.variant == VariantAtomic) {
10084       if (captured != EmptySquare) {
10085         int y, x;
10086         for (y = toY-1; y <= toY+1; y++) {
10087           for (x = toX-1; x <= toX+1; x++) {
10088             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10089                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10090               board[y][x] = EmptySquare;
10091             }
10092           }
10093         }
10094         board[toY][toX] = EmptySquare;
10095       }
10096     }
10097
10098     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10099         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10100     } else
10101     if(promoChar == '+') {
10102         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10103         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10104         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10105           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10106     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10107         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10108         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10109            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10110         board[toY][toX] = newPiece;
10111     }
10112     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10113                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10114         // [HGM] superchess: take promotion piece out of holdings
10115         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10116         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10117             if(!--board[k][BOARD_WIDTH-2])
10118                 board[k][BOARD_WIDTH-1] = EmptySquare;
10119         } else {
10120             if(!--board[BOARD_HEIGHT-1-k][1])
10121                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10122         }
10123     }
10124 }
10125
10126 /* Updates forwardMostMove */
10127 void
10128 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10129 {
10130     int x = toX, y = toY;
10131     char *s = parseList[forwardMostMove];
10132     ChessSquare p = boards[forwardMostMove][toY][toX];
10133 //    forwardMostMove++; // [HGM] bare: moved downstream
10134
10135     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10136     (void) CoordsToAlgebraic(boards[forwardMostMove],
10137                              PosFlags(forwardMostMove),
10138                              fromY, fromX, y, x, promoChar,
10139                              s);
10140     if(killX >= 0 && killY >= 0)
10141         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10142
10143     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10144         int timeLeft; static int lastLoadFlag=0; int king, piece;
10145         piece = boards[forwardMostMove][fromY][fromX];
10146         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10147         if(gameInfo.variant == VariantKnightmate)
10148             king += (int) WhiteUnicorn - (int) WhiteKing;
10149         if(forwardMostMove == 0) {
10150             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10151                 fprintf(serverMoves, "%s;", UserName());
10152             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10153                 fprintf(serverMoves, "%s;", second.tidy);
10154             fprintf(serverMoves, "%s;", first.tidy);
10155             if(gameMode == MachinePlaysWhite)
10156                 fprintf(serverMoves, "%s;", UserName());
10157             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10158                 fprintf(serverMoves, "%s;", second.tidy);
10159         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10160         lastLoadFlag = loadFlag;
10161         // print base move
10162         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10163         // print castling suffix
10164         if( toY == fromY && piece == king ) {
10165             if(toX-fromX > 1)
10166                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10167             if(fromX-toX >1)
10168                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10169         }
10170         // e.p. suffix
10171         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10172              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10173              boards[forwardMostMove][toY][toX] == EmptySquare
10174              && fromX != toX && fromY != toY)
10175                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10176         // promotion suffix
10177         if(promoChar != NULLCHAR) {
10178             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10179                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10180                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10181             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10182         }
10183         if(!loadFlag) {
10184                 char buf[MOVE_LEN*2], *p; int len;
10185             fprintf(serverMoves, "/%d/%d",
10186                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10187             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10188             else                      timeLeft = blackTimeRemaining/1000;
10189             fprintf(serverMoves, "/%d", timeLeft);
10190                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10191                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10192                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10193                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10194             fprintf(serverMoves, "/%s", buf);
10195         }
10196         fflush(serverMoves);
10197     }
10198
10199     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10200         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10201       return;
10202     }
10203     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10204     if (commentList[forwardMostMove+1] != NULL) {
10205         free(commentList[forwardMostMove+1]);
10206         commentList[forwardMostMove+1] = NULL;
10207     }
10208     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10209     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10210     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10211     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10212     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10213     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10214     adjustedClock = FALSE;
10215     gameInfo.result = GameUnfinished;
10216     if (gameInfo.resultDetails != NULL) {
10217         free(gameInfo.resultDetails);
10218         gameInfo.resultDetails = NULL;
10219     }
10220     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10221                               moveList[forwardMostMove - 1]);
10222     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10223       case MT_NONE:
10224       case MT_STALEMATE:
10225       default:
10226         break;
10227       case MT_CHECK:
10228         if(!IS_SHOGI(gameInfo.variant))
10229             strcat(parseList[forwardMostMove - 1], "+");
10230         break;
10231       case MT_CHECKMATE:
10232       case MT_STAINMATE:
10233         strcat(parseList[forwardMostMove - 1], "#");
10234         break;
10235     }
10236 }
10237
10238 /* Updates currentMove if not pausing */
10239 void
10240 ShowMove (int fromX, int fromY, int toX, int toY)
10241 {
10242     int instant = (gameMode == PlayFromGameFile) ?
10243         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10244     if(appData.noGUI) return;
10245     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10246         if (!instant) {
10247             if (forwardMostMove == currentMove + 1) {
10248                 AnimateMove(boards[forwardMostMove - 1],
10249                             fromX, fromY, toX, toY);
10250             }
10251         }
10252         currentMove = forwardMostMove;
10253     }
10254
10255     killX = killY = -1; // [HGM] lion: used up
10256
10257     if (instant) return;
10258
10259     DisplayMove(currentMove - 1);
10260     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10261             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10262                 SetHighlights(fromX, fromY, toX, toY);
10263             }
10264     }
10265     DrawPosition(FALSE, boards[currentMove]);
10266     DisplayBothClocks();
10267     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10268 }
10269
10270 void
10271 SendEgtPath (ChessProgramState *cps)
10272 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10273         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10274
10275         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10276
10277         while(*p) {
10278             char c, *q = name+1, *r, *s;
10279
10280             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10281             while(*p && *p != ',') *q++ = *p++;
10282             *q++ = ':'; *q = 0;
10283             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10284                 strcmp(name, ",nalimov:") == 0 ) {
10285                 // take nalimov path from the menu-changeable option first, if it is defined
10286               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10287                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10288             } else
10289             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10290                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10291                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10292                 s = r = StrStr(s, ":") + 1; // beginning of path info
10293                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10294                 c = *r; *r = 0;             // temporarily null-terminate path info
10295                     *--q = 0;               // strip of trailig ':' from name
10296                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10297                 *r = c;
10298                 SendToProgram(buf,cps);     // send egtbpath command for this format
10299             }
10300             if(*p == ',') p++; // read away comma to position for next format name
10301         }
10302 }
10303
10304 static int
10305 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10306 {
10307       int width = 8, height = 8, holdings = 0;             // most common sizes
10308       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10309       // correct the deviations default for each variant
10310       if( v == VariantXiangqi ) width = 9,  height = 10;
10311       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10312       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10313       if( v == VariantCapablanca || v == VariantCapaRandom ||
10314           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10315                                 width = 10;
10316       if( v == VariantCourier ) width = 12;
10317       if( v == VariantSuper )                            holdings = 8;
10318       if( v == VariantGreat )   width = 10,              holdings = 8;
10319       if( v == VariantSChess )                           holdings = 7;
10320       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10321       if( v == VariantChuChess) width = 10, height = 10;
10322       if( v == VariantChu )     width = 12, height = 12;
10323       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10324              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10325              holdingsSize >= 0 && holdingsSize != holdings;
10326 }
10327
10328 char variantError[MSG_SIZ];
10329
10330 char *
10331 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10332 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10333       char *p, *variant = VariantName(v);
10334       static char b[MSG_SIZ];
10335       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10336            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10337                                                holdingsSize, variant); // cook up sized variant name
10338            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10339            if(StrStr(list, b) == NULL) {
10340                // specific sized variant not known, check if general sizing allowed
10341                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10342                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10343                             boardWidth, boardHeight, holdingsSize, engine);
10344                    return NULL;
10345                }
10346                /* [HGM] here we really should compare with the maximum supported board size */
10347            }
10348       } else snprintf(b, MSG_SIZ,"%s", variant);
10349       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10350       p = StrStr(list, b);
10351       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10352       if(p == NULL) {
10353           // occurs not at all in list, or only as sub-string
10354           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10355           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10356               int l = strlen(variantError);
10357               char *q;
10358               while(p != list && p[-1] != ',') p--;
10359               q = strchr(p, ',');
10360               if(q) *q = NULLCHAR;
10361               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10362               if(q) *q= ',';
10363           }
10364           return NULL;
10365       }
10366       return b;
10367 }
10368
10369 void
10370 InitChessProgram (ChessProgramState *cps, int setup)
10371 /* setup needed to setup FRC opening position */
10372 {
10373     char buf[MSG_SIZ], *b;
10374     if (appData.noChessProgram) return;
10375     hintRequested = FALSE;
10376     bookRequested = FALSE;
10377
10378     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10379     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10380     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10381     if(cps->memSize) { /* [HGM] memory */
10382       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10383         SendToProgram(buf, cps);
10384     }
10385     SendEgtPath(cps); /* [HGM] EGT */
10386     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10387       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10388         SendToProgram(buf, cps);
10389     }
10390
10391     SendToProgram(cps->initString, cps);
10392     if (gameInfo.variant != VariantNormal &&
10393         gameInfo.variant != VariantLoadable
10394         /* [HGM] also send variant if board size non-standard */
10395         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10396
10397       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10398                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10399       if (b == NULL) {
10400         DisplayFatalError(variantError, 0, 1);
10401         return;
10402       }
10403
10404       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10405       SendToProgram(buf, cps);
10406     }
10407     currentlyInitializedVariant = gameInfo.variant;
10408
10409     /* [HGM] send opening position in FRC to first engine */
10410     if(setup) {
10411           SendToProgram("force\n", cps);
10412           SendBoard(cps, 0);
10413           /* engine is now in force mode! Set flag to wake it up after first move. */
10414           setboardSpoiledMachineBlack = 1;
10415     }
10416
10417     if (cps->sendICS) {
10418       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10419       SendToProgram(buf, cps);
10420     }
10421     cps->maybeThinking = FALSE;
10422     cps->offeredDraw = 0;
10423     if (!appData.icsActive) {
10424         SendTimeControl(cps, movesPerSession, timeControl,
10425                         timeIncrement, appData.searchDepth,
10426                         searchTime);
10427     }
10428     if (appData.showThinking
10429         // [HGM] thinking: four options require thinking output to be sent
10430         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10431                                 ) {
10432         SendToProgram("post\n", cps);
10433     }
10434     SendToProgram("hard\n", cps);
10435     if (!appData.ponderNextMove) {
10436         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10437            it without being sure what state we are in first.  "hard"
10438            is not a toggle, so that one is OK.
10439          */
10440         SendToProgram("easy\n", cps);
10441     }
10442     if (cps->usePing) {
10443       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10444       SendToProgram(buf, cps);
10445     }
10446     cps->initDone = TRUE;
10447     ClearEngineOutputPane(cps == &second);
10448 }
10449
10450
10451 void
10452 ResendOptions (ChessProgramState *cps)
10453 { // send the stored value of the options
10454   int i;
10455   char buf[MSG_SIZ];
10456   Option *opt = cps->option;
10457   for(i=0; i<cps->nrOptions; i++, opt++) {
10458       switch(opt->type) {
10459         case Spin:
10460         case Slider:
10461         case CheckBox:
10462             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10463           break;
10464         case ComboBox:
10465           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10466           break;
10467         default:
10468             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10469           break;
10470         case Button:
10471         case SaveButton:
10472           continue;
10473       }
10474       SendToProgram(buf, cps);
10475   }
10476 }
10477
10478 void
10479 StartChessProgram (ChessProgramState *cps)
10480 {
10481     char buf[MSG_SIZ];
10482     int err;
10483
10484     if (appData.noChessProgram) return;
10485     cps->initDone = FALSE;
10486
10487     if (strcmp(cps->host, "localhost") == 0) {
10488         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10489     } else if (*appData.remoteShell == NULLCHAR) {
10490         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10491     } else {
10492         if (*appData.remoteUser == NULLCHAR) {
10493           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10494                     cps->program);
10495         } else {
10496           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10497                     cps->host, appData.remoteUser, cps->program);
10498         }
10499         err = StartChildProcess(buf, "", &cps->pr);
10500     }
10501
10502     if (err != 0) {
10503       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10504         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10505         if(cps != &first) return;
10506         appData.noChessProgram = TRUE;
10507         ThawUI();
10508         SetNCPMode();
10509 //      DisplayFatalError(buf, err, 1);
10510 //      cps->pr = NoProc;
10511 //      cps->isr = NULL;
10512         return;
10513     }
10514
10515     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10516     if (cps->protocolVersion > 1) {
10517       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10518       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10519         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10520         cps->comboCnt = 0;  //                and values of combo boxes
10521       }
10522       SendToProgram(buf, cps);
10523       if(cps->reload) ResendOptions(cps);
10524     } else {
10525       SendToProgram("xboard\n", cps);
10526     }
10527 }
10528
10529 void
10530 TwoMachinesEventIfReady P((void))
10531 {
10532   static int curMess = 0;
10533   if (first.lastPing != first.lastPong) {
10534     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10535     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10536     return;
10537   }
10538   if (second.lastPing != second.lastPong) {
10539     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10540     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10541     return;
10542   }
10543   DisplayMessage("", ""); curMess = 0;
10544   TwoMachinesEvent();
10545 }
10546
10547 char *
10548 MakeName (char *template)
10549 {
10550     time_t clock;
10551     struct tm *tm;
10552     static char buf[MSG_SIZ];
10553     char *p = buf;
10554     int i;
10555
10556     clock = time((time_t *)NULL);
10557     tm = localtime(&clock);
10558
10559     while(*p++ = *template++) if(p[-1] == '%') {
10560         switch(*template++) {
10561           case 0:   *p = 0; return buf;
10562           case 'Y': i = tm->tm_year+1900; break;
10563           case 'y': i = tm->tm_year-100; break;
10564           case 'M': i = tm->tm_mon+1; break;
10565           case 'd': i = tm->tm_mday; break;
10566           case 'h': i = tm->tm_hour; break;
10567           case 'm': i = tm->tm_min; break;
10568           case 's': i = tm->tm_sec; break;
10569           default:  i = 0;
10570         }
10571         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10572     }
10573     return buf;
10574 }
10575
10576 int
10577 CountPlayers (char *p)
10578 {
10579     int n = 0;
10580     while(p = strchr(p, '\n')) p++, n++; // count participants
10581     return n;
10582 }
10583
10584 FILE *
10585 WriteTourneyFile (char *results, FILE *f)
10586 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10587     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10588     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10589         // create a file with tournament description
10590         fprintf(f, "-participants {%s}\n", appData.participants);
10591         fprintf(f, "-seedBase %d\n", appData.seedBase);
10592         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10593         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10594         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10595         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10596         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10597         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10598         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10599         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10600         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10601         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10602         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10603         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10604         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10605         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10606         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10607         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10608         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10609         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10610         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10611         fprintf(f, "-smpCores %d\n", appData.smpCores);
10612         if(searchTime > 0)
10613                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10614         else {
10615                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10616                 fprintf(f, "-tc %s\n", appData.timeControl);
10617                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10618         }
10619         fprintf(f, "-results \"%s\"\n", results);
10620     }
10621     return f;
10622 }
10623
10624 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10625
10626 void
10627 Substitute (char *participants, int expunge)
10628 {
10629     int i, changed, changes=0, nPlayers=0;
10630     char *p, *q, *r, buf[MSG_SIZ];
10631     if(participants == NULL) return;
10632     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10633     r = p = participants; q = appData.participants;
10634     while(*p && *p == *q) {
10635         if(*p == '\n') r = p+1, nPlayers++;
10636         p++; q++;
10637     }
10638     if(*p) { // difference
10639         while(*p && *p++ != '\n');
10640         while(*q && *q++ != '\n');
10641       changed = nPlayers;
10642         changes = 1 + (strcmp(p, q) != 0);
10643     }
10644     if(changes == 1) { // a single engine mnemonic was changed
10645         q = r; while(*q) nPlayers += (*q++ == '\n');
10646         p = buf; while(*r && (*p = *r++) != '\n') p++;
10647         *p = NULLCHAR;
10648         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10649         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10650         if(mnemonic[i]) { // The substitute is valid
10651             FILE *f;
10652             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10653                 flock(fileno(f), LOCK_EX);
10654                 ParseArgsFromFile(f);
10655                 fseek(f, 0, SEEK_SET);
10656                 FREE(appData.participants); appData.participants = participants;
10657                 if(expunge) { // erase results of replaced engine
10658                     int len = strlen(appData.results), w, b, dummy;
10659                     for(i=0; i<len; i++) {
10660                         Pairing(i, nPlayers, &w, &b, &dummy);
10661                         if((w == changed || b == changed) && appData.results[i] == '*') {
10662                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10663                             fclose(f);
10664                             return;
10665                         }
10666                     }
10667                     for(i=0; i<len; i++) {
10668                         Pairing(i, nPlayers, &w, &b, &dummy);
10669                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10670                     }
10671                 }
10672                 WriteTourneyFile(appData.results, f);
10673                 fclose(f); // release lock
10674                 return;
10675             }
10676         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10677     }
10678     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10679     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10680     free(participants);
10681     return;
10682 }
10683
10684 int
10685 CheckPlayers (char *participants)
10686 {
10687         int i;
10688         char buf[MSG_SIZ], *p;
10689         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10690         while(p = strchr(participants, '\n')) {
10691             *p = NULLCHAR;
10692             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10693             if(!mnemonic[i]) {
10694                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10695                 *p = '\n';
10696                 DisplayError(buf, 0);
10697                 return 1;
10698             }
10699             *p = '\n';
10700             participants = p + 1;
10701         }
10702         return 0;
10703 }
10704
10705 int
10706 CreateTourney (char *name)
10707 {
10708         FILE *f;
10709         if(matchMode && strcmp(name, appData.tourneyFile)) {
10710              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10711         }
10712         if(name[0] == NULLCHAR) {
10713             if(appData.participants[0])
10714                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10715             return 0;
10716         }
10717         f = fopen(name, "r");
10718         if(f) { // file exists
10719             ASSIGN(appData.tourneyFile, name);
10720             ParseArgsFromFile(f); // parse it
10721         } else {
10722             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10723             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10724                 DisplayError(_("Not enough participants"), 0);
10725                 return 0;
10726             }
10727             if(CheckPlayers(appData.participants)) return 0;
10728             ASSIGN(appData.tourneyFile, name);
10729             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10730             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10731         }
10732         fclose(f);
10733         appData.noChessProgram = FALSE;
10734         appData.clockMode = TRUE;
10735         SetGNUMode();
10736         return 1;
10737 }
10738
10739 int
10740 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10741 {
10742     char buf[MSG_SIZ], *p, *q;
10743     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10744     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10745     skip = !all && group[0]; // if group requested, we start in skip mode
10746     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10747         p = names; q = buf; header = 0;
10748         while(*p && *p != '\n') *q++ = *p++;
10749         *q = 0;
10750         if(*p == '\n') p++;
10751         if(buf[0] == '#') {
10752             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10753             depth++; // we must be entering a new group
10754             if(all) continue; // suppress printing group headers when complete list requested
10755             header = 1;
10756             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10757         }
10758         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10759         if(engineList[i]) free(engineList[i]);
10760         engineList[i] = strdup(buf);
10761         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10762         if(engineMnemonic[i]) free(engineMnemonic[i]);
10763         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10764             strcat(buf, " (");
10765             sscanf(q + 8, "%s", buf + strlen(buf));
10766             strcat(buf, ")");
10767         }
10768         engineMnemonic[i] = strdup(buf);
10769         i++;
10770     }
10771     engineList[i] = engineMnemonic[i] = NULL;
10772     return i;
10773 }
10774
10775 // following implemented as macro to avoid type limitations
10776 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10777
10778 void
10779 SwapEngines (int n)
10780 {   // swap settings for first engine and other engine (so far only some selected options)
10781     int h;
10782     char *p;
10783     if(n == 0) return;
10784     SWAP(directory, p)
10785     SWAP(chessProgram, p)
10786     SWAP(isUCI, h)
10787     SWAP(hasOwnBookUCI, h)
10788     SWAP(protocolVersion, h)
10789     SWAP(reuse, h)
10790     SWAP(scoreIsAbsolute, h)
10791     SWAP(timeOdds, h)
10792     SWAP(logo, p)
10793     SWAP(pgnName, p)
10794     SWAP(pvSAN, h)
10795     SWAP(engOptions, p)
10796     SWAP(engInitString, p)
10797     SWAP(computerString, p)
10798     SWAP(features, p)
10799     SWAP(fenOverride, p)
10800     SWAP(NPS, h)
10801     SWAP(accumulateTC, h)
10802     SWAP(drawDepth, h)
10803     SWAP(host, p)
10804 }
10805
10806 int
10807 GetEngineLine (char *s, int n)
10808 {
10809     int i;
10810     char buf[MSG_SIZ];
10811     extern char *icsNames;
10812     if(!s || !*s) return 0;
10813     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10814     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10815     if(!mnemonic[i]) return 0;
10816     if(n == 11) return 1; // just testing if there was a match
10817     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10818     if(n == 1) SwapEngines(n);
10819     ParseArgsFromString(buf);
10820     if(n == 1) SwapEngines(n);
10821     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10822         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10823         ParseArgsFromString(buf);
10824     }
10825     return 1;
10826 }
10827
10828 int
10829 SetPlayer (int player, char *p)
10830 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10831     int i;
10832     char buf[MSG_SIZ], *engineName;
10833     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10834     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10835     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10836     if(mnemonic[i]) {
10837         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10838         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10839         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10840         ParseArgsFromString(buf);
10841     } else { // no engine with this nickname is installed!
10842         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10843         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10844         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10845         ModeHighlight();
10846         DisplayError(buf, 0);
10847         return 0;
10848     }
10849     free(engineName);
10850     return i;
10851 }
10852
10853 char *recentEngines;
10854
10855 void
10856 RecentEngineEvent (int nr)
10857 {
10858     int n;
10859 //    SwapEngines(1); // bump first to second
10860 //    ReplaceEngine(&second, 1); // and load it there
10861     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10862     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10863     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10864         ReplaceEngine(&first, 0);
10865         FloatToFront(&appData.recentEngineList, command[n]);
10866     }
10867 }
10868
10869 int
10870 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10871 {   // determine players from game number
10872     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10873
10874     if(appData.tourneyType == 0) {
10875         roundsPerCycle = (nPlayers - 1) | 1;
10876         pairingsPerRound = nPlayers / 2;
10877     } else if(appData.tourneyType > 0) {
10878         roundsPerCycle = nPlayers - appData.tourneyType;
10879         pairingsPerRound = appData.tourneyType;
10880     }
10881     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10882     gamesPerCycle = gamesPerRound * roundsPerCycle;
10883     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10884     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10885     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10886     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10887     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10888     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10889
10890     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10891     if(appData.roundSync) *syncInterval = gamesPerRound;
10892
10893     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10894
10895     if(appData.tourneyType == 0) {
10896         if(curPairing == (nPlayers-1)/2 ) {
10897             *whitePlayer = curRound;
10898             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10899         } else {
10900             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10901             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10902             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10903             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10904         }
10905     } else if(appData.tourneyType > 1) {
10906         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10907         *whitePlayer = curRound + appData.tourneyType;
10908     } else if(appData.tourneyType > 0) {
10909         *whitePlayer = curPairing;
10910         *blackPlayer = curRound + appData.tourneyType;
10911     }
10912
10913     // take care of white/black alternation per round.
10914     // For cycles and games this is already taken care of by default, derived from matchGame!
10915     return curRound & 1;
10916 }
10917
10918 int
10919 NextTourneyGame (int nr, int *swapColors)
10920 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10921     char *p, *q;
10922     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10923     FILE *tf;
10924     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10925     tf = fopen(appData.tourneyFile, "r");
10926     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10927     ParseArgsFromFile(tf); fclose(tf);
10928     InitTimeControls(); // TC might be altered from tourney file
10929
10930     nPlayers = CountPlayers(appData.participants); // count participants
10931     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10932     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10933
10934     if(syncInterval) {
10935         p = q = appData.results;
10936         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10937         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10938             DisplayMessage(_("Waiting for other game(s)"),"");
10939             waitingForGame = TRUE;
10940             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10941             return 0;
10942         }
10943         waitingForGame = FALSE;
10944     }
10945
10946     if(appData.tourneyType < 0) {
10947         if(nr>=0 && !pairingReceived) {
10948             char buf[1<<16];
10949             if(pairing.pr == NoProc) {
10950                 if(!appData.pairingEngine[0]) {
10951                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10952                     return 0;
10953                 }
10954                 StartChessProgram(&pairing); // starts the pairing engine
10955             }
10956             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10957             SendToProgram(buf, &pairing);
10958             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10959             SendToProgram(buf, &pairing);
10960             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10961         }
10962         pairingReceived = 0;                              // ... so we continue here
10963         *swapColors = 0;
10964         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10965         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10966         matchGame = 1; roundNr = nr / syncInterval + 1;
10967     }
10968
10969     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10970
10971     // redefine engines, engine dir, etc.
10972     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10973     if(first.pr == NoProc) {
10974       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10975       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10976     }
10977     if(second.pr == NoProc) {
10978       SwapEngines(1);
10979       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10980       SwapEngines(1);         // and make that valid for second engine by swapping
10981       InitEngine(&second, 1);
10982     }
10983     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10984     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10985     return OK;
10986 }
10987
10988 void
10989 NextMatchGame ()
10990 {   // performs game initialization that does not invoke engines, and then tries to start the game
10991     int res, firstWhite, swapColors = 0;
10992     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10993     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
10994         char buf[MSG_SIZ];
10995         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10996         if(strcmp(buf, currentDebugFile)) { // name has changed
10997             FILE *f = fopen(buf, "w");
10998             if(f) { // if opening the new file failed, just keep using the old one
10999                 ASSIGN(currentDebugFile, buf);
11000                 fclose(debugFP);
11001                 debugFP = f;
11002             }
11003             if(appData.serverFileName) {
11004                 if(serverFP) fclose(serverFP);
11005                 serverFP = fopen(appData.serverFileName, "w");
11006                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11007                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11008             }
11009         }
11010     }
11011     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11012     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11013     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11014     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11015     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11016     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11017     Reset(FALSE, first.pr != NoProc);
11018     res = LoadGameOrPosition(matchGame); // setup game
11019     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11020     if(!res) return; // abort when bad game/pos file
11021     TwoMachinesEvent();
11022 }
11023
11024 void
11025 UserAdjudicationEvent (int result)
11026 {
11027     ChessMove gameResult = GameIsDrawn;
11028
11029     if( result > 0 ) {
11030         gameResult = WhiteWins;
11031     }
11032     else if( result < 0 ) {
11033         gameResult = BlackWins;
11034     }
11035
11036     if( gameMode == TwoMachinesPlay ) {
11037         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11038     }
11039 }
11040
11041
11042 // [HGM] save: calculate checksum of game to make games easily identifiable
11043 int
11044 StringCheckSum (char *s)
11045 {
11046         int i = 0;
11047         if(s==NULL) return 0;
11048         while(*s) i = i*259 + *s++;
11049         return i;
11050 }
11051
11052 int
11053 GameCheckSum ()
11054 {
11055         int i, sum=0;
11056         for(i=backwardMostMove; i<forwardMostMove; i++) {
11057                 sum += pvInfoList[i].depth;
11058                 sum += StringCheckSum(parseList[i]);
11059                 sum += StringCheckSum(commentList[i]);
11060                 sum *= 261;
11061         }
11062         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11063         return sum + StringCheckSum(commentList[i]);
11064 } // end of save patch
11065
11066 void
11067 GameEnds (ChessMove result, char *resultDetails, int whosays)
11068 {
11069     GameMode nextGameMode;
11070     int isIcsGame;
11071     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11072
11073     if(endingGame) return; /* [HGM] crash: forbid recursion */
11074     endingGame = 1;
11075     if(twoBoards) { // [HGM] dual: switch back to one board
11076         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11077         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11078     }
11079     if (appData.debugMode) {
11080       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11081               result, resultDetails ? resultDetails : "(null)", whosays);
11082     }
11083
11084     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11085
11086     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11087
11088     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11089         /* If we are playing on ICS, the server decides when the
11090            game is over, but the engine can offer to draw, claim
11091            a draw, or resign.
11092          */
11093 #if ZIPPY
11094         if (appData.zippyPlay && first.initDone) {
11095             if (result == GameIsDrawn) {
11096                 /* In case draw still needs to be claimed */
11097                 SendToICS(ics_prefix);
11098                 SendToICS("draw\n");
11099             } else if (StrCaseStr(resultDetails, "resign")) {
11100                 SendToICS(ics_prefix);
11101                 SendToICS("resign\n");
11102             }
11103         }
11104 #endif
11105         endingGame = 0; /* [HGM] crash */
11106         return;
11107     }
11108
11109     /* If we're loading the game from a file, stop */
11110     if (whosays == GE_FILE) {
11111       (void) StopLoadGameTimer();
11112       gameFileFP = NULL;
11113     }
11114
11115     /* Cancel draw offers */
11116     first.offeredDraw = second.offeredDraw = 0;
11117
11118     /* If this is an ICS game, only ICS can really say it's done;
11119        if not, anyone can. */
11120     isIcsGame = (gameMode == IcsPlayingWhite ||
11121                  gameMode == IcsPlayingBlack ||
11122                  gameMode == IcsObserving    ||
11123                  gameMode == IcsExamining);
11124
11125     if (!isIcsGame || whosays == GE_ICS) {
11126         /* OK -- not an ICS game, or ICS said it was done */
11127         StopClocks();
11128         if (!isIcsGame && !appData.noChessProgram)
11129           SetUserThinkingEnables();
11130
11131         /* [HGM] if a machine claims the game end we verify this claim */
11132         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11133             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11134                 char claimer;
11135                 ChessMove trueResult = (ChessMove) -1;
11136
11137                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11138                                             first.twoMachinesColor[0] :
11139                                             second.twoMachinesColor[0] ;
11140
11141                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11142                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11143                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11144                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11145                 } else
11146                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11147                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11148                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11149                 } else
11150                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11151                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11152                 }
11153
11154                 // now verify win claims, but not in drop games, as we don't understand those yet
11155                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11156                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11157                     (result == WhiteWins && claimer == 'w' ||
11158                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11159                       if (appData.debugMode) {
11160                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11161                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11162                       }
11163                       if(result != trueResult) {
11164                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11165                               result = claimer == 'w' ? BlackWins : WhiteWins;
11166                               resultDetails = buf;
11167                       }
11168                 } else
11169                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11170                     && (forwardMostMove <= backwardMostMove ||
11171                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11172                         (claimer=='b')==(forwardMostMove&1))
11173                                                                                   ) {
11174                       /* [HGM] verify: draws that were not flagged are false claims */
11175                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11176                       result = claimer == 'w' ? BlackWins : WhiteWins;
11177                       resultDetails = buf;
11178                 }
11179                 /* (Claiming a loss is accepted no questions asked!) */
11180             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11181                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11182                 result = GameUnfinished;
11183                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11184             }
11185             /* [HGM] bare: don't allow bare King to win */
11186             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11187                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11188                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11189                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11190                && result != GameIsDrawn)
11191             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11192                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11193                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11194                         if(p >= 0 && p <= (int)WhiteKing) k++;
11195                 }
11196                 if (appData.debugMode) {
11197                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11198                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11199                 }
11200                 if(k <= 1) {
11201                         result = GameIsDrawn;
11202                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11203                         resultDetails = buf;
11204                 }
11205             }
11206         }
11207
11208
11209         if(serverMoves != NULL && !loadFlag) { char c = '=';
11210             if(result==WhiteWins) c = '+';
11211             if(result==BlackWins) c = '-';
11212             if(resultDetails != NULL)
11213                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11214         }
11215         if (resultDetails != NULL) {
11216             gameInfo.result = result;
11217             gameInfo.resultDetails = StrSave(resultDetails);
11218
11219             /* display last move only if game was not loaded from file */
11220             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11221                 DisplayMove(currentMove - 1);
11222
11223             if (forwardMostMove != 0) {
11224                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11225                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11226                                                                 ) {
11227                     if (*appData.saveGameFile != NULLCHAR) {
11228                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11229                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11230                         else
11231                         SaveGameToFile(appData.saveGameFile, TRUE);
11232                     } else if (appData.autoSaveGames) {
11233                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11234                     }
11235                     if (*appData.savePositionFile != NULLCHAR) {
11236                         SavePositionToFile(appData.savePositionFile);
11237                     }
11238                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11239                 }
11240             }
11241
11242             /* Tell program how game ended in case it is learning */
11243             /* [HGM] Moved this to after saving the PGN, just in case */
11244             /* engine died and we got here through time loss. In that */
11245             /* case we will get a fatal error writing the pipe, which */
11246             /* would otherwise lose us the PGN.                       */
11247             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11248             /* output during GameEnds should never be fatal anymore   */
11249             if (gameMode == MachinePlaysWhite ||
11250                 gameMode == MachinePlaysBlack ||
11251                 gameMode == TwoMachinesPlay ||
11252                 gameMode == IcsPlayingWhite ||
11253                 gameMode == IcsPlayingBlack ||
11254                 gameMode == BeginningOfGame) {
11255                 char buf[MSG_SIZ];
11256                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11257                         resultDetails);
11258                 if (first.pr != NoProc) {
11259                     SendToProgram(buf, &first);
11260                 }
11261                 if (second.pr != NoProc &&
11262                     gameMode == TwoMachinesPlay) {
11263                     SendToProgram(buf, &second);
11264                 }
11265             }
11266         }
11267
11268         if (appData.icsActive) {
11269             if (appData.quietPlay &&
11270                 (gameMode == IcsPlayingWhite ||
11271                  gameMode == IcsPlayingBlack)) {
11272                 SendToICS(ics_prefix);
11273                 SendToICS("set shout 1\n");
11274             }
11275             nextGameMode = IcsIdle;
11276             ics_user_moved = FALSE;
11277             /* clean up premove.  It's ugly when the game has ended and the
11278              * premove highlights are still on the board.
11279              */
11280             if (gotPremove) {
11281               gotPremove = FALSE;
11282               ClearPremoveHighlights();
11283               DrawPosition(FALSE, boards[currentMove]);
11284             }
11285             if (whosays == GE_ICS) {
11286                 switch (result) {
11287                 case WhiteWins:
11288                     if (gameMode == IcsPlayingWhite)
11289                         PlayIcsWinSound();
11290                     else if(gameMode == IcsPlayingBlack)
11291                         PlayIcsLossSound();
11292                     break;
11293                 case BlackWins:
11294                     if (gameMode == IcsPlayingBlack)
11295                         PlayIcsWinSound();
11296                     else if(gameMode == IcsPlayingWhite)
11297                         PlayIcsLossSound();
11298                     break;
11299                 case GameIsDrawn:
11300                     PlayIcsDrawSound();
11301                     break;
11302                 default:
11303                     PlayIcsUnfinishedSound();
11304                 }
11305             }
11306             if(appData.quitNext) { ExitEvent(0); return; }
11307         } else if (gameMode == EditGame ||
11308                    gameMode == PlayFromGameFile ||
11309                    gameMode == AnalyzeMode ||
11310                    gameMode == AnalyzeFile) {
11311             nextGameMode = gameMode;
11312         } else {
11313             nextGameMode = EndOfGame;
11314         }
11315         pausing = FALSE;
11316         ModeHighlight();
11317     } else {
11318         nextGameMode = gameMode;
11319     }
11320
11321     if (appData.noChessProgram) {
11322         gameMode = nextGameMode;
11323         ModeHighlight();
11324         endingGame = 0; /* [HGM] crash */
11325         return;
11326     }
11327
11328     if (first.reuse) {
11329         /* Put first chess program into idle state */
11330         if (first.pr != NoProc &&
11331             (gameMode == MachinePlaysWhite ||
11332              gameMode == MachinePlaysBlack ||
11333              gameMode == TwoMachinesPlay ||
11334              gameMode == IcsPlayingWhite ||
11335              gameMode == IcsPlayingBlack ||
11336              gameMode == BeginningOfGame)) {
11337             SendToProgram("force\n", &first);
11338             if (first.usePing) {
11339               char buf[MSG_SIZ];
11340               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11341               SendToProgram(buf, &first);
11342             }
11343         }
11344     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11345         /* Kill off first chess program */
11346         if (first.isr != NULL)
11347           RemoveInputSource(first.isr);
11348         first.isr = NULL;
11349
11350         if (first.pr != NoProc) {
11351             ExitAnalyzeMode();
11352             DoSleep( appData.delayBeforeQuit );
11353             SendToProgram("quit\n", &first);
11354             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11355             first.reload = TRUE;
11356         }
11357         first.pr = NoProc;
11358     }
11359     if (second.reuse) {
11360         /* Put second chess program into idle state */
11361         if (second.pr != NoProc &&
11362             gameMode == TwoMachinesPlay) {
11363             SendToProgram("force\n", &second);
11364             if (second.usePing) {
11365               char buf[MSG_SIZ];
11366               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11367               SendToProgram(buf, &second);
11368             }
11369         }
11370     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11371         /* Kill off second chess program */
11372         if (second.isr != NULL)
11373           RemoveInputSource(second.isr);
11374         second.isr = NULL;
11375
11376         if (second.pr != NoProc) {
11377             DoSleep( appData.delayBeforeQuit );
11378             SendToProgram("quit\n", &second);
11379             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11380             second.reload = TRUE;
11381         }
11382         second.pr = NoProc;
11383     }
11384
11385     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11386         char resChar = '=';
11387         switch (result) {
11388         case WhiteWins:
11389           resChar = '+';
11390           if (first.twoMachinesColor[0] == 'w') {
11391             first.matchWins++;
11392           } else {
11393             second.matchWins++;
11394           }
11395           break;
11396         case BlackWins:
11397           resChar = '-';
11398           if (first.twoMachinesColor[0] == 'b') {
11399             first.matchWins++;
11400           } else {
11401             second.matchWins++;
11402           }
11403           break;
11404         case GameUnfinished:
11405           resChar = ' ';
11406         default:
11407           break;
11408         }
11409
11410         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11411         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11412             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11413             ReserveGame(nextGame, resChar); // sets nextGame
11414             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11415             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11416         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11417
11418         if (nextGame <= appData.matchGames && !abortMatch) {
11419             gameMode = nextGameMode;
11420             matchGame = nextGame; // this will be overruled in tourney mode!
11421             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11422             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11423             endingGame = 0; /* [HGM] crash */
11424             return;
11425         } else {
11426             gameMode = nextGameMode;
11427             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11428                      first.tidy, second.tidy,
11429                      first.matchWins, second.matchWins,
11430                      appData.matchGames - (first.matchWins + second.matchWins));
11431             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11432             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11433             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11434             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11435                 first.twoMachinesColor = "black\n";
11436                 second.twoMachinesColor = "white\n";
11437             } else {
11438                 first.twoMachinesColor = "white\n";
11439                 second.twoMachinesColor = "black\n";
11440             }
11441         }
11442     }
11443     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11444         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11445       ExitAnalyzeMode();
11446     gameMode = nextGameMode;
11447     ModeHighlight();
11448     endingGame = 0;  /* [HGM] crash */
11449     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11450         if(matchMode == TRUE) { // match through command line: exit with or without popup
11451             if(ranking) {
11452                 ToNrEvent(forwardMostMove);
11453                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11454                 else ExitEvent(0);
11455             } else DisplayFatalError(buf, 0, 0);
11456         } else { // match through menu; just stop, with or without popup
11457             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11458             ModeHighlight();
11459             if(ranking){
11460                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11461             } else DisplayNote(buf);
11462       }
11463       if(ranking) free(ranking);
11464     }
11465 }
11466
11467 /* Assumes program was just initialized (initString sent).
11468    Leaves program in force mode. */
11469 void
11470 FeedMovesToProgram (ChessProgramState *cps, int upto)
11471 {
11472     int i;
11473
11474     if (appData.debugMode)
11475       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11476               startedFromSetupPosition ? "position and " : "",
11477               backwardMostMove, upto, cps->which);
11478     if(currentlyInitializedVariant != gameInfo.variant) {
11479       char buf[MSG_SIZ];
11480         // [HGM] variantswitch: make engine aware of new variant
11481         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11482                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11483                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11484         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11485         SendToProgram(buf, cps);
11486         currentlyInitializedVariant = gameInfo.variant;
11487     }
11488     SendToProgram("force\n", cps);
11489     if (startedFromSetupPosition) {
11490         SendBoard(cps, backwardMostMove);
11491     if (appData.debugMode) {
11492         fprintf(debugFP, "feedMoves\n");
11493     }
11494     }
11495     for (i = backwardMostMove; i < upto; i++) {
11496         SendMoveToProgram(i, cps);
11497     }
11498 }
11499
11500
11501 int
11502 ResurrectChessProgram ()
11503 {
11504      /* The chess program may have exited.
11505         If so, restart it and feed it all the moves made so far. */
11506     static int doInit = 0;
11507
11508     if (appData.noChessProgram) return 1;
11509
11510     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11511         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11512         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11513         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11514     } else {
11515         if (first.pr != NoProc) return 1;
11516         StartChessProgram(&first);
11517     }
11518     InitChessProgram(&first, FALSE);
11519     FeedMovesToProgram(&first, currentMove);
11520
11521     if (!first.sendTime) {
11522         /* can't tell gnuchess what its clock should read,
11523            so we bow to its notion. */
11524         ResetClocks();
11525         timeRemaining[0][currentMove] = whiteTimeRemaining;
11526         timeRemaining[1][currentMove] = blackTimeRemaining;
11527     }
11528
11529     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11530                 appData.icsEngineAnalyze) && first.analysisSupport) {
11531       SendToProgram("analyze\n", &first);
11532       first.analyzing = TRUE;
11533     }
11534     return 1;
11535 }
11536
11537 /*
11538  * Button procedures
11539  */
11540 void
11541 Reset (int redraw, int init)
11542 {
11543     int i;
11544
11545     if (appData.debugMode) {
11546         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11547                 redraw, init, gameMode);
11548     }
11549     CleanupTail(); // [HGM] vari: delete any stored variations
11550     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11551     pausing = pauseExamInvalid = FALSE;
11552     startedFromSetupPosition = blackPlaysFirst = FALSE;
11553     firstMove = TRUE;
11554     whiteFlag = blackFlag = FALSE;
11555     userOfferedDraw = FALSE;
11556     hintRequested = bookRequested = FALSE;
11557     first.maybeThinking = FALSE;
11558     second.maybeThinking = FALSE;
11559     first.bookSuspend = FALSE; // [HGM] book
11560     second.bookSuspend = FALSE;
11561     thinkOutput[0] = NULLCHAR;
11562     lastHint[0] = NULLCHAR;
11563     ClearGameInfo(&gameInfo);
11564     gameInfo.variant = StringToVariant(appData.variant);
11565     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11566     ics_user_moved = ics_clock_paused = FALSE;
11567     ics_getting_history = H_FALSE;
11568     ics_gamenum = -1;
11569     white_holding[0] = black_holding[0] = NULLCHAR;
11570     ClearProgramStats();
11571     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11572
11573     ResetFrontEnd();
11574     ClearHighlights();
11575     flipView = appData.flipView;
11576     ClearPremoveHighlights();
11577     gotPremove = FALSE;
11578     alarmSounded = FALSE;
11579     killX = killY = -1; // [HGM] lion
11580
11581     GameEnds(EndOfFile, NULL, GE_PLAYER);
11582     if(appData.serverMovesName != NULL) {
11583         /* [HGM] prepare to make moves file for broadcasting */
11584         clock_t t = clock();
11585         if(serverMoves != NULL) fclose(serverMoves);
11586         serverMoves = fopen(appData.serverMovesName, "r");
11587         if(serverMoves != NULL) {
11588             fclose(serverMoves);
11589             /* delay 15 sec before overwriting, so all clients can see end */
11590             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11591         }
11592         serverMoves = fopen(appData.serverMovesName, "w");
11593     }
11594
11595     ExitAnalyzeMode();
11596     gameMode = BeginningOfGame;
11597     ModeHighlight();
11598     if(appData.icsActive) gameInfo.variant = VariantNormal;
11599     currentMove = forwardMostMove = backwardMostMove = 0;
11600     MarkTargetSquares(1);
11601     InitPosition(redraw);
11602     for (i = 0; i < MAX_MOVES; i++) {
11603         if (commentList[i] != NULL) {
11604             free(commentList[i]);
11605             commentList[i] = NULL;
11606         }
11607     }
11608     ResetClocks();
11609     timeRemaining[0][0] = whiteTimeRemaining;
11610     timeRemaining[1][0] = blackTimeRemaining;
11611
11612     if (first.pr == NoProc) {
11613         StartChessProgram(&first);
11614     }
11615     if (init) {
11616             InitChessProgram(&first, startedFromSetupPosition);
11617     }
11618     DisplayTitle("");
11619     DisplayMessage("", "");
11620     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11621     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11622     ClearMap();        // [HGM] exclude: invalidate map
11623 }
11624
11625 void
11626 AutoPlayGameLoop ()
11627 {
11628     for (;;) {
11629         if (!AutoPlayOneMove())
11630           return;
11631         if (matchMode || appData.timeDelay == 0)
11632           continue;
11633         if (appData.timeDelay < 0)
11634           return;
11635         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11636         break;
11637     }
11638 }
11639
11640 void
11641 AnalyzeNextGame()
11642 {
11643     ReloadGame(1); // next game
11644 }
11645
11646 int
11647 AutoPlayOneMove ()
11648 {
11649     int fromX, fromY, toX, toY;
11650
11651     if (appData.debugMode) {
11652       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11653     }
11654
11655     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11656       return FALSE;
11657
11658     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11659       pvInfoList[currentMove].depth = programStats.depth;
11660       pvInfoList[currentMove].score = programStats.score;
11661       pvInfoList[currentMove].time  = 0;
11662       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11663       else { // append analysis of final position as comment
11664         char buf[MSG_SIZ];
11665         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11666         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11667       }
11668       programStats.depth = 0;
11669     }
11670
11671     if (currentMove >= forwardMostMove) {
11672       if(gameMode == AnalyzeFile) {
11673           if(appData.loadGameIndex == -1) {
11674             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11675           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11676           } else {
11677           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11678         }
11679       }
11680 //      gameMode = EndOfGame;
11681 //      ModeHighlight();
11682
11683       /* [AS] Clear current move marker at the end of a game */
11684       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11685
11686       return FALSE;
11687     }
11688
11689     toX = moveList[currentMove][2] - AAA;
11690     toY = moveList[currentMove][3] - ONE;
11691
11692     if (moveList[currentMove][1] == '@') {
11693         if (appData.highlightLastMove) {
11694             SetHighlights(-1, -1, toX, toY);
11695         }
11696     } else {
11697         fromX = moveList[currentMove][0] - AAA;
11698         fromY = moveList[currentMove][1] - ONE;
11699
11700         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11701
11702         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11703
11704         if (appData.highlightLastMove) {
11705             SetHighlights(fromX, fromY, toX, toY);
11706         }
11707     }
11708     DisplayMove(currentMove);
11709     SendMoveToProgram(currentMove++, &first);
11710     DisplayBothClocks();
11711     DrawPosition(FALSE, boards[currentMove]);
11712     // [HGM] PV info: always display, routine tests if empty
11713     DisplayComment(currentMove - 1, commentList[currentMove]);
11714     return TRUE;
11715 }
11716
11717
11718 int
11719 LoadGameOneMove (ChessMove readAhead)
11720 {
11721     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11722     char promoChar = NULLCHAR;
11723     ChessMove moveType;
11724     char move[MSG_SIZ];
11725     char *p, *q;
11726
11727     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11728         gameMode != AnalyzeMode && gameMode != Training) {
11729         gameFileFP = NULL;
11730         return FALSE;
11731     }
11732
11733     yyboardindex = forwardMostMove;
11734     if (readAhead != EndOfFile) {
11735       moveType = readAhead;
11736     } else {
11737       if (gameFileFP == NULL)
11738           return FALSE;
11739       moveType = (ChessMove) Myylex();
11740     }
11741
11742     done = FALSE;
11743     switch (moveType) {
11744       case Comment:
11745         if (appData.debugMode)
11746           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11747         p = yy_text;
11748
11749         /* append the comment but don't display it */
11750         AppendComment(currentMove, p, FALSE);
11751         return TRUE;
11752
11753       case WhiteCapturesEnPassant:
11754       case BlackCapturesEnPassant:
11755       case WhitePromotion:
11756       case BlackPromotion:
11757       case WhiteNonPromotion:
11758       case BlackNonPromotion:
11759       case NormalMove:
11760       case FirstLeg:
11761       case WhiteKingSideCastle:
11762       case WhiteQueenSideCastle:
11763       case BlackKingSideCastle:
11764       case BlackQueenSideCastle:
11765       case WhiteKingSideCastleWild:
11766       case WhiteQueenSideCastleWild:
11767       case BlackKingSideCastleWild:
11768       case BlackQueenSideCastleWild:
11769       /* PUSH Fabien */
11770       case WhiteHSideCastleFR:
11771       case WhiteASideCastleFR:
11772       case BlackHSideCastleFR:
11773       case BlackASideCastleFR:
11774       /* POP Fabien */
11775         if (appData.debugMode)
11776           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11777         fromX = currentMoveString[0] - AAA;
11778         fromY = currentMoveString[1] - ONE;
11779         toX = currentMoveString[2] - AAA;
11780         toY = currentMoveString[3] - ONE;
11781         promoChar = currentMoveString[4];
11782         if(promoChar == ';') promoChar = NULLCHAR;
11783         break;
11784
11785       case WhiteDrop:
11786       case BlackDrop:
11787         if (appData.debugMode)
11788           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11789         fromX = moveType == WhiteDrop ?
11790           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11791         (int) CharToPiece(ToLower(currentMoveString[0]));
11792         fromY = DROP_RANK;
11793         toX = currentMoveString[2] - AAA;
11794         toY = currentMoveString[3] - ONE;
11795         break;
11796
11797       case WhiteWins:
11798       case BlackWins:
11799       case GameIsDrawn:
11800       case GameUnfinished:
11801         if (appData.debugMode)
11802           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11803         p = strchr(yy_text, '{');
11804         if (p == NULL) p = strchr(yy_text, '(');
11805         if (p == NULL) {
11806             p = yy_text;
11807             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11808         } else {
11809             q = strchr(p, *p == '{' ? '}' : ')');
11810             if (q != NULL) *q = NULLCHAR;
11811             p++;
11812         }
11813         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11814         GameEnds(moveType, p, GE_FILE);
11815         done = TRUE;
11816         if (cmailMsgLoaded) {
11817             ClearHighlights();
11818             flipView = WhiteOnMove(currentMove);
11819             if (moveType == GameUnfinished) flipView = !flipView;
11820             if (appData.debugMode)
11821               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11822         }
11823         break;
11824
11825       case EndOfFile:
11826         if (appData.debugMode)
11827           fprintf(debugFP, "Parser hit end of file\n");
11828         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11829           case MT_NONE:
11830           case MT_CHECK:
11831             break;
11832           case MT_CHECKMATE:
11833           case MT_STAINMATE:
11834             if (WhiteOnMove(currentMove)) {
11835                 GameEnds(BlackWins, "Black mates", GE_FILE);
11836             } else {
11837                 GameEnds(WhiteWins, "White mates", GE_FILE);
11838             }
11839             break;
11840           case MT_STALEMATE:
11841             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11842             break;
11843         }
11844         done = TRUE;
11845         break;
11846
11847       case MoveNumberOne:
11848         if (lastLoadGameStart == GNUChessGame) {
11849             /* GNUChessGames have numbers, but they aren't move numbers */
11850             if (appData.debugMode)
11851               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11852                       yy_text, (int) moveType);
11853             return LoadGameOneMove(EndOfFile); /* tail recursion */
11854         }
11855         /* else fall thru */
11856
11857       case XBoardGame:
11858       case GNUChessGame:
11859       case PGNTag:
11860         /* Reached start of next game in file */
11861         if (appData.debugMode)
11862           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11863         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11864           case MT_NONE:
11865           case MT_CHECK:
11866             break;
11867           case MT_CHECKMATE:
11868           case MT_STAINMATE:
11869             if (WhiteOnMove(currentMove)) {
11870                 GameEnds(BlackWins, "Black mates", GE_FILE);
11871             } else {
11872                 GameEnds(WhiteWins, "White mates", GE_FILE);
11873             }
11874             break;
11875           case MT_STALEMATE:
11876             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11877             break;
11878         }
11879         done = TRUE;
11880         break;
11881
11882       case PositionDiagram:     /* should not happen; ignore */
11883       case ElapsedTime:         /* ignore */
11884       case NAG:                 /* ignore */
11885         if (appData.debugMode)
11886           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11887                   yy_text, (int) moveType);
11888         return LoadGameOneMove(EndOfFile); /* tail recursion */
11889
11890       case IllegalMove:
11891         if (appData.testLegality) {
11892             if (appData.debugMode)
11893               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11894             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11895                     (forwardMostMove / 2) + 1,
11896                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11897             DisplayError(move, 0);
11898             done = TRUE;
11899         } else {
11900             if (appData.debugMode)
11901               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11902                       yy_text, currentMoveString);
11903             fromX = currentMoveString[0] - AAA;
11904             fromY = currentMoveString[1] - ONE;
11905             toX = currentMoveString[2] - AAA;
11906             toY = currentMoveString[3] - ONE;
11907             promoChar = currentMoveString[4];
11908         }
11909         break;
11910
11911       case AmbiguousMove:
11912         if (appData.debugMode)
11913           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11914         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11915                 (forwardMostMove / 2) + 1,
11916                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11917         DisplayError(move, 0);
11918         done = TRUE;
11919         break;
11920
11921       default:
11922       case ImpossibleMove:
11923         if (appData.debugMode)
11924           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11925         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11926                 (forwardMostMove / 2) + 1,
11927                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11928         DisplayError(move, 0);
11929         done = TRUE;
11930         break;
11931     }
11932
11933     if (done) {
11934         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11935             DrawPosition(FALSE, boards[currentMove]);
11936             DisplayBothClocks();
11937             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11938               DisplayComment(currentMove - 1, commentList[currentMove]);
11939         }
11940         (void) StopLoadGameTimer();
11941         gameFileFP = NULL;
11942         cmailOldMove = forwardMostMove;
11943         return FALSE;
11944     } else {
11945         /* currentMoveString is set as a side-effect of yylex */
11946
11947         thinkOutput[0] = NULLCHAR;
11948         MakeMove(fromX, fromY, toX, toY, promoChar);
11949         killX = killY = -1; // [HGM] lion: used up
11950         currentMove = forwardMostMove;
11951         return TRUE;
11952     }
11953 }
11954
11955 /* Load the nth game from the given file */
11956 int
11957 LoadGameFromFile (char *filename, int n, char *title, int useList)
11958 {
11959     FILE *f;
11960     char buf[MSG_SIZ];
11961
11962     if (strcmp(filename, "-") == 0) {
11963         f = stdin;
11964         title = "stdin";
11965     } else {
11966         f = fopen(filename, "rb");
11967         if (f == NULL) {
11968           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11969             DisplayError(buf, errno);
11970             return FALSE;
11971         }
11972     }
11973     if (fseek(f, 0, 0) == -1) {
11974         /* f is not seekable; probably a pipe */
11975         useList = FALSE;
11976     }
11977     if (useList && n == 0) {
11978         int error = GameListBuild(f);
11979         if (error) {
11980             DisplayError(_("Cannot build game list"), error);
11981         } else if (!ListEmpty(&gameList) &&
11982                    ((ListGame *) gameList.tailPred)->number > 1) {
11983             GameListPopUp(f, title);
11984             return TRUE;
11985         }
11986         GameListDestroy();
11987         n = 1;
11988     }
11989     if (n == 0) n = 1;
11990     return LoadGame(f, n, title, FALSE);
11991 }
11992
11993
11994 void
11995 MakeRegisteredMove ()
11996 {
11997     int fromX, fromY, toX, toY;
11998     char promoChar;
11999     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12000         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12001           case CMAIL_MOVE:
12002           case CMAIL_DRAW:
12003             if (appData.debugMode)
12004               fprintf(debugFP, "Restoring %s for game %d\n",
12005                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12006
12007             thinkOutput[0] = NULLCHAR;
12008             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12009             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12010             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12011             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12012             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12013             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12014             MakeMove(fromX, fromY, toX, toY, promoChar);
12015             ShowMove(fromX, fromY, toX, toY);
12016
12017             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12018               case MT_NONE:
12019               case MT_CHECK:
12020                 break;
12021
12022               case MT_CHECKMATE:
12023               case MT_STAINMATE:
12024                 if (WhiteOnMove(currentMove)) {
12025                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12026                 } else {
12027                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12028                 }
12029                 break;
12030
12031               case MT_STALEMATE:
12032                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12033                 break;
12034             }
12035
12036             break;
12037
12038           case CMAIL_RESIGN:
12039             if (WhiteOnMove(currentMove)) {
12040                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12041             } else {
12042                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12043             }
12044             break;
12045
12046           case CMAIL_ACCEPT:
12047             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12048             break;
12049
12050           default:
12051             break;
12052         }
12053     }
12054
12055     return;
12056 }
12057
12058 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12059 int
12060 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12061 {
12062     int retVal;
12063
12064     if (gameNumber > nCmailGames) {
12065         DisplayError(_("No more games in this message"), 0);
12066         return FALSE;
12067     }
12068     if (f == lastLoadGameFP) {
12069         int offset = gameNumber - lastLoadGameNumber;
12070         if (offset == 0) {
12071             cmailMsg[0] = NULLCHAR;
12072             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12073                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12074                 nCmailMovesRegistered--;
12075             }
12076             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12077             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12078                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12079             }
12080         } else {
12081             if (! RegisterMove()) return FALSE;
12082         }
12083     }
12084
12085     retVal = LoadGame(f, gameNumber, title, useList);
12086
12087     /* Make move registered during previous look at this game, if any */
12088     MakeRegisteredMove();
12089
12090     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12091         commentList[currentMove]
12092           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12093         DisplayComment(currentMove - 1, commentList[currentMove]);
12094     }
12095
12096     return retVal;
12097 }
12098
12099 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12100 int
12101 ReloadGame (int offset)
12102 {
12103     int gameNumber = lastLoadGameNumber + offset;
12104     if (lastLoadGameFP == NULL) {
12105         DisplayError(_("No game has been loaded yet"), 0);
12106         return FALSE;
12107     }
12108     if (gameNumber <= 0) {
12109         DisplayError(_("Can't back up any further"), 0);
12110         return FALSE;
12111     }
12112     if (cmailMsgLoaded) {
12113         return CmailLoadGame(lastLoadGameFP, gameNumber,
12114                              lastLoadGameTitle, lastLoadGameUseList);
12115     } else {
12116         return LoadGame(lastLoadGameFP, gameNumber,
12117                         lastLoadGameTitle, lastLoadGameUseList);
12118     }
12119 }
12120
12121 int keys[EmptySquare+1];
12122
12123 int
12124 PositionMatches (Board b1, Board b2)
12125 {
12126     int r, f, sum=0;
12127     switch(appData.searchMode) {
12128         case 1: return CompareWithRights(b1, b2);
12129         case 2:
12130             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12131                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12132             }
12133             return TRUE;
12134         case 3:
12135             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12136               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12137                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12138             }
12139             return sum==0;
12140         case 4:
12141             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12142                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12143             }
12144             return sum==0;
12145     }
12146     return TRUE;
12147 }
12148
12149 #define Q_PROMO  4
12150 #define Q_EP     3
12151 #define Q_BCASTL 2
12152 #define Q_WCASTL 1
12153
12154 int pieceList[256], quickBoard[256];
12155 ChessSquare pieceType[256] = { EmptySquare };
12156 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12157 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12158 int soughtTotal, turn;
12159 Boolean epOK, flipSearch;
12160
12161 typedef struct {
12162     unsigned char piece, to;
12163 } Move;
12164
12165 #define DSIZE (250000)
12166
12167 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12168 Move *moveDatabase = initialSpace;
12169 unsigned int movePtr, dataSize = DSIZE;
12170
12171 int
12172 MakePieceList (Board board, int *counts)
12173 {
12174     int r, f, n=Q_PROMO, total=0;
12175     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12176     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12177         int sq = f + (r<<4);
12178         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12179             quickBoard[sq] = ++n;
12180             pieceList[n] = sq;
12181             pieceType[n] = board[r][f];
12182             counts[board[r][f]]++;
12183             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12184             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12185             total++;
12186         }
12187     }
12188     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12189     return total;
12190 }
12191
12192 void
12193 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12194 {
12195     int sq = fromX + (fromY<<4);
12196     int piece = quickBoard[sq], rook;
12197     quickBoard[sq] = 0;
12198     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12199     if(piece == pieceList[1] && fromY == toY) {
12200       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12201         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12202         moveDatabase[movePtr++].piece = Q_WCASTL;
12203         quickBoard[sq] = piece;
12204         piece = quickBoard[from]; quickBoard[from] = 0;
12205         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12206       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12207         quickBoard[sq] = 0; // remove Rook
12208         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12209         moveDatabase[movePtr++].piece = Q_WCASTL;
12210         quickBoard[sq] = pieceList[1]; // put King
12211         piece = rook;
12212         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12213       }
12214     } else
12215     if(piece == pieceList[2] && fromY == toY) {
12216       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12217         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12218         moveDatabase[movePtr++].piece = Q_BCASTL;
12219         quickBoard[sq] = piece;
12220         piece = quickBoard[from]; quickBoard[from] = 0;
12221         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12222       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12223         quickBoard[sq] = 0; // remove Rook
12224         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12225         moveDatabase[movePtr++].piece = Q_BCASTL;
12226         quickBoard[sq] = pieceList[2]; // put King
12227         piece = rook;
12228         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12229       }
12230     } else
12231     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12232         quickBoard[(fromY<<4)+toX] = 0;
12233         moveDatabase[movePtr].piece = Q_EP;
12234         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12235         moveDatabase[movePtr].to = sq;
12236     } else
12237     if(promoPiece != pieceType[piece]) {
12238         moveDatabase[movePtr++].piece = Q_PROMO;
12239         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12240     }
12241     moveDatabase[movePtr].piece = piece;
12242     quickBoard[sq] = piece;
12243     movePtr++;
12244 }
12245
12246 int
12247 PackGame (Board board)
12248 {
12249     Move *newSpace = NULL;
12250     moveDatabase[movePtr].piece = 0; // terminate previous game
12251     if(movePtr > dataSize) {
12252         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12253         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12254         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12255         if(newSpace) {
12256             int i;
12257             Move *p = moveDatabase, *q = newSpace;
12258             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12259             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12260             moveDatabase = newSpace;
12261         } else { // calloc failed, we must be out of memory. Too bad...
12262             dataSize = 0; // prevent calloc events for all subsequent games
12263             return 0;     // and signal this one isn't cached
12264         }
12265     }
12266     movePtr++;
12267     MakePieceList(board, counts);
12268     return movePtr;
12269 }
12270
12271 int
12272 QuickCompare (Board board, int *minCounts, int *maxCounts)
12273 {   // compare according to search mode
12274     int r, f;
12275     switch(appData.searchMode)
12276     {
12277       case 1: // exact position match
12278         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12279         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12280             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12281         }
12282         break;
12283       case 2: // can have extra material on empty squares
12284         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12285             if(board[r][f] == EmptySquare) continue;
12286             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12287         }
12288         break;
12289       case 3: // material with exact Pawn structure
12290         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12291             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12292             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12293         } // fall through to material comparison
12294       case 4: // exact material
12295         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12296         break;
12297       case 6: // material range with given imbalance
12298         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12299         // fall through to range comparison
12300       case 5: // material range
12301         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12302     }
12303     return TRUE;
12304 }
12305
12306 int
12307 QuickScan (Board board, Move *move)
12308 {   // reconstruct game,and compare all positions in it
12309     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12310     do {
12311         int piece = move->piece;
12312         int to = move->to, from = pieceList[piece];
12313         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12314           if(!piece) return -1;
12315           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12316             piece = (++move)->piece;
12317             from = pieceList[piece];
12318             counts[pieceType[piece]]--;
12319             pieceType[piece] = (ChessSquare) move->to;
12320             counts[move->to]++;
12321           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12322             counts[pieceType[quickBoard[to]]]--;
12323             quickBoard[to] = 0; total--;
12324             move++;
12325             continue;
12326           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12327             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12328             from  = pieceList[piece]; // so this must be King
12329             quickBoard[from] = 0;
12330             pieceList[piece] = to;
12331             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12332             quickBoard[from] = 0; // rook
12333             quickBoard[to] = piece;
12334             to = move->to; piece = move->piece;
12335             goto aftercastle;
12336           }
12337         }
12338         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12339         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12340         quickBoard[from] = 0;
12341       aftercastle:
12342         quickBoard[to] = piece;
12343         pieceList[piece] = to;
12344         cnt++; turn ^= 3;
12345         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12346            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12347            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12348                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12349           ) {
12350             static int lastCounts[EmptySquare+1];
12351             int i;
12352             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12353             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12354         } else stretch = 0;
12355         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12356         move++;
12357     } while(1);
12358 }
12359
12360 void
12361 InitSearch ()
12362 {
12363     int r, f;
12364     flipSearch = FALSE;
12365     CopyBoard(soughtBoard, boards[currentMove]);
12366     soughtTotal = MakePieceList(soughtBoard, maxSought);
12367     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12368     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12369     CopyBoard(reverseBoard, boards[currentMove]);
12370     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12371         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12372         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12373         reverseBoard[r][f] = piece;
12374     }
12375     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12376     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12377     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12378                  || (boards[currentMove][CASTLING][2] == NoRights ||
12379                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12380                  && (boards[currentMove][CASTLING][5] == NoRights ||
12381                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12382       ) {
12383         flipSearch = TRUE;
12384         CopyBoard(flipBoard, soughtBoard);
12385         CopyBoard(rotateBoard, reverseBoard);
12386         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12387             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12388             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12389         }
12390     }
12391     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12392     if(appData.searchMode >= 5) {
12393         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12394         MakePieceList(soughtBoard, minSought);
12395         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12396     }
12397     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12398         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12399 }
12400
12401 GameInfo dummyInfo;
12402 static int creatingBook;
12403
12404 int
12405 GameContainsPosition (FILE *f, ListGame *lg)
12406 {
12407     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12408     int fromX, fromY, toX, toY;
12409     char promoChar;
12410     static int initDone=FALSE;
12411
12412     // weed out games based on numerical tag comparison
12413     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12414     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12415     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12416     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12417     if(!initDone) {
12418         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12419         initDone = TRUE;
12420     }
12421     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12422     else CopyBoard(boards[scratch], initialPosition); // default start position
12423     if(lg->moves) {
12424         turn = btm + 1;
12425         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12426         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12427     }
12428     if(btm) plyNr++;
12429     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12430     fseek(f, lg->offset, 0);
12431     yynewfile(f);
12432     while(1) {
12433         yyboardindex = scratch;
12434         quickFlag = plyNr+1;
12435         next = Myylex();
12436         quickFlag = 0;
12437         switch(next) {
12438             case PGNTag:
12439                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12440             default:
12441                 continue;
12442
12443             case XBoardGame:
12444             case GNUChessGame:
12445                 if(plyNr) return -1; // after we have seen moves, this is for new game
12446               continue;
12447
12448             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12449             case ImpossibleMove:
12450             case WhiteWins: // game ends here with these four
12451             case BlackWins:
12452             case GameIsDrawn:
12453             case GameUnfinished:
12454                 return -1;
12455
12456             case IllegalMove:
12457                 if(appData.testLegality) return -1;
12458             case WhiteCapturesEnPassant:
12459             case BlackCapturesEnPassant:
12460             case WhitePromotion:
12461             case BlackPromotion:
12462             case WhiteNonPromotion:
12463             case BlackNonPromotion:
12464             case NormalMove:
12465             case FirstLeg:
12466             case WhiteKingSideCastle:
12467             case WhiteQueenSideCastle:
12468             case BlackKingSideCastle:
12469             case BlackQueenSideCastle:
12470             case WhiteKingSideCastleWild:
12471             case WhiteQueenSideCastleWild:
12472             case BlackKingSideCastleWild:
12473             case BlackQueenSideCastleWild:
12474             case WhiteHSideCastleFR:
12475             case WhiteASideCastleFR:
12476             case BlackHSideCastleFR:
12477             case BlackASideCastleFR:
12478                 fromX = currentMoveString[0] - AAA;
12479                 fromY = currentMoveString[1] - ONE;
12480                 toX = currentMoveString[2] - AAA;
12481                 toY = currentMoveString[3] - ONE;
12482                 promoChar = currentMoveString[4];
12483                 break;
12484             case WhiteDrop:
12485             case BlackDrop:
12486                 fromX = next == WhiteDrop ?
12487                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12488                   (int) CharToPiece(ToLower(currentMoveString[0]));
12489                 fromY = DROP_RANK;
12490                 toX = currentMoveString[2] - AAA;
12491                 toY = currentMoveString[3] - ONE;
12492                 promoChar = 0;
12493                 break;
12494         }
12495         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12496         plyNr++;
12497         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12498         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12499         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12500         if(appData.findMirror) {
12501             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12502             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12503         }
12504     }
12505 }
12506
12507 /* Load the nth game from open file f */
12508 int
12509 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12510 {
12511     ChessMove cm;
12512     char buf[MSG_SIZ];
12513     int gn = gameNumber;
12514     ListGame *lg = NULL;
12515     int numPGNTags = 0;
12516     int err, pos = -1;
12517     GameMode oldGameMode;
12518     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12519
12520     if (appData.debugMode)
12521         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12522
12523     if (gameMode == Training )
12524         SetTrainingModeOff();
12525
12526     oldGameMode = gameMode;
12527     if (gameMode != BeginningOfGame) {
12528       Reset(FALSE, TRUE);
12529     }
12530     killX = killY = -1; // [HGM] lion: in case we did not Reset
12531
12532     gameFileFP = f;
12533     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12534         fclose(lastLoadGameFP);
12535     }
12536
12537     if (useList) {
12538         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12539
12540         if (lg) {
12541             fseek(f, lg->offset, 0);
12542             GameListHighlight(gameNumber);
12543             pos = lg->position;
12544             gn = 1;
12545         }
12546         else {
12547             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12548               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12549             else
12550             DisplayError(_("Game number out of range"), 0);
12551             return FALSE;
12552         }
12553     } else {
12554         GameListDestroy();
12555         if (fseek(f, 0, 0) == -1) {
12556             if (f == lastLoadGameFP ?
12557                 gameNumber == lastLoadGameNumber + 1 :
12558                 gameNumber == 1) {
12559                 gn = 1;
12560             } else {
12561                 DisplayError(_("Can't seek on game file"), 0);
12562                 return FALSE;
12563             }
12564         }
12565     }
12566     lastLoadGameFP = f;
12567     lastLoadGameNumber = gameNumber;
12568     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12569     lastLoadGameUseList = useList;
12570
12571     yynewfile(f);
12572
12573     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12574       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12575                 lg->gameInfo.black);
12576             DisplayTitle(buf);
12577     } else if (*title != NULLCHAR) {
12578         if (gameNumber > 1) {
12579           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12580             DisplayTitle(buf);
12581         } else {
12582             DisplayTitle(title);
12583         }
12584     }
12585
12586     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12587         gameMode = PlayFromGameFile;
12588         ModeHighlight();
12589     }
12590
12591     currentMove = forwardMostMove = backwardMostMove = 0;
12592     CopyBoard(boards[0], initialPosition);
12593     StopClocks();
12594
12595     /*
12596      * Skip the first gn-1 games in the file.
12597      * Also skip over anything that precedes an identifiable
12598      * start of game marker, to avoid being confused by
12599      * garbage at the start of the file.  Currently
12600      * recognized start of game markers are the move number "1",
12601      * the pattern "gnuchess .* game", the pattern
12602      * "^[#;%] [^ ]* game file", and a PGN tag block.
12603      * A game that starts with one of the latter two patterns
12604      * will also have a move number 1, possibly
12605      * following a position diagram.
12606      * 5-4-02: Let's try being more lenient and allowing a game to
12607      * start with an unnumbered move.  Does that break anything?
12608      */
12609     cm = lastLoadGameStart = EndOfFile;
12610     while (gn > 0) {
12611         yyboardindex = forwardMostMove;
12612         cm = (ChessMove) Myylex();
12613         switch (cm) {
12614           case EndOfFile:
12615             if (cmailMsgLoaded) {
12616                 nCmailGames = CMAIL_MAX_GAMES - gn;
12617             } else {
12618                 Reset(TRUE, TRUE);
12619                 DisplayError(_("Game not found in file"), 0);
12620             }
12621             return FALSE;
12622
12623           case GNUChessGame:
12624           case XBoardGame:
12625             gn--;
12626             lastLoadGameStart = cm;
12627             break;
12628
12629           case MoveNumberOne:
12630             switch (lastLoadGameStart) {
12631               case GNUChessGame:
12632               case XBoardGame:
12633               case PGNTag:
12634                 break;
12635               case MoveNumberOne:
12636               case EndOfFile:
12637                 gn--;           /* count this game */
12638                 lastLoadGameStart = cm;
12639                 break;
12640               default:
12641                 /* impossible */
12642                 break;
12643             }
12644             break;
12645
12646           case PGNTag:
12647             switch (lastLoadGameStart) {
12648               case GNUChessGame:
12649               case PGNTag:
12650               case MoveNumberOne:
12651               case EndOfFile:
12652                 gn--;           /* count this game */
12653                 lastLoadGameStart = cm;
12654                 break;
12655               case XBoardGame:
12656                 lastLoadGameStart = cm; /* game counted already */
12657                 break;
12658               default:
12659                 /* impossible */
12660                 break;
12661             }
12662             if (gn > 0) {
12663                 do {
12664                     yyboardindex = forwardMostMove;
12665                     cm = (ChessMove) Myylex();
12666                 } while (cm == PGNTag || cm == Comment);
12667             }
12668             break;
12669
12670           case WhiteWins:
12671           case BlackWins:
12672           case GameIsDrawn:
12673             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12674                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12675                     != CMAIL_OLD_RESULT) {
12676                     nCmailResults ++ ;
12677                     cmailResult[  CMAIL_MAX_GAMES
12678                                 - gn - 1] = CMAIL_OLD_RESULT;
12679                 }
12680             }
12681             break;
12682
12683           case NormalMove:
12684           case FirstLeg:
12685             /* Only a NormalMove can be at the start of a game
12686              * without a position diagram. */
12687             if (lastLoadGameStart == EndOfFile ) {
12688               gn--;
12689               lastLoadGameStart = MoveNumberOne;
12690             }
12691             break;
12692
12693           default:
12694             break;
12695         }
12696     }
12697
12698     if (appData.debugMode)
12699       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12700
12701     if (cm == XBoardGame) {
12702         /* Skip any header junk before position diagram and/or move 1 */
12703         for (;;) {
12704             yyboardindex = forwardMostMove;
12705             cm = (ChessMove) Myylex();
12706
12707             if (cm == EndOfFile ||
12708                 cm == GNUChessGame || cm == XBoardGame) {
12709                 /* Empty game; pretend end-of-file and handle later */
12710                 cm = EndOfFile;
12711                 break;
12712             }
12713
12714             if (cm == MoveNumberOne || cm == PositionDiagram ||
12715                 cm == PGNTag || cm == Comment)
12716               break;
12717         }
12718     } else if (cm == GNUChessGame) {
12719         if (gameInfo.event != NULL) {
12720             free(gameInfo.event);
12721         }
12722         gameInfo.event = StrSave(yy_text);
12723     }
12724
12725     startedFromSetupPosition = FALSE;
12726     while (cm == PGNTag) {
12727         if (appData.debugMode)
12728           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12729         err = ParsePGNTag(yy_text, &gameInfo);
12730         if (!err) numPGNTags++;
12731
12732         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12733         if(gameInfo.variant != oldVariant) {
12734             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12735             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12736             InitPosition(TRUE);
12737             oldVariant = gameInfo.variant;
12738             if (appData.debugMode)
12739               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12740         }
12741
12742
12743         if (gameInfo.fen != NULL) {
12744           Board initial_position;
12745           startedFromSetupPosition = TRUE;
12746           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12747             Reset(TRUE, TRUE);
12748             DisplayError(_("Bad FEN position in file"), 0);
12749             return FALSE;
12750           }
12751           CopyBoard(boards[0], initial_position);
12752           if (blackPlaysFirst) {
12753             currentMove = forwardMostMove = backwardMostMove = 1;
12754             CopyBoard(boards[1], initial_position);
12755             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12756             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12757             timeRemaining[0][1] = whiteTimeRemaining;
12758             timeRemaining[1][1] = blackTimeRemaining;
12759             if (commentList[0] != NULL) {
12760               commentList[1] = commentList[0];
12761               commentList[0] = NULL;
12762             }
12763           } else {
12764             currentMove = forwardMostMove = backwardMostMove = 0;
12765           }
12766           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12767           {   int i;
12768               initialRulePlies = FENrulePlies;
12769               for( i=0; i< nrCastlingRights; i++ )
12770                   initialRights[i] = initial_position[CASTLING][i];
12771           }
12772           yyboardindex = forwardMostMove;
12773           free(gameInfo.fen);
12774           gameInfo.fen = NULL;
12775         }
12776
12777         yyboardindex = forwardMostMove;
12778         cm = (ChessMove) Myylex();
12779
12780         /* Handle comments interspersed among the tags */
12781         while (cm == Comment) {
12782             char *p;
12783             if (appData.debugMode)
12784               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12785             p = yy_text;
12786             AppendComment(currentMove, p, FALSE);
12787             yyboardindex = forwardMostMove;
12788             cm = (ChessMove) Myylex();
12789         }
12790     }
12791
12792     /* don't rely on existence of Event tag since if game was
12793      * pasted from clipboard the Event tag may not exist
12794      */
12795     if (numPGNTags > 0){
12796         char *tags;
12797         if (gameInfo.variant == VariantNormal) {
12798           VariantClass v = StringToVariant(gameInfo.event);
12799           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12800           if(v < VariantShogi) gameInfo.variant = v;
12801         }
12802         if (!matchMode) {
12803           if( appData.autoDisplayTags ) {
12804             tags = PGNTags(&gameInfo);
12805             TagsPopUp(tags, CmailMsg());
12806             free(tags);
12807           }
12808         }
12809     } else {
12810         /* Make something up, but don't display it now */
12811         SetGameInfo();
12812         TagsPopDown();
12813     }
12814
12815     if (cm == PositionDiagram) {
12816         int i, j;
12817         char *p;
12818         Board initial_position;
12819
12820         if (appData.debugMode)
12821           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12822
12823         if (!startedFromSetupPosition) {
12824             p = yy_text;
12825             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12826               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12827                 switch (*p) {
12828                   case '{':
12829                   case '[':
12830                   case '-':
12831                   case ' ':
12832                   case '\t':
12833                   case '\n':
12834                   case '\r':
12835                     break;
12836                   default:
12837                     initial_position[i][j++] = CharToPiece(*p);
12838                     break;
12839                 }
12840             while (*p == ' ' || *p == '\t' ||
12841                    *p == '\n' || *p == '\r') p++;
12842
12843             if (strncmp(p, "black", strlen("black"))==0)
12844               blackPlaysFirst = TRUE;
12845             else
12846               blackPlaysFirst = FALSE;
12847             startedFromSetupPosition = TRUE;
12848
12849             CopyBoard(boards[0], initial_position);
12850             if (blackPlaysFirst) {
12851                 currentMove = forwardMostMove = backwardMostMove = 1;
12852                 CopyBoard(boards[1], initial_position);
12853                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12854                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12855                 timeRemaining[0][1] = whiteTimeRemaining;
12856                 timeRemaining[1][1] = blackTimeRemaining;
12857                 if (commentList[0] != NULL) {
12858                     commentList[1] = commentList[0];
12859                     commentList[0] = NULL;
12860                 }
12861             } else {
12862                 currentMove = forwardMostMove = backwardMostMove = 0;
12863             }
12864         }
12865         yyboardindex = forwardMostMove;
12866         cm = (ChessMove) Myylex();
12867     }
12868
12869   if(!creatingBook) {
12870     if (first.pr == NoProc) {
12871         StartChessProgram(&first);
12872     }
12873     InitChessProgram(&first, FALSE);
12874     SendToProgram("force\n", &first);
12875     if (startedFromSetupPosition) {
12876         SendBoard(&first, forwardMostMove);
12877     if (appData.debugMode) {
12878         fprintf(debugFP, "Load Game\n");
12879     }
12880         DisplayBothClocks();
12881     }
12882   }
12883
12884     /* [HGM] server: flag to write setup moves in broadcast file as one */
12885     loadFlag = appData.suppressLoadMoves;
12886
12887     while (cm == Comment) {
12888         char *p;
12889         if (appData.debugMode)
12890           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12891         p = yy_text;
12892         AppendComment(currentMove, p, FALSE);
12893         yyboardindex = forwardMostMove;
12894         cm = (ChessMove) Myylex();
12895     }
12896
12897     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12898         cm == WhiteWins || cm == BlackWins ||
12899         cm == GameIsDrawn || cm == GameUnfinished) {
12900         DisplayMessage("", _("No moves in game"));
12901         if (cmailMsgLoaded) {
12902             if (appData.debugMode)
12903               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12904             ClearHighlights();
12905             flipView = FALSE;
12906         }
12907         DrawPosition(FALSE, boards[currentMove]);
12908         DisplayBothClocks();
12909         gameMode = EditGame;
12910         ModeHighlight();
12911         gameFileFP = NULL;
12912         cmailOldMove = 0;
12913         return TRUE;
12914     }
12915
12916     // [HGM] PV info: routine tests if comment empty
12917     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12918         DisplayComment(currentMove - 1, commentList[currentMove]);
12919     }
12920     if (!matchMode && appData.timeDelay != 0)
12921       DrawPosition(FALSE, boards[currentMove]);
12922
12923     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12924       programStats.ok_to_send = 1;
12925     }
12926
12927     /* if the first token after the PGN tags is a move
12928      * and not move number 1, retrieve it from the parser
12929      */
12930     if (cm != MoveNumberOne)
12931         LoadGameOneMove(cm);
12932
12933     /* load the remaining moves from the file */
12934     while (LoadGameOneMove(EndOfFile)) {
12935       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12936       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12937     }
12938
12939     /* rewind to the start of the game */
12940     currentMove = backwardMostMove;
12941
12942     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12943
12944     if (oldGameMode == AnalyzeFile) {
12945       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12946       AnalyzeFileEvent();
12947     } else
12948     if (oldGameMode == AnalyzeMode) {
12949       AnalyzeFileEvent();
12950     }
12951
12952     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12953         long int w, b; // [HGM] adjourn: restore saved clock times
12954         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12955         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12956             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12957             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12958         }
12959     }
12960
12961     if(creatingBook) return TRUE;
12962     if (!matchMode && pos > 0) {
12963         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12964     } else
12965     if (matchMode || appData.timeDelay == 0) {
12966       ToEndEvent();
12967     } else if (appData.timeDelay > 0) {
12968       AutoPlayGameLoop();
12969     }
12970
12971     if (appData.debugMode)
12972         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12973
12974     loadFlag = 0; /* [HGM] true game starts */
12975     return TRUE;
12976 }
12977
12978 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12979 int
12980 ReloadPosition (int offset)
12981 {
12982     int positionNumber = lastLoadPositionNumber + offset;
12983     if (lastLoadPositionFP == NULL) {
12984         DisplayError(_("No position has been loaded yet"), 0);
12985         return FALSE;
12986     }
12987     if (positionNumber <= 0) {
12988         DisplayError(_("Can't back up any further"), 0);
12989         return FALSE;
12990     }
12991     return LoadPosition(lastLoadPositionFP, positionNumber,
12992                         lastLoadPositionTitle);
12993 }
12994
12995 /* Load the nth position from the given file */
12996 int
12997 LoadPositionFromFile (char *filename, int n, char *title)
12998 {
12999     FILE *f;
13000     char buf[MSG_SIZ];
13001
13002     if (strcmp(filename, "-") == 0) {
13003         return LoadPosition(stdin, n, "stdin");
13004     } else {
13005         f = fopen(filename, "rb");
13006         if (f == NULL) {
13007             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13008             DisplayError(buf, errno);
13009             return FALSE;
13010         } else {
13011             return LoadPosition(f, n, title);
13012         }
13013     }
13014 }
13015
13016 /* Load the nth position from the given open file, and close it */
13017 int
13018 LoadPosition (FILE *f, int positionNumber, char *title)
13019 {
13020     char *p, line[MSG_SIZ];
13021     Board initial_position;
13022     int i, j, fenMode, pn;
13023
13024     if (gameMode == Training )
13025         SetTrainingModeOff();
13026
13027     if (gameMode != BeginningOfGame) {
13028         Reset(FALSE, TRUE);
13029     }
13030     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13031         fclose(lastLoadPositionFP);
13032     }
13033     if (positionNumber == 0) positionNumber = 1;
13034     lastLoadPositionFP = f;
13035     lastLoadPositionNumber = positionNumber;
13036     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13037     if (first.pr == NoProc && !appData.noChessProgram) {
13038       StartChessProgram(&first);
13039       InitChessProgram(&first, FALSE);
13040     }
13041     pn = positionNumber;
13042     if (positionNumber < 0) {
13043         /* Negative position number means to seek to that byte offset */
13044         if (fseek(f, -positionNumber, 0) == -1) {
13045             DisplayError(_("Can't seek on position file"), 0);
13046             return FALSE;
13047         };
13048         pn = 1;
13049     } else {
13050         if (fseek(f, 0, 0) == -1) {
13051             if (f == lastLoadPositionFP ?
13052                 positionNumber == lastLoadPositionNumber + 1 :
13053                 positionNumber == 1) {
13054                 pn = 1;
13055             } else {
13056                 DisplayError(_("Can't seek on position file"), 0);
13057                 return FALSE;
13058             }
13059         }
13060     }
13061     /* See if this file is FEN or old-style xboard */
13062     if (fgets(line, MSG_SIZ, f) == NULL) {
13063         DisplayError(_("Position not found in file"), 0);
13064         return FALSE;
13065     }
13066     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13067     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13068
13069     if (pn >= 2) {
13070         if (fenMode || line[0] == '#') pn--;
13071         while (pn > 0) {
13072             /* skip positions before number pn */
13073             if (fgets(line, MSG_SIZ, f) == NULL) {
13074                 Reset(TRUE, TRUE);
13075                 DisplayError(_("Position not found in file"), 0);
13076                 return FALSE;
13077             }
13078             if (fenMode || line[0] == '#') pn--;
13079         }
13080     }
13081
13082     if (fenMode) {
13083         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13084             DisplayError(_("Bad FEN position in file"), 0);
13085             return FALSE;
13086         }
13087     } else {
13088         (void) fgets(line, MSG_SIZ, f);
13089         (void) fgets(line, MSG_SIZ, f);
13090
13091         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13092             (void) fgets(line, MSG_SIZ, f);
13093             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13094                 if (*p == ' ')
13095                   continue;
13096                 initial_position[i][j++] = CharToPiece(*p);
13097             }
13098         }
13099
13100         blackPlaysFirst = FALSE;
13101         if (!feof(f)) {
13102             (void) fgets(line, MSG_SIZ, f);
13103             if (strncmp(line, "black", strlen("black"))==0)
13104               blackPlaysFirst = TRUE;
13105         }
13106     }
13107     startedFromSetupPosition = TRUE;
13108
13109     CopyBoard(boards[0], initial_position);
13110     if (blackPlaysFirst) {
13111         currentMove = forwardMostMove = backwardMostMove = 1;
13112         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13113         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13114         CopyBoard(boards[1], initial_position);
13115         DisplayMessage("", _("Black to play"));
13116     } else {
13117         currentMove = forwardMostMove = backwardMostMove = 0;
13118         DisplayMessage("", _("White to play"));
13119     }
13120     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13121     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13122         SendToProgram("force\n", &first);
13123         SendBoard(&first, forwardMostMove);
13124     }
13125     if (appData.debugMode) {
13126 int i, j;
13127   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13128   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13129         fprintf(debugFP, "Load Position\n");
13130     }
13131
13132     if (positionNumber > 1) {
13133       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13134         DisplayTitle(line);
13135     } else {
13136         DisplayTitle(title);
13137     }
13138     gameMode = EditGame;
13139     ModeHighlight();
13140     ResetClocks();
13141     timeRemaining[0][1] = whiteTimeRemaining;
13142     timeRemaining[1][1] = blackTimeRemaining;
13143     DrawPosition(FALSE, boards[currentMove]);
13144
13145     return TRUE;
13146 }
13147
13148
13149 void
13150 CopyPlayerNameIntoFileName (char **dest, char *src)
13151 {
13152     while (*src != NULLCHAR && *src != ',') {
13153         if (*src == ' ') {
13154             *(*dest)++ = '_';
13155             src++;
13156         } else {
13157             *(*dest)++ = *src++;
13158         }
13159     }
13160 }
13161
13162 char *
13163 DefaultFileName (char *ext)
13164 {
13165     static char def[MSG_SIZ];
13166     char *p;
13167
13168     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13169         p = def;
13170         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13171         *p++ = '-';
13172         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13173         *p++ = '.';
13174         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13175     } else {
13176         def[0] = NULLCHAR;
13177     }
13178     return def;
13179 }
13180
13181 /* Save the current game to the given file */
13182 int
13183 SaveGameToFile (char *filename, int append)
13184 {
13185     FILE *f;
13186     char buf[MSG_SIZ];
13187     int result, i, t,tot=0;
13188
13189     if (strcmp(filename, "-") == 0) {
13190         return SaveGame(stdout, 0, NULL);
13191     } else {
13192         for(i=0; i<10; i++) { // upto 10 tries
13193              f = fopen(filename, append ? "a" : "w");
13194              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13195              if(f || errno != 13) break;
13196              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13197              tot += t;
13198         }
13199         if (f == NULL) {
13200             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13201             DisplayError(buf, errno);
13202             return FALSE;
13203         } else {
13204             safeStrCpy(buf, lastMsg, MSG_SIZ);
13205             DisplayMessage(_("Waiting for access to save file"), "");
13206             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13207             DisplayMessage(_("Saving game"), "");
13208             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13209             result = SaveGame(f, 0, NULL);
13210             DisplayMessage(buf, "");
13211             return result;
13212         }
13213     }
13214 }
13215
13216 char *
13217 SavePart (char *str)
13218 {
13219     static char buf[MSG_SIZ];
13220     char *p;
13221
13222     p = strchr(str, ' ');
13223     if (p == NULL) return str;
13224     strncpy(buf, str, p - str);
13225     buf[p - str] = NULLCHAR;
13226     return buf;
13227 }
13228
13229 #define PGN_MAX_LINE 75
13230
13231 #define PGN_SIDE_WHITE  0
13232 #define PGN_SIDE_BLACK  1
13233
13234 static int
13235 FindFirstMoveOutOfBook (int side)
13236 {
13237     int result = -1;
13238
13239     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13240         int index = backwardMostMove;
13241         int has_book_hit = 0;
13242
13243         if( (index % 2) != side ) {
13244             index++;
13245         }
13246
13247         while( index < forwardMostMove ) {
13248             /* Check to see if engine is in book */
13249             int depth = pvInfoList[index].depth;
13250             int score = pvInfoList[index].score;
13251             int in_book = 0;
13252
13253             if( depth <= 2 ) {
13254                 in_book = 1;
13255             }
13256             else if( score == 0 && depth == 63 ) {
13257                 in_book = 1; /* Zappa */
13258             }
13259             else if( score == 2 && depth == 99 ) {
13260                 in_book = 1; /* Abrok */
13261             }
13262
13263             has_book_hit += in_book;
13264
13265             if( ! in_book ) {
13266                 result = index;
13267
13268                 break;
13269             }
13270
13271             index += 2;
13272         }
13273     }
13274
13275     return result;
13276 }
13277
13278 void
13279 GetOutOfBookInfo (char * buf)
13280 {
13281     int oob[2];
13282     int i;
13283     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13284
13285     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13286     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13287
13288     *buf = '\0';
13289
13290     if( oob[0] >= 0 || oob[1] >= 0 ) {
13291         for( i=0; i<2; i++ ) {
13292             int idx = oob[i];
13293
13294             if( idx >= 0 ) {
13295                 if( i > 0 && oob[0] >= 0 ) {
13296                     strcat( buf, "   " );
13297                 }
13298
13299                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13300                 sprintf( buf+strlen(buf), "%s%.2f",
13301                     pvInfoList[idx].score >= 0 ? "+" : "",
13302                     pvInfoList[idx].score / 100.0 );
13303             }
13304         }
13305     }
13306 }
13307
13308 /* Save game in PGN style and close the file */
13309 int
13310 SaveGamePGN (FILE *f)
13311 {
13312     int i, offset, linelen, newblock;
13313 //    char *movetext;
13314     char numtext[32];
13315     int movelen, numlen, blank;
13316     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13317
13318     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13319
13320     PrintPGNTags(f, &gameInfo);
13321
13322     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13323
13324     if (backwardMostMove > 0 || startedFromSetupPosition) {
13325         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13326         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13327         fprintf(f, "\n{--------------\n");
13328         PrintPosition(f, backwardMostMove);
13329         fprintf(f, "--------------}\n");
13330         free(fen);
13331     }
13332     else {
13333         /* [AS] Out of book annotation */
13334         if( appData.saveOutOfBookInfo ) {
13335             char buf[64];
13336
13337             GetOutOfBookInfo( buf );
13338
13339             if( buf[0] != '\0' ) {
13340                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13341             }
13342         }
13343
13344         fprintf(f, "\n");
13345     }
13346
13347     i = backwardMostMove;
13348     linelen = 0;
13349     newblock = TRUE;
13350
13351     while (i < forwardMostMove) {
13352         /* Print comments preceding this move */
13353         if (commentList[i] != NULL) {
13354             if (linelen > 0) fprintf(f, "\n");
13355             fprintf(f, "%s", commentList[i]);
13356             linelen = 0;
13357             newblock = TRUE;
13358         }
13359
13360         /* Format move number */
13361         if ((i % 2) == 0)
13362           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13363         else
13364           if (newblock)
13365             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13366           else
13367             numtext[0] = NULLCHAR;
13368
13369         numlen = strlen(numtext);
13370         newblock = FALSE;
13371
13372         /* Print move number */
13373         blank = linelen > 0 && numlen > 0;
13374         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13375             fprintf(f, "\n");
13376             linelen = 0;
13377             blank = 0;
13378         }
13379         if (blank) {
13380             fprintf(f, " ");
13381             linelen++;
13382         }
13383         fprintf(f, "%s", numtext);
13384         linelen += numlen;
13385
13386         /* Get move */
13387         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13388         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13389
13390         /* Print move */
13391         blank = linelen > 0 && movelen > 0;
13392         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13393             fprintf(f, "\n");
13394             linelen = 0;
13395             blank = 0;
13396         }
13397         if (blank) {
13398             fprintf(f, " ");
13399             linelen++;
13400         }
13401         fprintf(f, "%s", move_buffer);
13402         linelen += movelen;
13403
13404         /* [AS] Add PV info if present */
13405         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13406             /* [HGM] add time */
13407             char buf[MSG_SIZ]; int seconds;
13408
13409             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13410
13411             if( seconds <= 0)
13412               buf[0] = 0;
13413             else
13414               if( seconds < 30 )
13415                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13416               else
13417                 {
13418                   seconds = (seconds + 4)/10; // round to full seconds
13419                   if( seconds < 60 )
13420                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13421                   else
13422                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13423                 }
13424
13425             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13426                       pvInfoList[i].score >= 0 ? "+" : "",
13427                       pvInfoList[i].score / 100.0,
13428                       pvInfoList[i].depth,
13429                       buf );
13430
13431             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13432
13433             /* Print score/depth */
13434             blank = linelen > 0 && movelen > 0;
13435             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13436                 fprintf(f, "\n");
13437                 linelen = 0;
13438                 blank = 0;
13439             }
13440             if (blank) {
13441                 fprintf(f, " ");
13442                 linelen++;
13443             }
13444             fprintf(f, "%s", move_buffer);
13445             linelen += movelen;
13446         }
13447
13448         i++;
13449     }
13450
13451     /* Start a new line */
13452     if (linelen > 0) fprintf(f, "\n");
13453
13454     /* Print comments after last move */
13455     if (commentList[i] != NULL) {
13456         fprintf(f, "%s\n", commentList[i]);
13457     }
13458
13459     /* Print result */
13460     if (gameInfo.resultDetails != NULL &&
13461         gameInfo.resultDetails[0] != NULLCHAR) {
13462         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13463         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13464            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13465             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13466         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13467     } else {
13468         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13469     }
13470
13471     fclose(f);
13472     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13473     return TRUE;
13474 }
13475
13476 /* Save game in old style and close the file */
13477 int
13478 SaveGameOldStyle (FILE *f)
13479 {
13480     int i, offset;
13481     time_t tm;
13482
13483     tm = time((time_t *) NULL);
13484
13485     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13486     PrintOpponents(f);
13487
13488     if (backwardMostMove > 0 || startedFromSetupPosition) {
13489         fprintf(f, "\n[--------------\n");
13490         PrintPosition(f, backwardMostMove);
13491         fprintf(f, "--------------]\n");
13492     } else {
13493         fprintf(f, "\n");
13494     }
13495
13496     i = backwardMostMove;
13497     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13498
13499     while (i < forwardMostMove) {
13500         if (commentList[i] != NULL) {
13501             fprintf(f, "[%s]\n", commentList[i]);
13502         }
13503
13504         if ((i % 2) == 1) {
13505             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13506             i++;
13507         } else {
13508             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13509             i++;
13510             if (commentList[i] != NULL) {
13511                 fprintf(f, "\n");
13512                 continue;
13513             }
13514             if (i >= forwardMostMove) {
13515                 fprintf(f, "\n");
13516                 break;
13517             }
13518             fprintf(f, "%s\n", parseList[i]);
13519             i++;
13520         }
13521     }
13522
13523     if (commentList[i] != NULL) {
13524         fprintf(f, "[%s]\n", commentList[i]);
13525     }
13526
13527     /* This isn't really the old style, but it's close enough */
13528     if (gameInfo.resultDetails != NULL &&
13529         gameInfo.resultDetails[0] != NULLCHAR) {
13530         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13531                 gameInfo.resultDetails);
13532     } else {
13533         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13534     }
13535
13536     fclose(f);
13537     return TRUE;
13538 }
13539
13540 /* Save the current game to open file f and close the file */
13541 int
13542 SaveGame (FILE *f, int dummy, char *dummy2)
13543 {
13544     if (gameMode == EditPosition) EditPositionDone(TRUE);
13545     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13546     if (appData.oldSaveStyle)
13547       return SaveGameOldStyle(f);
13548     else
13549       return SaveGamePGN(f);
13550 }
13551
13552 /* Save the current position to the given file */
13553 int
13554 SavePositionToFile (char *filename)
13555 {
13556     FILE *f;
13557     char buf[MSG_SIZ];
13558
13559     if (strcmp(filename, "-") == 0) {
13560         return SavePosition(stdout, 0, NULL);
13561     } else {
13562         f = fopen(filename, "a");
13563         if (f == NULL) {
13564             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13565             DisplayError(buf, errno);
13566             return FALSE;
13567         } else {
13568             safeStrCpy(buf, lastMsg, MSG_SIZ);
13569             DisplayMessage(_("Waiting for access to save file"), "");
13570             flock(fileno(f), LOCK_EX); // [HGM] lock
13571             DisplayMessage(_("Saving position"), "");
13572             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13573             SavePosition(f, 0, NULL);
13574             DisplayMessage(buf, "");
13575             return TRUE;
13576         }
13577     }
13578 }
13579
13580 /* Save the current position to the given open file and close the file */
13581 int
13582 SavePosition (FILE *f, int dummy, char *dummy2)
13583 {
13584     time_t tm;
13585     char *fen;
13586
13587     if (gameMode == EditPosition) EditPositionDone(TRUE);
13588     if (appData.oldSaveStyle) {
13589         tm = time((time_t *) NULL);
13590
13591         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13592         PrintOpponents(f);
13593         fprintf(f, "[--------------\n");
13594         PrintPosition(f, currentMove);
13595         fprintf(f, "--------------]\n");
13596     } else {
13597         fen = PositionToFEN(currentMove, NULL, 1);
13598         fprintf(f, "%s\n", fen);
13599         free(fen);
13600     }
13601     fclose(f);
13602     return TRUE;
13603 }
13604
13605 void
13606 ReloadCmailMsgEvent (int unregister)
13607 {
13608 #if !WIN32
13609     static char *inFilename = NULL;
13610     static char *outFilename;
13611     int i;
13612     struct stat inbuf, outbuf;
13613     int status;
13614
13615     /* Any registered moves are unregistered if unregister is set, */
13616     /* i.e. invoked by the signal handler */
13617     if (unregister) {
13618         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13619             cmailMoveRegistered[i] = FALSE;
13620             if (cmailCommentList[i] != NULL) {
13621                 free(cmailCommentList[i]);
13622                 cmailCommentList[i] = NULL;
13623             }
13624         }
13625         nCmailMovesRegistered = 0;
13626     }
13627
13628     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13629         cmailResult[i] = CMAIL_NOT_RESULT;
13630     }
13631     nCmailResults = 0;
13632
13633     if (inFilename == NULL) {
13634         /* Because the filenames are static they only get malloced once  */
13635         /* and they never get freed                                      */
13636         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13637         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13638
13639         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13640         sprintf(outFilename, "%s.out", appData.cmailGameName);
13641     }
13642
13643     status = stat(outFilename, &outbuf);
13644     if (status < 0) {
13645         cmailMailedMove = FALSE;
13646     } else {
13647         status = stat(inFilename, &inbuf);
13648         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13649     }
13650
13651     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13652        counts the games, notes how each one terminated, etc.
13653
13654        It would be nice to remove this kludge and instead gather all
13655        the information while building the game list.  (And to keep it
13656        in the game list nodes instead of having a bunch of fixed-size
13657        parallel arrays.)  Note this will require getting each game's
13658        termination from the PGN tags, as the game list builder does
13659        not process the game moves.  --mann
13660        */
13661     cmailMsgLoaded = TRUE;
13662     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13663
13664     /* Load first game in the file or popup game menu */
13665     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13666
13667 #endif /* !WIN32 */
13668     return;
13669 }
13670
13671 int
13672 RegisterMove ()
13673 {
13674     FILE *f;
13675     char string[MSG_SIZ];
13676
13677     if (   cmailMailedMove
13678         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13679         return TRUE;            /* Allow free viewing  */
13680     }
13681
13682     /* Unregister move to ensure that we don't leave RegisterMove        */
13683     /* with the move registered when the conditions for registering no   */
13684     /* longer hold                                                       */
13685     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13686         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13687         nCmailMovesRegistered --;
13688
13689         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13690           {
13691               free(cmailCommentList[lastLoadGameNumber - 1]);
13692               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13693           }
13694     }
13695
13696     if (cmailOldMove == -1) {
13697         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13698         return FALSE;
13699     }
13700
13701     if (currentMove > cmailOldMove + 1) {
13702         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13703         return FALSE;
13704     }
13705
13706     if (currentMove < cmailOldMove) {
13707         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13708         return FALSE;
13709     }
13710
13711     if (forwardMostMove > currentMove) {
13712         /* Silently truncate extra moves */
13713         TruncateGame();
13714     }
13715
13716     if (   (currentMove == cmailOldMove + 1)
13717         || (   (currentMove == cmailOldMove)
13718             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13719                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13720         if (gameInfo.result != GameUnfinished) {
13721             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13722         }
13723
13724         if (commentList[currentMove] != NULL) {
13725             cmailCommentList[lastLoadGameNumber - 1]
13726               = StrSave(commentList[currentMove]);
13727         }
13728         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13729
13730         if (appData.debugMode)
13731           fprintf(debugFP, "Saving %s for game %d\n",
13732                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13733
13734         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13735
13736         f = fopen(string, "w");
13737         if (appData.oldSaveStyle) {
13738             SaveGameOldStyle(f); /* also closes the file */
13739
13740             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13741             f = fopen(string, "w");
13742             SavePosition(f, 0, NULL); /* also closes the file */
13743         } else {
13744             fprintf(f, "{--------------\n");
13745             PrintPosition(f, currentMove);
13746             fprintf(f, "--------------}\n\n");
13747
13748             SaveGame(f, 0, NULL); /* also closes the file*/
13749         }
13750
13751         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13752         nCmailMovesRegistered ++;
13753     } else if (nCmailGames == 1) {
13754         DisplayError(_("You have not made a move yet"), 0);
13755         return FALSE;
13756     }
13757
13758     return TRUE;
13759 }
13760
13761 void
13762 MailMoveEvent ()
13763 {
13764 #if !WIN32
13765     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13766     FILE *commandOutput;
13767     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13768     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13769     int nBuffers;
13770     int i;
13771     int archived;
13772     char *arcDir;
13773
13774     if (! cmailMsgLoaded) {
13775         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13776         return;
13777     }
13778
13779     if (nCmailGames == nCmailResults) {
13780         DisplayError(_("No unfinished games"), 0);
13781         return;
13782     }
13783
13784 #if CMAIL_PROHIBIT_REMAIL
13785     if (cmailMailedMove) {
13786       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);
13787         DisplayError(msg, 0);
13788         return;
13789     }
13790 #endif
13791
13792     if (! (cmailMailedMove || RegisterMove())) return;
13793
13794     if (   cmailMailedMove
13795         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13796       snprintf(string, MSG_SIZ, partCommandString,
13797                appData.debugMode ? " -v" : "", appData.cmailGameName);
13798         commandOutput = popen(string, "r");
13799
13800         if (commandOutput == NULL) {
13801             DisplayError(_("Failed to invoke cmail"), 0);
13802         } else {
13803             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13804                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13805             }
13806             if (nBuffers > 1) {
13807                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13808                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13809                 nBytes = MSG_SIZ - 1;
13810             } else {
13811                 (void) memcpy(msg, buffer, nBytes);
13812             }
13813             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13814
13815             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13816                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13817
13818                 archived = TRUE;
13819                 for (i = 0; i < nCmailGames; i ++) {
13820                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13821                         archived = FALSE;
13822                     }
13823                 }
13824                 if (   archived
13825                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13826                         != NULL)) {
13827                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13828                            arcDir,
13829                            appData.cmailGameName,
13830                            gameInfo.date);
13831                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13832                     cmailMsgLoaded = FALSE;
13833                 }
13834             }
13835
13836             DisplayInformation(msg);
13837             pclose(commandOutput);
13838         }
13839     } else {
13840         if ((*cmailMsg) != '\0') {
13841             DisplayInformation(cmailMsg);
13842         }
13843     }
13844
13845     return;
13846 #endif /* !WIN32 */
13847 }
13848
13849 char *
13850 CmailMsg ()
13851 {
13852 #if WIN32
13853     return NULL;
13854 #else
13855     int  prependComma = 0;
13856     char number[5];
13857     char string[MSG_SIZ];       /* Space for game-list */
13858     int  i;
13859
13860     if (!cmailMsgLoaded) return "";
13861
13862     if (cmailMailedMove) {
13863       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13864     } else {
13865         /* Create a list of games left */
13866       snprintf(string, MSG_SIZ, "[");
13867         for (i = 0; i < nCmailGames; i ++) {
13868             if (! (   cmailMoveRegistered[i]
13869                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13870                 if (prependComma) {
13871                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13872                 } else {
13873                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13874                     prependComma = 1;
13875                 }
13876
13877                 strcat(string, number);
13878             }
13879         }
13880         strcat(string, "]");
13881
13882         if (nCmailMovesRegistered + nCmailResults == 0) {
13883             switch (nCmailGames) {
13884               case 1:
13885                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13886                 break;
13887
13888               case 2:
13889                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13890                 break;
13891
13892               default:
13893                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13894                          nCmailGames);
13895                 break;
13896             }
13897         } else {
13898             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13899               case 1:
13900                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13901                          string);
13902                 break;
13903
13904               case 0:
13905                 if (nCmailResults == nCmailGames) {
13906                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13907                 } else {
13908                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13909                 }
13910                 break;
13911
13912               default:
13913                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13914                          string);
13915             }
13916         }
13917     }
13918     return cmailMsg;
13919 #endif /* WIN32 */
13920 }
13921
13922 void
13923 ResetGameEvent ()
13924 {
13925     if (gameMode == Training)
13926       SetTrainingModeOff();
13927
13928     Reset(TRUE, TRUE);
13929     cmailMsgLoaded = FALSE;
13930     if (appData.icsActive) {
13931       SendToICS(ics_prefix);
13932       SendToICS("refresh\n");
13933     }
13934 }
13935
13936 void
13937 ExitEvent (int status)
13938 {
13939     exiting++;
13940     if (exiting > 2) {
13941       /* Give up on clean exit */
13942       exit(status);
13943     }
13944     if (exiting > 1) {
13945       /* Keep trying for clean exit */
13946       return;
13947     }
13948
13949     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13950
13951     if (telnetISR != NULL) {
13952       RemoveInputSource(telnetISR);
13953     }
13954     if (icsPR != NoProc) {
13955       DestroyChildProcess(icsPR, TRUE);
13956     }
13957
13958     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13959     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13960
13961     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13962     /* make sure this other one finishes before killing it!                  */
13963     if(endingGame) { int count = 0;
13964         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13965         while(endingGame && count++ < 10) DoSleep(1);
13966         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13967     }
13968
13969     /* Kill off chess programs */
13970     if (first.pr != NoProc) {
13971         ExitAnalyzeMode();
13972
13973         DoSleep( appData.delayBeforeQuit );
13974         SendToProgram("quit\n", &first);
13975         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
13976     }
13977     if (second.pr != NoProc) {
13978         DoSleep( appData.delayBeforeQuit );
13979         SendToProgram("quit\n", &second);
13980         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
13981     }
13982     if (first.isr != NULL) {
13983         RemoveInputSource(first.isr);
13984     }
13985     if (second.isr != NULL) {
13986         RemoveInputSource(second.isr);
13987     }
13988
13989     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13990     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13991
13992     ShutDownFrontEnd();
13993     exit(status);
13994 }
13995
13996 void
13997 PauseEngine (ChessProgramState *cps)
13998 {
13999     SendToProgram("pause\n", cps);
14000     cps->pause = 2;
14001 }
14002
14003 void
14004 UnPauseEngine (ChessProgramState *cps)
14005 {
14006     SendToProgram("resume\n", cps);
14007     cps->pause = 1;
14008 }
14009
14010 void
14011 PauseEvent ()
14012 {
14013     if (appData.debugMode)
14014         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14015     if (pausing) {
14016         pausing = FALSE;
14017         ModeHighlight();
14018         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14019             StartClocks();
14020             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14021                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14022                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14023             }
14024             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14025             HandleMachineMove(stashedInputMove, stalledEngine);
14026             stalledEngine = NULL;
14027             return;
14028         }
14029         if (gameMode == MachinePlaysWhite ||
14030             gameMode == TwoMachinesPlay   ||
14031             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14032             if(first.pause)  UnPauseEngine(&first);
14033             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14034             if(second.pause) UnPauseEngine(&second);
14035             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14036             StartClocks();
14037         } else {
14038             DisplayBothClocks();
14039         }
14040         if (gameMode == PlayFromGameFile) {
14041             if (appData.timeDelay >= 0)
14042                 AutoPlayGameLoop();
14043         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14044             Reset(FALSE, TRUE);
14045             SendToICS(ics_prefix);
14046             SendToICS("refresh\n");
14047         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14048             ForwardInner(forwardMostMove);
14049         }
14050         pauseExamInvalid = FALSE;
14051     } else {
14052         switch (gameMode) {
14053           default:
14054             return;
14055           case IcsExamining:
14056             pauseExamForwardMostMove = forwardMostMove;
14057             pauseExamInvalid = FALSE;
14058             /* fall through */
14059           case IcsObserving:
14060           case IcsPlayingWhite:
14061           case IcsPlayingBlack:
14062             pausing = TRUE;
14063             ModeHighlight();
14064             return;
14065           case PlayFromGameFile:
14066             (void) StopLoadGameTimer();
14067             pausing = TRUE;
14068             ModeHighlight();
14069             break;
14070           case BeginningOfGame:
14071             if (appData.icsActive) return;
14072             /* else fall through */
14073           case MachinePlaysWhite:
14074           case MachinePlaysBlack:
14075           case TwoMachinesPlay:
14076             if (forwardMostMove == 0)
14077               return;           /* don't pause if no one has moved */
14078             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14079                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14080                 if(onMove->pause) {           // thinking engine can be paused
14081                     PauseEngine(onMove);      // do it
14082                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14083                         PauseEngine(onMove->other);
14084                     else
14085                         SendToProgram("easy\n", onMove->other);
14086                     StopClocks();
14087                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14088             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14089                 if(first.pause) {
14090                     PauseEngine(&first);
14091                     StopClocks();
14092                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14093             } else { // human on move, pause pondering by either method
14094                 if(first.pause)
14095                     PauseEngine(&first);
14096                 else if(appData.ponderNextMove)
14097                     SendToProgram("easy\n", &first);
14098                 StopClocks();
14099             }
14100             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14101           case AnalyzeMode:
14102             pausing = TRUE;
14103             ModeHighlight();
14104             break;
14105         }
14106     }
14107 }
14108
14109 void
14110 EditCommentEvent ()
14111 {
14112     char title[MSG_SIZ];
14113
14114     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14115       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14116     } else {
14117       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14118                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14119                parseList[currentMove - 1]);
14120     }
14121
14122     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14123 }
14124
14125
14126 void
14127 EditTagsEvent ()
14128 {
14129     char *tags = PGNTags(&gameInfo);
14130     bookUp = FALSE;
14131     EditTagsPopUp(tags, NULL);
14132     free(tags);
14133 }
14134
14135 void
14136 ToggleSecond ()
14137 {
14138   if(second.analyzing) {
14139     SendToProgram("exit\n", &second);
14140     second.analyzing = FALSE;
14141   } else {
14142     if (second.pr == NoProc) StartChessProgram(&second);
14143     InitChessProgram(&second, FALSE);
14144     FeedMovesToProgram(&second, currentMove);
14145
14146     SendToProgram("analyze\n", &second);
14147     second.analyzing = TRUE;
14148   }
14149 }
14150
14151 /* Toggle ShowThinking */
14152 void
14153 ToggleShowThinking()
14154 {
14155   appData.showThinking = !appData.showThinking;
14156   ShowThinkingEvent();
14157 }
14158
14159 int
14160 AnalyzeModeEvent ()
14161 {
14162     char buf[MSG_SIZ];
14163
14164     if (!first.analysisSupport) {
14165       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14166       DisplayError(buf, 0);
14167       return 0;
14168     }
14169     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14170     if (appData.icsActive) {
14171         if (gameMode != IcsObserving) {
14172           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14173             DisplayError(buf, 0);
14174             /* secure check */
14175             if (appData.icsEngineAnalyze) {
14176                 if (appData.debugMode)
14177                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14178                 ExitAnalyzeMode();
14179                 ModeHighlight();
14180             }
14181             return 0;
14182         }
14183         /* if enable, user wants to disable icsEngineAnalyze */
14184         if (appData.icsEngineAnalyze) {
14185                 ExitAnalyzeMode();
14186                 ModeHighlight();
14187                 return 0;
14188         }
14189         appData.icsEngineAnalyze = TRUE;
14190         if (appData.debugMode)
14191             fprintf(debugFP, "ICS engine analyze starting... \n");
14192     }
14193
14194     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14195     if (appData.noChessProgram || gameMode == AnalyzeMode)
14196       return 0;
14197
14198     if (gameMode != AnalyzeFile) {
14199         if (!appData.icsEngineAnalyze) {
14200                EditGameEvent();
14201                if (gameMode != EditGame) return 0;
14202         }
14203         if (!appData.showThinking) ToggleShowThinking();
14204         ResurrectChessProgram();
14205         SendToProgram("analyze\n", &first);
14206         first.analyzing = TRUE;
14207         /*first.maybeThinking = TRUE;*/
14208         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14209         EngineOutputPopUp();
14210     }
14211     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14212     pausing = FALSE;
14213     ModeHighlight();
14214     SetGameInfo();
14215
14216     StartAnalysisClock();
14217     GetTimeMark(&lastNodeCountTime);
14218     lastNodeCount = 0;
14219     return 1;
14220 }
14221
14222 void
14223 AnalyzeFileEvent ()
14224 {
14225     if (appData.noChessProgram || gameMode == AnalyzeFile)
14226       return;
14227
14228     if (!first.analysisSupport) {
14229       char buf[MSG_SIZ];
14230       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14231       DisplayError(buf, 0);
14232       return;
14233     }
14234
14235     if (gameMode != AnalyzeMode) {
14236         keepInfo = 1; // mere annotating should not alter PGN tags
14237         EditGameEvent();
14238         keepInfo = 0;
14239         if (gameMode != EditGame) return;
14240         if (!appData.showThinking) ToggleShowThinking();
14241         ResurrectChessProgram();
14242         SendToProgram("analyze\n", &first);
14243         first.analyzing = TRUE;
14244         /*first.maybeThinking = TRUE;*/
14245         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14246         EngineOutputPopUp();
14247     }
14248     gameMode = AnalyzeFile;
14249     pausing = FALSE;
14250     ModeHighlight();
14251
14252     StartAnalysisClock();
14253     GetTimeMark(&lastNodeCountTime);
14254     lastNodeCount = 0;
14255     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14256     AnalysisPeriodicEvent(1);
14257 }
14258
14259 void
14260 MachineWhiteEvent ()
14261 {
14262     char buf[MSG_SIZ];
14263     char *bookHit = NULL;
14264
14265     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14266       return;
14267
14268
14269     if (gameMode == PlayFromGameFile ||
14270         gameMode == TwoMachinesPlay  ||
14271         gameMode == Training         ||
14272         gameMode == AnalyzeMode      ||
14273         gameMode == EndOfGame)
14274         EditGameEvent();
14275
14276     if (gameMode == EditPosition)
14277         EditPositionDone(TRUE);
14278
14279     if (!WhiteOnMove(currentMove)) {
14280         DisplayError(_("It is not White's turn"), 0);
14281         return;
14282     }
14283
14284     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14285       ExitAnalyzeMode();
14286
14287     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14288         gameMode == AnalyzeFile)
14289         TruncateGame();
14290
14291     ResurrectChessProgram();    /* in case it isn't running */
14292     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14293         gameMode = MachinePlaysWhite;
14294         ResetClocks();
14295     } else
14296     gameMode = MachinePlaysWhite;
14297     pausing = FALSE;
14298     ModeHighlight();
14299     SetGameInfo();
14300     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14301     DisplayTitle(buf);
14302     if (first.sendName) {
14303       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14304       SendToProgram(buf, &first);
14305     }
14306     if (first.sendTime) {
14307       if (first.useColors) {
14308         SendToProgram("black\n", &first); /*gnu kludge*/
14309       }
14310       SendTimeRemaining(&first, TRUE);
14311     }
14312     if (first.useColors) {
14313       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14314     }
14315     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14316     SetMachineThinkingEnables();
14317     first.maybeThinking = TRUE;
14318     StartClocks();
14319     firstMove = FALSE;
14320
14321     if (appData.autoFlipView && !flipView) {
14322       flipView = !flipView;
14323       DrawPosition(FALSE, NULL);
14324       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14325     }
14326
14327     if(bookHit) { // [HGM] book: simulate book reply
14328         static char bookMove[MSG_SIZ]; // a bit generous?
14329
14330         programStats.nodes = programStats.depth = programStats.time =
14331         programStats.score = programStats.got_only_move = 0;
14332         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14333
14334         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14335         strcat(bookMove, bookHit);
14336         HandleMachineMove(bookMove, &first);
14337     }
14338 }
14339
14340 void
14341 MachineBlackEvent ()
14342 {
14343   char buf[MSG_SIZ];
14344   char *bookHit = NULL;
14345
14346     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14347         return;
14348
14349
14350     if (gameMode == PlayFromGameFile ||
14351         gameMode == TwoMachinesPlay  ||
14352         gameMode == Training         ||
14353         gameMode == AnalyzeMode      ||
14354         gameMode == EndOfGame)
14355         EditGameEvent();
14356
14357     if (gameMode == EditPosition)
14358         EditPositionDone(TRUE);
14359
14360     if (WhiteOnMove(currentMove)) {
14361         DisplayError(_("It is not Black's turn"), 0);
14362         return;
14363     }
14364
14365     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14366       ExitAnalyzeMode();
14367
14368     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14369         gameMode == AnalyzeFile)
14370         TruncateGame();
14371
14372     ResurrectChessProgram();    /* in case it isn't running */
14373     gameMode = MachinePlaysBlack;
14374     pausing = FALSE;
14375     ModeHighlight();
14376     SetGameInfo();
14377     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14378     DisplayTitle(buf);
14379     if (first.sendName) {
14380       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14381       SendToProgram(buf, &first);
14382     }
14383     if (first.sendTime) {
14384       if (first.useColors) {
14385         SendToProgram("white\n", &first); /*gnu kludge*/
14386       }
14387       SendTimeRemaining(&first, FALSE);
14388     }
14389     if (first.useColors) {
14390       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14391     }
14392     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14393     SetMachineThinkingEnables();
14394     first.maybeThinking = TRUE;
14395     StartClocks();
14396
14397     if (appData.autoFlipView && flipView) {
14398       flipView = !flipView;
14399       DrawPosition(FALSE, NULL);
14400       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14401     }
14402     if(bookHit) { // [HGM] book: simulate book reply
14403         static char bookMove[MSG_SIZ]; // a bit generous?
14404
14405         programStats.nodes = programStats.depth = programStats.time =
14406         programStats.score = programStats.got_only_move = 0;
14407         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14408
14409         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14410         strcat(bookMove, bookHit);
14411         HandleMachineMove(bookMove, &first);
14412     }
14413 }
14414
14415
14416 void
14417 DisplayTwoMachinesTitle ()
14418 {
14419     char buf[MSG_SIZ];
14420     if (appData.matchGames > 0) {
14421         if(appData.tourneyFile[0]) {
14422           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14423                    gameInfo.white, _("vs."), gameInfo.black,
14424                    nextGame+1, appData.matchGames+1,
14425                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14426         } else
14427         if (first.twoMachinesColor[0] == 'w') {
14428           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14429                    gameInfo.white, _("vs."),  gameInfo.black,
14430                    first.matchWins, second.matchWins,
14431                    matchGame - 1 - (first.matchWins + second.matchWins));
14432         } else {
14433           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14434                    gameInfo.white, _("vs."), gameInfo.black,
14435                    second.matchWins, first.matchWins,
14436                    matchGame - 1 - (first.matchWins + second.matchWins));
14437         }
14438     } else {
14439       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14440     }
14441     DisplayTitle(buf);
14442 }
14443
14444 void
14445 SettingsMenuIfReady ()
14446 {
14447   if (second.lastPing != second.lastPong) {
14448     DisplayMessage("", _("Waiting for second chess program"));
14449     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14450     return;
14451   }
14452   ThawUI();
14453   DisplayMessage("", "");
14454   SettingsPopUp(&second);
14455 }
14456
14457 int
14458 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14459 {
14460     char buf[MSG_SIZ];
14461     if (cps->pr == NoProc) {
14462         StartChessProgram(cps);
14463         if (cps->protocolVersion == 1) {
14464           retry();
14465           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14466         } else {
14467           /* kludge: allow timeout for initial "feature" command */
14468           if(retry != TwoMachinesEventIfReady) FreezeUI();
14469           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14470           DisplayMessage("", buf);
14471           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14472         }
14473         return 1;
14474     }
14475     return 0;
14476 }
14477
14478 void
14479 TwoMachinesEvent P((void))
14480 {
14481     int i;
14482     char buf[MSG_SIZ];
14483     ChessProgramState *onmove;
14484     char *bookHit = NULL;
14485     static int stalling = 0;
14486     TimeMark now;
14487     long wait;
14488
14489     if (appData.noChessProgram) return;
14490
14491     switch (gameMode) {
14492       case TwoMachinesPlay:
14493         return;
14494       case MachinePlaysWhite:
14495       case MachinePlaysBlack:
14496         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14497             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14498             return;
14499         }
14500         /* fall through */
14501       case BeginningOfGame:
14502       case PlayFromGameFile:
14503       case EndOfGame:
14504         EditGameEvent();
14505         if (gameMode != EditGame) return;
14506         break;
14507       case EditPosition:
14508         EditPositionDone(TRUE);
14509         break;
14510       case AnalyzeMode:
14511       case AnalyzeFile:
14512         ExitAnalyzeMode();
14513         break;
14514       case EditGame:
14515       default:
14516         break;
14517     }
14518
14519 //    forwardMostMove = currentMove;
14520     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14521     startingEngine = TRUE;
14522
14523     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14524
14525     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14526     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14527       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14528       return;
14529     }
14530     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14531
14532     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14533                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14534         startingEngine = FALSE;
14535         DisplayError("second engine does not play this", 0);
14536         return;
14537     }
14538
14539     if(!stalling) {
14540       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14541       SendToProgram("force\n", &second);
14542       stalling = 1;
14543       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14544       return;
14545     }
14546     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14547     if(appData.matchPause>10000 || appData.matchPause<10)
14548                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14549     wait = SubtractTimeMarks(&now, &pauseStart);
14550     if(wait < appData.matchPause) {
14551         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14552         return;
14553     }
14554     // we are now committed to starting the game
14555     stalling = 0;
14556     DisplayMessage("", "");
14557     if (startedFromSetupPosition) {
14558         SendBoard(&second, backwardMostMove);
14559     if (appData.debugMode) {
14560         fprintf(debugFP, "Two Machines\n");
14561     }
14562     }
14563     for (i = backwardMostMove; i < forwardMostMove; i++) {
14564         SendMoveToProgram(i, &second);
14565     }
14566
14567     gameMode = TwoMachinesPlay;
14568     pausing = startingEngine = FALSE;
14569     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14570     SetGameInfo();
14571     DisplayTwoMachinesTitle();
14572     firstMove = TRUE;
14573     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14574         onmove = &first;
14575     } else {
14576         onmove = &second;
14577     }
14578     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14579     SendToProgram(first.computerString, &first);
14580     if (first.sendName) {
14581       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14582       SendToProgram(buf, &first);
14583     }
14584     SendToProgram(second.computerString, &second);
14585     if (second.sendName) {
14586       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14587       SendToProgram(buf, &second);
14588     }
14589
14590     ResetClocks();
14591     if (!first.sendTime || !second.sendTime) {
14592         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14593         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14594     }
14595     if (onmove->sendTime) {
14596       if (onmove->useColors) {
14597         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14598       }
14599       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14600     }
14601     if (onmove->useColors) {
14602       SendToProgram(onmove->twoMachinesColor, onmove);
14603     }
14604     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14605 //    SendToProgram("go\n", onmove);
14606     onmove->maybeThinking = TRUE;
14607     SetMachineThinkingEnables();
14608
14609     StartClocks();
14610
14611     if(bookHit) { // [HGM] book: simulate book reply
14612         static char bookMove[MSG_SIZ]; // a bit generous?
14613
14614         programStats.nodes = programStats.depth = programStats.time =
14615         programStats.score = programStats.got_only_move = 0;
14616         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14617
14618         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14619         strcat(bookMove, bookHit);
14620         savedMessage = bookMove; // args for deferred call
14621         savedState = onmove;
14622         ScheduleDelayedEvent(DeferredBookMove, 1);
14623     }
14624 }
14625
14626 void
14627 TrainingEvent ()
14628 {
14629     if (gameMode == Training) {
14630       SetTrainingModeOff();
14631       gameMode = PlayFromGameFile;
14632       DisplayMessage("", _("Training mode off"));
14633     } else {
14634       gameMode = Training;
14635       animateTraining = appData.animate;
14636
14637       /* make sure we are not already at the end of the game */
14638       if (currentMove < forwardMostMove) {
14639         SetTrainingModeOn();
14640         DisplayMessage("", _("Training mode on"));
14641       } else {
14642         gameMode = PlayFromGameFile;
14643         DisplayError(_("Already at end of game"), 0);
14644       }
14645     }
14646     ModeHighlight();
14647 }
14648
14649 void
14650 IcsClientEvent ()
14651 {
14652     if (!appData.icsActive) return;
14653     switch (gameMode) {
14654       case IcsPlayingWhite:
14655       case IcsPlayingBlack:
14656       case IcsObserving:
14657       case IcsIdle:
14658       case BeginningOfGame:
14659       case IcsExamining:
14660         return;
14661
14662       case EditGame:
14663         break;
14664
14665       case EditPosition:
14666         EditPositionDone(TRUE);
14667         break;
14668
14669       case AnalyzeMode:
14670       case AnalyzeFile:
14671         ExitAnalyzeMode();
14672         break;
14673
14674       default:
14675         EditGameEvent();
14676         break;
14677     }
14678
14679     gameMode = IcsIdle;
14680     ModeHighlight();
14681     return;
14682 }
14683
14684 void
14685 EditGameEvent ()
14686 {
14687     int i;
14688
14689     switch (gameMode) {
14690       case Training:
14691         SetTrainingModeOff();
14692         break;
14693       case MachinePlaysWhite:
14694       case MachinePlaysBlack:
14695       case BeginningOfGame:
14696         SendToProgram("force\n", &first);
14697         SetUserThinkingEnables();
14698         break;
14699       case PlayFromGameFile:
14700         (void) StopLoadGameTimer();
14701         if (gameFileFP != NULL) {
14702             gameFileFP = NULL;
14703         }
14704         break;
14705       case EditPosition:
14706         EditPositionDone(TRUE);
14707         break;
14708       case AnalyzeMode:
14709       case AnalyzeFile:
14710         ExitAnalyzeMode();
14711         SendToProgram("force\n", &first);
14712         break;
14713       case TwoMachinesPlay:
14714         GameEnds(EndOfFile, NULL, GE_PLAYER);
14715         ResurrectChessProgram();
14716         SetUserThinkingEnables();
14717         break;
14718       case EndOfGame:
14719         ResurrectChessProgram();
14720         break;
14721       case IcsPlayingBlack:
14722       case IcsPlayingWhite:
14723         DisplayError(_("Warning: You are still playing a game"), 0);
14724         break;
14725       case IcsObserving:
14726         DisplayError(_("Warning: You are still observing a game"), 0);
14727         break;
14728       case IcsExamining:
14729         DisplayError(_("Warning: You are still examining a game"), 0);
14730         break;
14731       case IcsIdle:
14732         break;
14733       case EditGame:
14734       default:
14735         return;
14736     }
14737
14738     pausing = FALSE;
14739     StopClocks();
14740     first.offeredDraw = second.offeredDraw = 0;
14741
14742     if (gameMode == PlayFromGameFile) {
14743         whiteTimeRemaining = timeRemaining[0][currentMove];
14744         blackTimeRemaining = timeRemaining[1][currentMove];
14745         DisplayTitle("");
14746     }
14747
14748     if (gameMode == MachinePlaysWhite ||
14749         gameMode == MachinePlaysBlack ||
14750         gameMode == TwoMachinesPlay ||
14751         gameMode == EndOfGame) {
14752         i = forwardMostMove;
14753         while (i > currentMove) {
14754             SendToProgram("undo\n", &first);
14755             i--;
14756         }
14757         if(!adjustedClock) {
14758         whiteTimeRemaining = timeRemaining[0][currentMove];
14759         blackTimeRemaining = timeRemaining[1][currentMove];
14760         DisplayBothClocks();
14761         }
14762         if (whiteFlag || blackFlag) {
14763             whiteFlag = blackFlag = 0;
14764         }
14765         DisplayTitle("");
14766     }
14767
14768     gameMode = EditGame;
14769     ModeHighlight();
14770     SetGameInfo();
14771 }
14772
14773
14774 void
14775 EditPositionEvent ()
14776 {
14777     if (gameMode == EditPosition) {
14778         EditGameEvent();
14779         return;
14780     }
14781
14782     EditGameEvent();
14783     if (gameMode != EditGame) return;
14784
14785     gameMode = EditPosition;
14786     ModeHighlight();
14787     SetGameInfo();
14788     if (currentMove > 0)
14789       CopyBoard(boards[0], boards[currentMove]);
14790
14791     blackPlaysFirst = !WhiteOnMove(currentMove);
14792     ResetClocks();
14793     currentMove = forwardMostMove = backwardMostMove = 0;
14794     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14795     DisplayMove(-1);
14796     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14797 }
14798
14799 void
14800 ExitAnalyzeMode ()
14801 {
14802     /* [DM] icsEngineAnalyze - possible call from other functions */
14803     if (appData.icsEngineAnalyze) {
14804         appData.icsEngineAnalyze = FALSE;
14805
14806         DisplayMessage("",_("Close ICS engine analyze..."));
14807     }
14808     if (first.analysisSupport && first.analyzing) {
14809       SendToBoth("exit\n");
14810       first.analyzing = second.analyzing = FALSE;
14811     }
14812     thinkOutput[0] = NULLCHAR;
14813 }
14814
14815 void
14816 EditPositionDone (Boolean fakeRights)
14817 {
14818     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14819
14820     startedFromSetupPosition = TRUE;
14821     InitChessProgram(&first, FALSE);
14822     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14823       boards[0][EP_STATUS] = EP_NONE;
14824       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14825       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14826         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14827         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14828       } else boards[0][CASTLING][2] = NoRights;
14829       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14830         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14831         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14832       } else boards[0][CASTLING][5] = NoRights;
14833       if(gameInfo.variant == VariantSChess) {
14834         int i;
14835         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14836           boards[0][VIRGIN][i] = 0;
14837           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14838           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14839         }
14840       }
14841     }
14842     SendToProgram("force\n", &first);
14843     if (blackPlaysFirst) {
14844         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14845         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14846         currentMove = forwardMostMove = backwardMostMove = 1;
14847         CopyBoard(boards[1], boards[0]);
14848     } else {
14849         currentMove = forwardMostMove = backwardMostMove = 0;
14850     }
14851     SendBoard(&first, forwardMostMove);
14852     if (appData.debugMode) {
14853         fprintf(debugFP, "EditPosDone\n");
14854     }
14855     DisplayTitle("");
14856     DisplayMessage("", "");
14857     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14858     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14859     gameMode = EditGame;
14860     ModeHighlight();
14861     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14862     ClearHighlights(); /* [AS] */
14863 }
14864
14865 /* Pause for `ms' milliseconds */
14866 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14867 void
14868 TimeDelay (long ms)
14869 {
14870     TimeMark m1, m2;
14871
14872     GetTimeMark(&m1);
14873     do {
14874         GetTimeMark(&m2);
14875     } while (SubtractTimeMarks(&m2, &m1) < ms);
14876 }
14877
14878 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14879 void
14880 SendMultiLineToICS (char *buf)
14881 {
14882     char temp[MSG_SIZ+1], *p;
14883     int len;
14884
14885     len = strlen(buf);
14886     if (len > MSG_SIZ)
14887       len = MSG_SIZ;
14888
14889     strncpy(temp, buf, len);
14890     temp[len] = 0;
14891
14892     p = temp;
14893     while (*p) {
14894         if (*p == '\n' || *p == '\r')
14895           *p = ' ';
14896         ++p;
14897     }
14898
14899     strcat(temp, "\n");
14900     SendToICS(temp);
14901     SendToPlayer(temp, strlen(temp));
14902 }
14903
14904 void
14905 SetWhiteToPlayEvent ()
14906 {
14907     if (gameMode == EditPosition) {
14908         blackPlaysFirst = FALSE;
14909         DisplayBothClocks();    /* works because currentMove is 0 */
14910     } else if (gameMode == IcsExamining) {
14911         SendToICS(ics_prefix);
14912         SendToICS("tomove white\n");
14913     }
14914 }
14915
14916 void
14917 SetBlackToPlayEvent ()
14918 {
14919     if (gameMode == EditPosition) {
14920         blackPlaysFirst = TRUE;
14921         currentMove = 1;        /* kludge */
14922         DisplayBothClocks();
14923         currentMove = 0;
14924     } else if (gameMode == IcsExamining) {
14925         SendToICS(ics_prefix);
14926         SendToICS("tomove black\n");
14927     }
14928 }
14929
14930 void
14931 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14932 {
14933     char buf[MSG_SIZ];
14934     ChessSquare piece = boards[0][y][x];
14935     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14936     static int lastVariant;
14937
14938     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14939
14940     switch (selection) {
14941       case ClearBoard:
14942         CopyBoard(currentBoard, boards[0]);
14943         CopyBoard(menuBoard, initialPosition);
14944         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14945             SendToICS(ics_prefix);
14946             SendToICS("bsetup clear\n");
14947         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14948             SendToICS(ics_prefix);
14949             SendToICS("clearboard\n");
14950         } else {
14951             int nonEmpty = 0;
14952             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14953                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14954                 for (y = 0; y < BOARD_HEIGHT; y++) {
14955                     if (gameMode == IcsExamining) {
14956                         if (boards[currentMove][y][x] != EmptySquare) {
14957                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14958                                     AAA + x, ONE + y);
14959                             SendToICS(buf);
14960                         }
14961                     } else {
14962                         if(boards[0][y][x] != p) nonEmpty++;
14963                         boards[0][y][x] = p;
14964                     }
14965                 }
14966                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14967             }
14968             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14969                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14970                     ChessSquare p = menuBoard[0][x];
14971                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14972                     p = menuBoard[BOARD_HEIGHT-1][x];
14973                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14974                 }
14975                 DisplayMessage("Clicking clock again restores position", "");
14976                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14977                 if(!nonEmpty) { // asked to clear an empty board
14978                     CopyBoard(boards[0], menuBoard);
14979                 } else
14980                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14981                     CopyBoard(boards[0], initialPosition);
14982                 } else
14983                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14984                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14985                     CopyBoard(boards[0], erasedBoard);
14986                 } else
14987                     CopyBoard(erasedBoard, currentBoard);
14988
14989             }
14990         }
14991         if (gameMode == EditPosition) {
14992             DrawPosition(FALSE, boards[0]);
14993         }
14994         break;
14995
14996       case WhitePlay:
14997         SetWhiteToPlayEvent();
14998         break;
14999
15000       case BlackPlay:
15001         SetBlackToPlayEvent();
15002         break;
15003
15004       case EmptySquare:
15005         if (gameMode == IcsExamining) {
15006             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15007             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15008             SendToICS(buf);
15009         } else {
15010             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15011                 if(x == BOARD_LEFT-2) {
15012                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15013                     boards[0][y][1] = 0;
15014                 } else
15015                 if(x == BOARD_RGHT+1) {
15016                     if(y >= gameInfo.holdingsSize) break;
15017                     boards[0][y][BOARD_WIDTH-2] = 0;
15018                 } else break;
15019             }
15020             boards[0][y][x] = EmptySquare;
15021             DrawPosition(FALSE, boards[0]);
15022         }
15023         break;
15024
15025       case PromotePiece:
15026         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15027            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15028             selection = (ChessSquare) (PROMOTED piece);
15029         } else if(piece == EmptySquare) selection = WhiteSilver;
15030         else selection = (ChessSquare)((int)piece - 1);
15031         goto defaultlabel;
15032
15033       case DemotePiece:
15034         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15035            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15036             selection = (ChessSquare) (DEMOTED piece);
15037         } else if(piece == EmptySquare) selection = BlackSilver;
15038         else selection = (ChessSquare)((int)piece + 1);
15039         goto defaultlabel;
15040
15041       case WhiteQueen:
15042       case BlackQueen:
15043         if(gameInfo.variant == VariantShatranj ||
15044            gameInfo.variant == VariantXiangqi  ||
15045            gameInfo.variant == VariantCourier  ||
15046            gameInfo.variant == VariantASEAN    ||
15047            gameInfo.variant == VariantMakruk     )
15048             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15049         goto defaultlabel;
15050
15051       case WhiteKing:
15052       case BlackKing:
15053         if(gameInfo.variant == VariantXiangqi)
15054             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15055         if(gameInfo.variant == VariantKnightmate)
15056             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15057       default:
15058         defaultlabel:
15059         if (gameMode == IcsExamining) {
15060             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15061             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15062                      PieceToChar(selection), AAA + x, ONE + y);
15063             SendToICS(buf);
15064         } else {
15065             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15066                 int n;
15067                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15068                     n = PieceToNumber(selection - BlackPawn);
15069                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15070                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15071                     boards[0][BOARD_HEIGHT-1-n][1]++;
15072                 } else
15073                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15074                     n = PieceToNumber(selection);
15075                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15076                     boards[0][n][BOARD_WIDTH-1] = selection;
15077                     boards[0][n][BOARD_WIDTH-2]++;
15078                 }
15079             } else
15080             boards[0][y][x] = selection;
15081             DrawPosition(TRUE, boards[0]);
15082             ClearHighlights();
15083             fromX = fromY = -1;
15084         }
15085         break;
15086     }
15087 }
15088
15089
15090 void
15091 DropMenuEvent (ChessSquare selection, int x, int y)
15092 {
15093     ChessMove moveType;
15094
15095     switch (gameMode) {
15096       case IcsPlayingWhite:
15097       case MachinePlaysBlack:
15098         if (!WhiteOnMove(currentMove)) {
15099             DisplayMoveError(_("It is Black's turn"));
15100             return;
15101         }
15102         moveType = WhiteDrop;
15103         break;
15104       case IcsPlayingBlack:
15105       case MachinePlaysWhite:
15106         if (WhiteOnMove(currentMove)) {
15107             DisplayMoveError(_("It is White's turn"));
15108             return;
15109         }
15110         moveType = BlackDrop;
15111         break;
15112       case EditGame:
15113         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15114         break;
15115       default:
15116         return;
15117     }
15118
15119     if (moveType == BlackDrop && selection < BlackPawn) {
15120       selection = (ChessSquare) ((int) selection
15121                                  + (int) BlackPawn - (int) WhitePawn);
15122     }
15123     if (boards[currentMove][y][x] != EmptySquare) {
15124         DisplayMoveError(_("That square is occupied"));
15125         return;
15126     }
15127
15128     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15129 }
15130
15131 void
15132 AcceptEvent ()
15133 {
15134     /* Accept a pending offer of any kind from opponent */
15135
15136     if (appData.icsActive) {
15137         SendToICS(ics_prefix);
15138         SendToICS("accept\n");
15139     } else if (cmailMsgLoaded) {
15140         if (currentMove == cmailOldMove &&
15141             commentList[cmailOldMove] != NULL &&
15142             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15143                    "Black offers a draw" : "White offers a draw")) {
15144             TruncateGame();
15145             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15146             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15147         } else {
15148             DisplayError(_("There is no pending offer on this move"), 0);
15149             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15150         }
15151     } else {
15152         /* Not used for offers from chess program */
15153     }
15154 }
15155
15156 void
15157 DeclineEvent ()
15158 {
15159     /* Decline a pending offer of any kind from opponent */
15160
15161     if (appData.icsActive) {
15162         SendToICS(ics_prefix);
15163         SendToICS("decline\n");
15164     } else if (cmailMsgLoaded) {
15165         if (currentMove == cmailOldMove &&
15166             commentList[cmailOldMove] != NULL &&
15167             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15168                    "Black offers a draw" : "White offers a draw")) {
15169 #ifdef NOTDEF
15170             AppendComment(cmailOldMove, "Draw declined", TRUE);
15171             DisplayComment(cmailOldMove - 1, "Draw declined");
15172 #endif /*NOTDEF*/
15173         } else {
15174             DisplayError(_("There is no pending offer on this move"), 0);
15175         }
15176     } else {
15177         /* Not used for offers from chess program */
15178     }
15179 }
15180
15181 void
15182 RematchEvent ()
15183 {
15184     /* Issue ICS rematch command */
15185     if (appData.icsActive) {
15186         SendToICS(ics_prefix);
15187         SendToICS("rematch\n");
15188     }
15189 }
15190
15191 void
15192 CallFlagEvent ()
15193 {
15194     /* Call your opponent's flag (claim a win on time) */
15195     if (appData.icsActive) {
15196         SendToICS(ics_prefix);
15197         SendToICS("flag\n");
15198     } else {
15199         switch (gameMode) {
15200           default:
15201             return;
15202           case MachinePlaysWhite:
15203             if (whiteFlag) {
15204                 if (blackFlag)
15205                   GameEnds(GameIsDrawn, "Both players ran out of time",
15206                            GE_PLAYER);
15207                 else
15208                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15209             } else {
15210                 DisplayError(_("Your opponent is not out of time"), 0);
15211             }
15212             break;
15213           case MachinePlaysBlack:
15214             if (blackFlag) {
15215                 if (whiteFlag)
15216                   GameEnds(GameIsDrawn, "Both players ran out of time",
15217                            GE_PLAYER);
15218                 else
15219                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15220             } else {
15221                 DisplayError(_("Your opponent is not out of time"), 0);
15222             }
15223             break;
15224         }
15225     }
15226 }
15227
15228 void
15229 ClockClick (int which)
15230 {       // [HGM] code moved to back-end from winboard.c
15231         if(which) { // black clock
15232           if (gameMode == EditPosition || gameMode == IcsExamining) {
15233             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15234             SetBlackToPlayEvent();
15235           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15236           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15237           } else if (shiftKey) {
15238             AdjustClock(which, -1);
15239           } else if (gameMode == IcsPlayingWhite ||
15240                      gameMode == MachinePlaysBlack) {
15241             CallFlagEvent();
15242           }
15243         } else { // white clock
15244           if (gameMode == EditPosition || gameMode == IcsExamining) {
15245             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15246             SetWhiteToPlayEvent();
15247           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15248           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15249           } else if (shiftKey) {
15250             AdjustClock(which, -1);
15251           } else if (gameMode == IcsPlayingBlack ||
15252                    gameMode == MachinePlaysWhite) {
15253             CallFlagEvent();
15254           }
15255         }
15256 }
15257
15258 void
15259 DrawEvent ()
15260 {
15261     /* Offer draw or accept pending draw offer from opponent */
15262
15263     if (appData.icsActive) {
15264         /* Note: tournament rules require draw offers to be
15265            made after you make your move but before you punch
15266            your clock.  Currently ICS doesn't let you do that;
15267            instead, you immediately punch your clock after making
15268            a move, but you can offer a draw at any time. */
15269
15270         SendToICS(ics_prefix);
15271         SendToICS("draw\n");
15272         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15273     } else if (cmailMsgLoaded) {
15274         if (currentMove == cmailOldMove &&
15275             commentList[cmailOldMove] != NULL &&
15276             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15277                    "Black offers a draw" : "White offers a draw")) {
15278             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15279             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15280         } else if (currentMove == cmailOldMove + 1) {
15281             char *offer = WhiteOnMove(cmailOldMove) ?
15282               "White offers a draw" : "Black offers a draw";
15283             AppendComment(currentMove, offer, TRUE);
15284             DisplayComment(currentMove - 1, offer);
15285             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15286         } else {
15287             DisplayError(_("You must make your move before offering a draw"), 0);
15288             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15289         }
15290     } else if (first.offeredDraw) {
15291         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15292     } else {
15293         if (first.sendDrawOffers) {
15294             SendToProgram("draw\n", &first);
15295             userOfferedDraw = TRUE;
15296         }
15297     }
15298 }
15299
15300 void
15301 AdjournEvent ()
15302 {
15303     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15304
15305     if (appData.icsActive) {
15306         SendToICS(ics_prefix);
15307         SendToICS("adjourn\n");
15308     } else {
15309         /* Currently GNU Chess doesn't offer or accept Adjourns */
15310     }
15311 }
15312
15313
15314 void
15315 AbortEvent ()
15316 {
15317     /* Offer Abort or accept pending Abort offer from opponent */
15318
15319     if (appData.icsActive) {
15320         SendToICS(ics_prefix);
15321         SendToICS("abort\n");
15322     } else {
15323         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15324     }
15325 }
15326
15327 void
15328 ResignEvent ()
15329 {
15330     /* Resign.  You can do this even if it's not your turn. */
15331
15332     if (appData.icsActive) {
15333         SendToICS(ics_prefix);
15334         SendToICS("resign\n");
15335     } else {
15336         switch (gameMode) {
15337           case MachinePlaysWhite:
15338             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15339             break;
15340           case MachinePlaysBlack:
15341             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15342             break;
15343           case EditGame:
15344             if (cmailMsgLoaded) {
15345                 TruncateGame();
15346                 if (WhiteOnMove(cmailOldMove)) {
15347                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15348                 } else {
15349                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15350                 }
15351                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15352             }
15353             break;
15354           default:
15355             break;
15356         }
15357     }
15358 }
15359
15360
15361 void
15362 StopObservingEvent ()
15363 {
15364     /* Stop observing current games */
15365     SendToICS(ics_prefix);
15366     SendToICS("unobserve\n");
15367 }
15368
15369 void
15370 StopExaminingEvent ()
15371 {
15372     /* Stop observing current game */
15373     SendToICS(ics_prefix);
15374     SendToICS("unexamine\n");
15375 }
15376
15377 void
15378 ForwardInner (int target)
15379 {
15380     int limit; int oldSeekGraphUp = seekGraphUp;
15381
15382     if (appData.debugMode)
15383         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15384                 target, currentMove, forwardMostMove);
15385
15386     if (gameMode == EditPosition)
15387       return;
15388
15389     seekGraphUp = FALSE;
15390     MarkTargetSquares(1);
15391
15392     if (gameMode == PlayFromGameFile && !pausing)
15393       PauseEvent();
15394
15395     if (gameMode == IcsExamining && pausing)
15396       limit = pauseExamForwardMostMove;
15397     else
15398       limit = forwardMostMove;
15399
15400     if (target > limit) target = limit;
15401
15402     if (target > 0 && moveList[target - 1][0]) {
15403         int fromX, fromY, toX, toY;
15404         toX = moveList[target - 1][2] - AAA;
15405         toY = moveList[target - 1][3] - ONE;
15406         if (moveList[target - 1][1] == '@') {
15407             if (appData.highlightLastMove) {
15408                 SetHighlights(-1, -1, toX, toY);
15409             }
15410         } else {
15411             fromX = moveList[target - 1][0] - AAA;
15412             fromY = moveList[target - 1][1] - ONE;
15413             if (target == currentMove + 1) {
15414                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15415             }
15416             if (appData.highlightLastMove) {
15417                 SetHighlights(fromX, fromY, toX, toY);
15418             }
15419         }
15420     }
15421     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15422         gameMode == Training || gameMode == PlayFromGameFile ||
15423         gameMode == AnalyzeFile) {
15424         while (currentMove < target) {
15425             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15426             SendMoveToProgram(currentMove++, &first);
15427         }
15428     } else {
15429         currentMove = target;
15430     }
15431
15432     if (gameMode == EditGame || gameMode == EndOfGame) {
15433         whiteTimeRemaining = timeRemaining[0][currentMove];
15434         blackTimeRemaining = timeRemaining[1][currentMove];
15435     }
15436     DisplayBothClocks();
15437     DisplayMove(currentMove - 1);
15438     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15439     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15440     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15441         DisplayComment(currentMove - 1, commentList[currentMove]);
15442     }
15443     ClearMap(); // [HGM] exclude: invalidate map
15444 }
15445
15446
15447 void
15448 ForwardEvent ()
15449 {
15450     if (gameMode == IcsExamining && !pausing) {
15451         SendToICS(ics_prefix);
15452         SendToICS("forward\n");
15453     } else {
15454         ForwardInner(currentMove + 1);
15455     }
15456 }
15457
15458 void
15459 ToEndEvent ()
15460 {
15461     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15462         /* to optimze, we temporarily turn off analysis mode while we feed
15463          * the remaining moves to the engine. Otherwise we get analysis output
15464          * after each move.
15465          */
15466         if (first.analysisSupport) {
15467           SendToProgram("exit\nforce\n", &first);
15468           first.analyzing = FALSE;
15469         }
15470     }
15471
15472     if (gameMode == IcsExamining && !pausing) {
15473         SendToICS(ics_prefix);
15474         SendToICS("forward 999999\n");
15475     } else {
15476         ForwardInner(forwardMostMove);
15477     }
15478
15479     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15480         /* we have fed all the moves, so reactivate analysis mode */
15481         SendToProgram("analyze\n", &first);
15482         first.analyzing = TRUE;
15483         /*first.maybeThinking = TRUE;*/
15484         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15485     }
15486 }
15487
15488 void
15489 BackwardInner (int target)
15490 {
15491     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15492
15493     if (appData.debugMode)
15494         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15495                 target, currentMove, forwardMostMove);
15496
15497     if (gameMode == EditPosition) return;
15498     seekGraphUp = FALSE;
15499     MarkTargetSquares(1);
15500     if (currentMove <= backwardMostMove) {
15501         ClearHighlights();
15502         DrawPosition(full_redraw, boards[currentMove]);
15503         return;
15504     }
15505     if (gameMode == PlayFromGameFile && !pausing)
15506       PauseEvent();
15507
15508     if (moveList[target][0]) {
15509         int fromX, fromY, toX, toY;
15510         toX = moveList[target][2] - AAA;
15511         toY = moveList[target][3] - ONE;
15512         if (moveList[target][1] == '@') {
15513             if (appData.highlightLastMove) {
15514                 SetHighlights(-1, -1, toX, toY);
15515             }
15516         } else {
15517             fromX = moveList[target][0] - AAA;
15518             fromY = moveList[target][1] - ONE;
15519             if (target == currentMove - 1) {
15520                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15521             }
15522             if (appData.highlightLastMove) {
15523                 SetHighlights(fromX, fromY, toX, toY);
15524             }
15525         }
15526     }
15527     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15528         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15529         while (currentMove > target) {
15530             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15531                 // null move cannot be undone. Reload program with move history before it.
15532                 int i;
15533                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15534                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15535                 }
15536                 SendBoard(&first, i);
15537               if(second.analyzing) SendBoard(&second, i);
15538                 for(currentMove=i; currentMove<target; currentMove++) {
15539                     SendMoveToProgram(currentMove, &first);
15540                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15541                 }
15542                 break;
15543             }
15544             SendToBoth("undo\n");
15545             currentMove--;
15546         }
15547     } else {
15548         currentMove = target;
15549     }
15550
15551     if (gameMode == EditGame || gameMode == EndOfGame) {
15552         whiteTimeRemaining = timeRemaining[0][currentMove];
15553         blackTimeRemaining = timeRemaining[1][currentMove];
15554     }
15555     DisplayBothClocks();
15556     DisplayMove(currentMove - 1);
15557     DrawPosition(full_redraw, boards[currentMove]);
15558     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15559     // [HGM] PV info: routine tests if comment empty
15560     DisplayComment(currentMove - 1, commentList[currentMove]);
15561     ClearMap(); // [HGM] exclude: invalidate map
15562 }
15563
15564 void
15565 BackwardEvent ()
15566 {
15567     if (gameMode == IcsExamining && !pausing) {
15568         SendToICS(ics_prefix);
15569         SendToICS("backward\n");
15570     } else {
15571         BackwardInner(currentMove - 1);
15572     }
15573 }
15574
15575 void
15576 ToStartEvent ()
15577 {
15578     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15579         /* to optimize, we temporarily turn off analysis mode while we undo
15580          * all the moves. Otherwise we get analysis output after each undo.
15581          */
15582         if (first.analysisSupport) {
15583           SendToProgram("exit\nforce\n", &first);
15584           first.analyzing = FALSE;
15585         }
15586     }
15587
15588     if (gameMode == IcsExamining && !pausing) {
15589         SendToICS(ics_prefix);
15590         SendToICS("backward 999999\n");
15591     } else {
15592         BackwardInner(backwardMostMove);
15593     }
15594
15595     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15596         /* we have fed all the moves, so reactivate analysis mode */
15597         SendToProgram("analyze\n", &first);
15598         first.analyzing = TRUE;
15599         /*first.maybeThinking = TRUE;*/
15600         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15601     }
15602 }
15603
15604 void
15605 ToNrEvent (int to)
15606 {
15607   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15608   if (to >= forwardMostMove) to = forwardMostMove;
15609   if (to <= backwardMostMove) to = backwardMostMove;
15610   if (to < currentMove) {
15611     BackwardInner(to);
15612   } else {
15613     ForwardInner(to);
15614   }
15615 }
15616
15617 void
15618 RevertEvent (Boolean annotate)
15619 {
15620     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15621         return;
15622     }
15623     if (gameMode != IcsExamining) {
15624         DisplayError(_("You are not examining a game"), 0);
15625         return;
15626     }
15627     if (pausing) {
15628         DisplayError(_("You can't revert while pausing"), 0);
15629         return;
15630     }
15631     SendToICS(ics_prefix);
15632     SendToICS("revert\n");
15633 }
15634
15635 void
15636 RetractMoveEvent ()
15637 {
15638     switch (gameMode) {
15639       case MachinePlaysWhite:
15640       case MachinePlaysBlack:
15641         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15642             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15643             return;
15644         }
15645         if (forwardMostMove < 2) return;
15646         currentMove = forwardMostMove = forwardMostMove - 2;
15647         whiteTimeRemaining = timeRemaining[0][currentMove];
15648         blackTimeRemaining = timeRemaining[1][currentMove];
15649         DisplayBothClocks();
15650         DisplayMove(currentMove - 1);
15651         ClearHighlights();/*!! could figure this out*/
15652         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15653         SendToProgram("remove\n", &first);
15654         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15655         break;
15656
15657       case BeginningOfGame:
15658       default:
15659         break;
15660
15661       case IcsPlayingWhite:
15662       case IcsPlayingBlack:
15663         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15664             SendToICS(ics_prefix);
15665             SendToICS("takeback 2\n");
15666         } else {
15667             SendToICS(ics_prefix);
15668             SendToICS("takeback 1\n");
15669         }
15670         break;
15671     }
15672 }
15673
15674 void
15675 MoveNowEvent ()
15676 {
15677     ChessProgramState *cps;
15678
15679     switch (gameMode) {
15680       case MachinePlaysWhite:
15681         if (!WhiteOnMove(forwardMostMove)) {
15682             DisplayError(_("It is your turn"), 0);
15683             return;
15684         }
15685         cps = &first;
15686         break;
15687       case MachinePlaysBlack:
15688         if (WhiteOnMove(forwardMostMove)) {
15689             DisplayError(_("It is your turn"), 0);
15690             return;
15691         }
15692         cps = &first;
15693         break;
15694       case TwoMachinesPlay:
15695         if (WhiteOnMove(forwardMostMove) ==
15696             (first.twoMachinesColor[0] == 'w')) {
15697             cps = &first;
15698         } else {
15699             cps = &second;
15700         }
15701         break;
15702       case BeginningOfGame:
15703       default:
15704         return;
15705     }
15706     SendToProgram("?\n", cps);
15707 }
15708
15709 void
15710 TruncateGameEvent ()
15711 {
15712     EditGameEvent();
15713     if (gameMode != EditGame) return;
15714     TruncateGame();
15715 }
15716
15717 void
15718 TruncateGame ()
15719 {
15720     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15721     if (forwardMostMove > currentMove) {
15722         if (gameInfo.resultDetails != NULL) {
15723             free(gameInfo.resultDetails);
15724             gameInfo.resultDetails = NULL;
15725             gameInfo.result = GameUnfinished;
15726         }
15727         forwardMostMove = currentMove;
15728         HistorySet(parseList, backwardMostMove, forwardMostMove,
15729                    currentMove-1);
15730     }
15731 }
15732
15733 void
15734 HintEvent ()
15735 {
15736     if (appData.noChessProgram) return;
15737     switch (gameMode) {
15738       case MachinePlaysWhite:
15739         if (WhiteOnMove(forwardMostMove)) {
15740             DisplayError(_("Wait until your turn."), 0);
15741             return;
15742         }
15743         break;
15744       case BeginningOfGame:
15745       case MachinePlaysBlack:
15746         if (!WhiteOnMove(forwardMostMove)) {
15747             DisplayError(_("Wait until your turn."), 0);
15748             return;
15749         }
15750         break;
15751       default:
15752         DisplayError(_("No hint available"), 0);
15753         return;
15754     }
15755     SendToProgram("hint\n", &first);
15756     hintRequested = TRUE;
15757 }
15758
15759 void
15760 CreateBookEvent ()
15761 {
15762     ListGame * lg = (ListGame *) gameList.head;
15763     FILE *f, *g;
15764     int nItem;
15765     static int secondTime = FALSE;
15766
15767     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15768         DisplayError(_("Game list not loaded or empty"), 0);
15769         return;
15770     }
15771
15772     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15773         fclose(g);
15774         secondTime++;
15775         DisplayNote(_("Book file exists! Try again for overwrite."));
15776         return;
15777     }
15778
15779     creatingBook = TRUE;
15780     secondTime = FALSE;
15781
15782     /* Get list size */
15783     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15784         LoadGame(f, nItem, "", TRUE);
15785         AddGameToBook(TRUE);
15786         lg = (ListGame *) lg->node.succ;
15787     }
15788
15789     creatingBook = FALSE;
15790     FlushBook();
15791 }
15792
15793 void
15794 BookEvent ()
15795 {
15796     if (appData.noChessProgram) return;
15797     switch (gameMode) {
15798       case MachinePlaysWhite:
15799         if (WhiteOnMove(forwardMostMove)) {
15800             DisplayError(_("Wait until your turn."), 0);
15801             return;
15802         }
15803         break;
15804       case BeginningOfGame:
15805       case MachinePlaysBlack:
15806         if (!WhiteOnMove(forwardMostMove)) {
15807             DisplayError(_("Wait until your turn."), 0);
15808             return;
15809         }
15810         break;
15811       case EditPosition:
15812         EditPositionDone(TRUE);
15813         break;
15814       case TwoMachinesPlay:
15815         return;
15816       default:
15817         break;
15818     }
15819     SendToProgram("bk\n", &first);
15820     bookOutput[0] = NULLCHAR;
15821     bookRequested = TRUE;
15822 }
15823
15824 void
15825 AboutGameEvent ()
15826 {
15827     char *tags = PGNTags(&gameInfo);
15828     TagsPopUp(tags, CmailMsg());
15829     free(tags);
15830 }
15831
15832 /* end button procedures */
15833
15834 void
15835 PrintPosition (FILE *fp, int move)
15836 {
15837     int i, j;
15838
15839     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15840         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15841             char c = PieceToChar(boards[move][i][j]);
15842             fputc(c == 'x' ? '.' : c, fp);
15843             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15844         }
15845     }
15846     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15847       fprintf(fp, "white to play\n");
15848     else
15849       fprintf(fp, "black to play\n");
15850 }
15851
15852 void
15853 PrintOpponents (FILE *fp)
15854 {
15855     if (gameInfo.white != NULL) {
15856         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15857     } else {
15858         fprintf(fp, "\n");
15859     }
15860 }
15861
15862 /* Find last component of program's own name, using some heuristics */
15863 void
15864 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15865 {
15866     char *p, *q, c;
15867     int local = (strcmp(host, "localhost") == 0);
15868     while (!local && (p = strchr(prog, ';')) != NULL) {
15869         p++;
15870         while (*p == ' ') p++;
15871         prog = p;
15872     }
15873     if (*prog == '"' || *prog == '\'') {
15874         q = strchr(prog + 1, *prog);
15875     } else {
15876         q = strchr(prog, ' ');
15877     }
15878     if (q == NULL) q = prog + strlen(prog);
15879     p = q;
15880     while (p >= prog && *p != '/' && *p != '\\') p--;
15881     p++;
15882     if(p == prog && *p == '"') p++;
15883     c = *q; *q = 0;
15884     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15885     memcpy(buf, p, q - p);
15886     buf[q - p] = NULLCHAR;
15887     if (!local) {
15888         strcat(buf, "@");
15889         strcat(buf, host);
15890     }
15891 }
15892
15893 char *
15894 TimeControlTagValue ()
15895 {
15896     char buf[MSG_SIZ];
15897     if (!appData.clockMode) {
15898       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15899     } else if (movesPerSession > 0) {
15900       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15901     } else if (timeIncrement == 0) {
15902       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15903     } else {
15904       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15905     }
15906     return StrSave(buf);
15907 }
15908
15909 void
15910 SetGameInfo ()
15911 {
15912     /* This routine is used only for certain modes */
15913     VariantClass v = gameInfo.variant;
15914     ChessMove r = GameUnfinished;
15915     char *p = NULL;
15916
15917     if(keepInfo) return;
15918
15919     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15920         r = gameInfo.result;
15921         p = gameInfo.resultDetails;
15922         gameInfo.resultDetails = NULL;
15923     }
15924     ClearGameInfo(&gameInfo);
15925     gameInfo.variant = v;
15926
15927     switch (gameMode) {
15928       case MachinePlaysWhite:
15929         gameInfo.event = StrSave( appData.pgnEventHeader );
15930         gameInfo.site = StrSave(HostName());
15931         gameInfo.date = PGNDate();
15932         gameInfo.round = StrSave("-");
15933         gameInfo.white = StrSave(first.tidy);
15934         gameInfo.black = StrSave(UserName());
15935         gameInfo.timeControl = TimeControlTagValue();
15936         break;
15937
15938       case MachinePlaysBlack:
15939         gameInfo.event = StrSave( appData.pgnEventHeader );
15940         gameInfo.site = StrSave(HostName());
15941         gameInfo.date = PGNDate();
15942         gameInfo.round = StrSave("-");
15943         gameInfo.white = StrSave(UserName());
15944         gameInfo.black = StrSave(first.tidy);
15945         gameInfo.timeControl = TimeControlTagValue();
15946         break;
15947
15948       case TwoMachinesPlay:
15949         gameInfo.event = StrSave( appData.pgnEventHeader );
15950         gameInfo.site = StrSave(HostName());
15951         gameInfo.date = PGNDate();
15952         if (roundNr > 0) {
15953             char buf[MSG_SIZ];
15954             snprintf(buf, MSG_SIZ, "%d", roundNr);
15955             gameInfo.round = StrSave(buf);
15956         } else {
15957             gameInfo.round = StrSave("-");
15958         }
15959         if (first.twoMachinesColor[0] == 'w') {
15960             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15961             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15962         } else {
15963             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15964             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15965         }
15966         gameInfo.timeControl = TimeControlTagValue();
15967         break;
15968
15969       case EditGame:
15970         gameInfo.event = StrSave("Edited game");
15971         gameInfo.site = StrSave(HostName());
15972         gameInfo.date = PGNDate();
15973         gameInfo.round = StrSave("-");
15974         gameInfo.white = StrSave("-");
15975         gameInfo.black = StrSave("-");
15976         gameInfo.result = r;
15977         gameInfo.resultDetails = p;
15978         break;
15979
15980       case EditPosition:
15981         gameInfo.event = StrSave("Edited position");
15982         gameInfo.site = StrSave(HostName());
15983         gameInfo.date = PGNDate();
15984         gameInfo.round = StrSave("-");
15985         gameInfo.white = StrSave("-");
15986         gameInfo.black = StrSave("-");
15987         break;
15988
15989       case IcsPlayingWhite:
15990       case IcsPlayingBlack:
15991       case IcsObserving:
15992       case IcsExamining:
15993         break;
15994
15995       case PlayFromGameFile:
15996         gameInfo.event = StrSave("Game from non-PGN file");
15997         gameInfo.site = StrSave(HostName());
15998         gameInfo.date = PGNDate();
15999         gameInfo.round = StrSave("-");
16000         gameInfo.white = StrSave("?");
16001         gameInfo.black = StrSave("?");
16002         break;
16003
16004       default:
16005         break;
16006     }
16007 }
16008
16009 void
16010 ReplaceComment (int index, char *text)
16011 {
16012     int len;
16013     char *p;
16014     float score;
16015
16016     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16017        pvInfoList[index-1].depth == len &&
16018        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16019        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16020     while (*text == '\n') text++;
16021     len = strlen(text);
16022     while (len > 0 && text[len - 1] == '\n') len--;
16023
16024     if (commentList[index] != NULL)
16025       free(commentList[index]);
16026
16027     if (len == 0) {
16028         commentList[index] = NULL;
16029         return;
16030     }
16031   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16032       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16033       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16034     commentList[index] = (char *) malloc(len + 2);
16035     strncpy(commentList[index], text, len);
16036     commentList[index][len] = '\n';
16037     commentList[index][len + 1] = NULLCHAR;
16038   } else {
16039     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16040     char *p;
16041     commentList[index] = (char *) malloc(len + 7);
16042     safeStrCpy(commentList[index], "{\n", 3);
16043     safeStrCpy(commentList[index]+2, text, len+1);
16044     commentList[index][len+2] = NULLCHAR;
16045     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16046     strcat(commentList[index], "\n}\n");
16047   }
16048 }
16049
16050 void
16051 CrushCRs (char *text)
16052 {
16053   char *p = text;
16054   char *q = text;
16055   char ch;
16056
16057   do {
16058     ch = *p++;
16059     if (ch == '\r') continue;
16060     *q++ = ch;
16061   } while (ch != '\0');
16062 }
16063
16064 void
16065 AppendComment (int index, char *text, Boolean addBraces)
16066 /* addBraces  tells if we should add {} */
16067 {
16068     int oldlen, len;
16069     char *old;
16070
16071 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16072     if(addBraces == 3) addBraces = 0; else // force appending literally
16073     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16074
16075     CrushCRs(text);
16076     while (*text == '\n') text++;
16077     len = strlen(text);
16078     while (len > 0 && text[len - 1] == '\n') len--;
16079     text[len] = NULLCHAR;
16080
16081     if (len == 0) return;
16082
16083     if (commentList[index] != NULL) {
16084       Boolean addClosingBrace = addBraces;
16085         old = commentList[index];
16086         oldlen = strlen(old);
16087         while(commentList[index][oldlen-1] ==  '\n')
16088           commentList[index][--oldlen] = NULLCHAR;
16089         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16090         safeStrCpy(commentList[index], old, oldlen + len + 6);
16091         free(old);
16092         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16093         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16094           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16095           while (*text == '\n') { text++; len--; }
16096           commentList[index][--oldlen] = NULLCHAR;
16097       }
16098         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16099         else          strcat(commentList[index], "\n");
16100         strcat(commentList[index], text);
16101         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16102         else          strcat(commentList[index], "\n");
16103     } else {
16104         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16105         if(addBraces)
16106           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16107         else commentList[index][0] = NULLCHAR;
16108         strcat(commentList[index], text);
16109         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16110         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16111     }
16112 }
16113
16114 static char *
16115 FindStr (char * text, char * sub_text)
16116 {
16117     char * result = strstr( text, sub_text );
16118
16119     if( result != NULL ) {
16120         result += strlen( sub_text );
16121     }
16122
16123     return result;
16124 }
16125
16126 /* [AS] Try to extract PV info from PGN comment */
16127 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16128 char *
16129 GetInfoFromComment (int index, char * text)
16130 {
16131     char * sep = text, *p;
16132
16133     if( text != NULL && index > 0 ) {
16134         int score = 0;
16135         int depth = 0;
16136         int time = -1, sec = 0, deci;
16137         char * s_eval = FindStr( text, "[%eval " );
16138         char * s_emt = FindStr( text, "[%emt " );
16139 #if 0
16140         if( s_eval != NULL || s_emt != NULL ) {
16141 #else
16142         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16143 #endif
16144             /* New style */
16145             char delim;
16146
16147             if( s_eval != NULL ) {
16148                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16149                     return text;
16150                 }
16151
16152                 if( delim != ']' ) {
16153                     return text;
16154                 }
16155             }
16156
16157             if( s_emt != NULL ) {
16158             }
16159                 return text;
16160         }
16161         else {
16162             /* We expect something like: [+|-]nnn.nn/dd */
16163             int score_lo = 0;
16164
16165             if(*text != '{') return text; // [HGM] braces: must be normal comment
16166
16167             sep = strchr( text, '/' );
16168             if( sep == NULL || sep < (text+4) ) {
16169                 return text;
16170             }
16171
16172             p = text;
16173             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16174             if(p[1] == '(') { // comment starts with PV
16175                p = strchr(p, ')'); // locate end of PV
16176                if(p == NULL || sep < p+5) return text;
16177                // at this point we have something like "{(.*) +0.23/6 ..."
16178                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16179                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16180                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16181             }
16182             time = -1; sec = -1; deci = -1;
16183             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16184                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16185                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16186                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16187                 return text;
16188             }
16189
16190             if( score_lo < 0 || score_lo >= 100 ) {
16191                 return text;
16192             }
16193
16194             if(sec >= 0) time = 600*time + 10*sec; else
16195             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16196
16197             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16198
16199             /* [HGM] PV time: now locate end of PV info */
16200             while( *++sep >= '0' && *sep <= '9'); // strip depth
16201             if(time >= 0)
16202             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16203             if(sec >= 0)
16204             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16205             if(deci >= 0)
16206             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16207             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16208         }
16209
16210         if( depth <= 0 ) {
16211             return text;
16212         }
16213
16214         if( time < 0 ) {
16215             time = -1;
16216         }
16217
16218         pvInfoList[index-1].depth = depth;
16219         pvInfoList[index-1].score = score;
16220         pvInfoList[index-1].time  = 10*time; // centi-sec
16221         if(*sep == '}') *sep = 0; else *--sep = '{';
16222         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16223     }
16224     return sep;
16225 }
16226
16227 void
16228 SendToProgram (char *message, ChessProgramState *cps)
16229 {
16230     int count, outCount, error;
16231     char buf[MSG_SIZ];
16232
16233     if (cps->pr == NoProc) return;
16234     Attention(cps);
16235
16236     if (appData.debugMode) {
16237         TimeMark now;
16238         GetTimeMark(&now);
16239         fprintf(debugFP, "%ld >%-6s: %s",
16240                 SubtractTimeMarks(&now, &programStartTime),
16241                 cps->which, message);
16242         if(serverFP)
16243             fprintf(serverFP, "%ld >%-6s: %s",
16244                 SubtractTimeMarks(&now, &programStartTime),
16245                 cps->which, message), fflush(serverFP);
16246     }
16247
16248     count = strlen(message);
16249     outCount = OutputToProcess(cps->pr, message, count, &error);
16250     if (outCount < count && !exiting
16251                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16252       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16253       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16254         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16255             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16256                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16257                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16258                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16259             } else {
16260                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16261                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16262                 gameInfo.result = res;
16263             }
16264             gameInfo.resultDetails = StrSave(buf);
16265         }
16266         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16267         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16268     }
16269 }
16270
16271 void
16272 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16273 {
16274     char *end_str;
16275     char buf[MSG_SIZ];
16276     ChessProgramState *cps = (ChessProgramState *)closure;
16277
16278     if (isr != cps->isr) return; /* Killed intentionally */
16279     if (count <= 0) {
16280         if (count == 0) {
16281             RemoveInputSource(cps->isr);
16282             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16283                     _(cps->which), cps->program);
16284             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16285             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16286                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16287                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16288                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16289                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16290                 } else {
16291                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16292                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16293                     gameInfo.result = res;
16294                 }
16295                 gameInfo.resultDetails = StrSave(buf);
16296             }
16297             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16298             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16299         } else {
16300             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16301                     _(cps->which), cps->program);
16302             RemoveInputSource(cps->isr);
16303
16304             /* [AS] Program is misbehaving badly... kill it */
16305             if( count == -2 ) {
16306                 DestroyChildProcess( cps->pr, 9 );
16307                 cps->pr = NoProc;
16308             }
16309
16310             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16311         }
16312         return;
16313     }
16314
16315     if ((end_str = strchr(message, '\r')) != NULL)
16316       *end_str = NULLCHAR;
16317     if ((end_str = strchr(message, '\n')) != NULL)
16318       *end_str = NULLCHAR;
16319
16320     if (appData.debugMode) {
16321         TimeMark now; int print = 1;
16322         char *quote = ""; char c; int i;
16323
16324         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16325                 char start = message[0];
16326                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16327                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16328                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16329                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16330                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16331                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16332                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16333                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16334                    sscanf(message, "hint: %c", &c)!=1 &&
16335                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16336                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16337                     print = (appData.engineComments >= 2);
16338                 }
16339                 message[0] = start; // restore original message
16340         }
16341         if(print) {
16342                 GetTimeMark(&now);
16343                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16344                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16345                         quote,
16346                         message);
16347                 if(serverFP)
16348                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16349                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16350                         quote,
16351                         message), fflush(serverFP);
16352         }
16353     }
16354
16355     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16356     if (appData.icsEngineAnalyze) {
16357         if (strstr(message, "whisper") != NULL ||
16358              strstr(message, "kibitz") != NULL ||
16359             strstr(message, "tellics") != NULL) return;
16360     }
16361
16362     HandleMachineMove(message, cps);
16363 }
16364
16365
16366 void
16367 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16368 {
16369     char buf[MSG_SIZ];
16370     int seconds;
16371
16372     if( timeControl_2 > 0 ) {
16373         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16374             tc = timeControl_2;
16375         }
16376     }
16377     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16378     inc /= cps->timeOdds;
16379     st  /= cps->timeOdds;
16380
16381     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16382
16383     if (st > 0) {
16384       /* Set exact time per move, normally using st command */
16385       if (cps->stKludge) {
16386         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16387         seconds = st % 60;
16388         if (seconds == 0) {
16389           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16390         } else {
16391           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16392         }
16393       } else {
16394         snprintf(buf, MSG_SIZ, "st %d\n", st);
16395       }
16396     } else {
16397       /* Set conventional or incremental time control, using level command */
16398       if (seconds == 0) {
16399         /* Note old gnuchess bug -- minutes:seconds used to not work.
16400            Fixed in later versions, but still avoid :seconds
16401            when seconds is 0. */
16402         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16403       } else {
16404         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16405                  seconds, inc/1000.);
16406       }
16407     }
16408     SendToProgram(buf, cps);
16409
16410     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16411     /* Orthogonally, limit search to given depth */
16412     if (sd > 0) {
16413       if (cps->sdKludge) {
16414         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16415       } else {
16416         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16417       }
16418       SendToProgram(buf, cps);
16419     }
16420
16421     if(cps->nps >= 0) { /* [HGM] nps */
16422         if(cps->supportsNPS == FALSE)
16423           cps->nps = -1; // don't use if engine explicitly says not supported!
16424         else {
16425           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16426           SendToProgram(buf, cps);
16427         }
16428     }
16429 }
16430
16431 ChessProgramState *
16432 WhitePlayer ()
16433 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16434 {
16435     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16436        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16437         return &second;
16438     return &first;
16439 }
16440
16441 void
16442 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16443 {
16444     char message[MSG_SIZ];
16445     long time, otime;
16446
16447     /* Note: this routine must be called when the clocks are stopped
16448        or when they have *just* been set or switched; otherwise
16449        it will be off by the time since the current tick started.
16450     */
16451     if (machineWhite) {
16452         time = whiteTimeRemaining / 10;
16453         otime = blackTimeRemaining / 10;
16454     } else {
16455         time = blackTimeRemaining / 10;
16456         otime = whiteTimeRemaining / 10;
16457     }
16458     /* [HGM] translate opponent's time by time-odds factor */
16459     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16460
16461     if (time <= 0) time = 1;
16462     if (otime <= 0) otime = 1;
16463
16464     snprintf(message, MSG_SIZ, "time %ld\n", time);
16465     SendToProgram(message, cps);
16466
16467     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16468     SendToProgram(message, cps);
16469 }
16470
16471 char *
16472 EngineDefinedVariant (ChessProgramState *cps, int n)
16473 {   // return name of n-th unknown variant that engine supports
16474     static char buf[MSG_SIZ];
16475     char *p, *s = cps->variants;
16476     if(!s) return NULL;
16477     do { // parse string from variants feature
16478       VariantClass v;
16479         p = strchr(s, ',');
16480         if(p) *p = NULLCHAR;
16481       v = StringToVariant(s);
16482       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16483         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16484             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16485         }
16486         if(p) *p++ = ',';
16487         if(n < 0) return buf;
16488     } while(s = p);
16489     return NULL;
16490 }
16491
16492 int
16493 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16494 {
16495   char buf[MSG_SIZ];
16496   int len = strlen(name);
16497   int val;
16498
16499   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16500     (*p) += len + 1;
16501     sscanf(*p, "%d", &val);
16502     *loc = (val != 0);
16503     while (**p && **p != ' ')
16504       (*p)++;
16505     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16506     SendToProgram(buf, cps);
16507     return TRUE;
16508   }
16509   return FALSE;
16510 }
16511
16512 int
16513 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16514 {
16515   char buf[MSG_SIZ];
16516   int len = strlen(name);
16517   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16518     (*p) += len + 1;
16519     sscanf(*p, "%d", loc);
16520     while (**p && **p != ' ') (*p)++;
16521     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16522     SendToProgram(buf, cps);
16523     return TRUE;
16524   }
16525   return FALSE;
16526 }
16527
16528 int
16529 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16530 {
16531   char buf[MSG_SIZ];
16532   int len = strlen(name);
16533   if (strncmp((*p), name, len) == 0
16534       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16535     (*p) += len + 2;
16536     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16537     sscanf(*p, "%[^\"]", *loc);
16538     while (**p && **p != '\"') (*p)++;
16539     if (**p == '\"') (*p)++;
16540     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16541     SendToProgram(buf, cps);
16542     return TRUE;
16543   }
16544   return FALSE;
16545 }
16546
16547 int
16548 ParseOption (Option *opt, ChessProgramState *cps)
16549 // [HGM] options: process the string that defines an engine option, and determine
16550 // name, type, default value, and allowed value range
16551 {
16552         char *p, *q, buf[MSG_SIZ];
16553         int n, min = (-1)<<31, max = 1<<31, def;
16554
16555         if(p = strstr(opt->name, " -spin ")) {
16556             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16557             if(max < min) max = min; // enforce consistency
16558             if(def < min) def = min;
16559             if(def > max) def = max;
16560             opt->value = def;
16561             opt->min = min;
16562             opt->max = max;
16563             opt->type = Spin;
16564         } else if((p = strstr(opt->name, " -slider "))) {
16565             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16566             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16567             if(max < min) max = min; // enforce consistency
16568             if(def < min) def = min;
16569             if(def > max) def = max;
16570             opt->value = def;
16571             opt->min = min;
16572             opt->max = max;
16573             opt->type = Spin; // Slider;
16574         } else if((p = strstr(opt->name, " -string "))) {
16575             opt->textValue = p+9;
16576             opt->type = TextBox;
16577         } else if((p = strstr(opt->name, " -file "))) {
16578             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16579             opt->textValue = p+7;
16580             opt->type = FileName; // FileName;
16581         } else if((p = strstr(opt->name, " -path "))) {
16582             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16583             opt->textValue = p+7;
16584             opt->type = PathName; // PathName;
16585         } else if(p = strstr(opt->name, " -check ")) {
16586             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16587             opt->value = (def != 0);
16588             opt->type = CheckBox;
16589         } else if(p = strstr(opt->name, " -combo ")) {
16590             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16591             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16592             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16593             opt->value = n = 0;
16594             while(q = StrStr(q, " /// ")) {
16595                 n++; *q = 0;    // count choices, and null-terminate each of them
16596                 q += 5;
16597                 if(*q == '*') { // remember default, which is marked with * prefix
16598                     q++;
16599                     opt->value = n;
16600                 }
16601                 cps->comboList[cps->comboCnt++] = q;
16602             }
16603             cps->comboList[cps->comboCnt++] = NULL;
16604             opt->max = n + 1;
16605             opt->type = ComboBox;
16606         } else if(p = strstr(opt->name, " -button")) {
16607             opt->type = Button;
16608         } else if(p = strstr(opt->name, " -save")) {
16609             opt->type = SaveButton;
16610         } else return FALSE;
16611         *p = 0; // terminate option name
16612         // now look if the command-line options define a setting for this engine option.
16613         if(cps->optionSettings && cps->optionSettings[0])
16614             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16615         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16616           snprintf(buf, MSG_SIZ, "option %s", p);
16617                 if(p = strstr(buf, ",")) *p = 0;
16618                 if(q = strchr(buf, '=')) switch(opt->type) {
16619                     case ComboBox:
16620                         for(n=0; n<opt->max; n++)
16621                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16622                         break;
16623                     case TextBox:
16624                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16625                         break;
16626                     case Spin:
16627                     case CheckBox:
16628                         opt->value = atoi(q+1);
16629                     default:
16630                         break;
16631                 }
16632                 strcat(buf, "\n");
16633                 SendToProgram(buf, cps);
16634         }
16635         return TRUE;
16636 }
16637
16638 void
16639 FeatureDone (ChessProgramState *cps, int val)
16640 {
16641   DelayedEventCallback cb = GetDelayedEvent();
16642   if ((cb == InitBackEnd3 && cps == &first) ||
16643       (cb == SettingsMenuIfReady && cps == &second) ||
16644       (cb == LoadEngine) ||
16645       (cb == TwoMachinesEventIfReady)) {
16646     CancelDelayedEvent();
16647     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16648   }
16649   cps->initDone = val;
16650   if(val) cps->reload = FALSE;
16651 }
16652
16653 /* Parse feature command from engine */
16654 void
16655 ParseFeatures (char *args, ChessProgramState *cps)
16656 {
16657   char *p = args;
16658   char *q = NULL;
16659   int val;
16660   char buf[MSG_SIZ];
16661
16662   for (;;) {
16663     while (*p == ' ') p++;
16664     if (*p == NULLCHAR) return;
16665
16666     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16667     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16668     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16669     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16670     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16671     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16672     if (BoolFeature(&p, "reuse", &val, cps)) {
16673       /* Engine can disable reuse, but can't enable it if user said no */
16674       if (!val) cps->reuse = FALSE;
16675       continue;
16676     }
16677     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16678     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16679       if (gameMode == TwoMachinesPlay) {
16680         DisplayTwoMachinesTitle();
16681       } else {
16682         DisplayTitle("");
16683       }
16684       continue;
16685     }
16686     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16687     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16688     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16689     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16690     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16691     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16692     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16693     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16694     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16695     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16696     if (IntFeature(&p, "done", &val, cps)) {
16697       FeatureDone(cps, val);
16698       continue;
16699     }
16700     /* Added by Tord: */
16701     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16702     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16703     /* End of additions by Tord */
16704
16705     /* [HGM] added features: */
16706     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16707     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16708     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16709     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16710     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16711     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16712     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16713     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16714         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16715         FREE(cps->option[cps->nrOptions].name);
16716         cps->option[cps->nrOptions].name = q; q = NULL;
16717         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16718           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16719             SendToProgram(buf, cps);
16720             continue;
16721         }
16722         if(cps->nrOptions >= MAX_OPTIONS) {
16723             cps->nrOptions--;
16724             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16725             DisplayError(buf, 0);
16726         }
16727         continue;
16728     }
16729     /* End of additions by HGM */
16730
16731     /* unknown feature: complain and skip */
16732     q = p;
16733     while (*q && *q != '=') q++;
16734     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16735     SendToProgram(buf, cps);
16736     p = q;
16737     if (*p == '=') {
16738       p++;
16739       if (*p == '\"') {
16740         p++;
16741         while (*p && *p != '\"') p++;
16742         if (*p == '\"') p++;
16743       } else {
16744         while (*p && *p != ' ') p++;
16745       }
16746     }
16747   }
16748
16749 }
16750
16751 void
16752 PeriodicUpdatesEvent (int newState)
16753 {
16754     if (newState == appData.periodicUpdates)
16755       return;
16756
16757     appData.periodicUpdates=newState;
16758
16759     /* Display type changes, so update it now */
16760 //    DisplayAnalysis();
16761
16762     /* Get the ball rolling again... */
16763     if (newState) {
16764         AnalysisPeriodicEvent(1);
16765         StartAnalysisClock();
16766     }
16767 }
16768
16769 void
16770 PonderNextMoveEvent (int newState)
16771 {
16772     if (newState == appData.ponderNextMove) return;
16773     if (gameMode == EditPosition) EditPositionDone(TRUE);
16774     if (newState) {
16775         SendToProgram("hard\n", &first);
16776         if (gameMode == TwoMachinesPlay) {
16777             SendToProgram("hard\n", &second);
16778         }
16779     } else {
16780         SendToProgram("easy\n", &first);
16781         thinkOutput[0] = NULLCHAR;
16782         if (gameMode == TwoMachinesPlay) {
16783             SendToProgram("easy\n", &second);
16784         }
16785     }
16786     appData.ponderNextMove = newState;
16787 }
16788
16789 void
16790 NewSettingEvent (int option, int *feature, char *command, int value)
16791 {
16792     char buf[MSG_SIZ];
16793
16794     if (gameMode == EditPosition) EditPositionDone(TRUE);
16795     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16796     if(feature == NULL || *feature) SendToProgram(buf, &first);
16797     if (gameMode == TwoMachinesPlay) {
16798         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16799     }
16800 }
16801
16802 void
16803 ShowThinkingEvent ()
16804 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16805 {
16806     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16807     int newState = appData.showThinking
16808         // [HGM] thinking: other features now need thinking output as well
16809         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16810
16811     if (oldState == newState) return;
16812     oldState = newState;
16813     if (gameMode == EditPosition) EditPositionDone(TRUE);
16814     if (oldState) {
16815         SendToProgram("post\n", &first);
16816         if (gameMode == TwoMachinesPlay) {
16817             SendToProgram("post\n", &second);
16818         }
16819     } else {
16820         SendToProgram("nopost\n", &first);
16821         thinkOutput[0] = NULLCHAR;
16822         if (gameMode == TwoMachinesPlay) {
16823             SendToProgram("nopost\n", &second);
16824         }
16825     }
16826 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16827 }
16828
16829 void
16830 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16831 {
16832   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16833   if (pr == NoProc) return;
16834   AskQuestion(title, question, replyPrefix, pr);
16835 }
16836
16837 void
16838 TypeInEvent (char firstChar)
16839 {
16840     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16841         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16842         gameMode == AnalyzeMode || gameMode == EditGame ||
16843         gameMode == EditPosition || gameMode == IcsExamining ||
16844         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16845         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16846                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16847                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16848         gameMode == Training) PopUpMoveDialog(firstChar);
16849 }
16850
16851 void
16852 TypeInDoneEvent (char *move)
16853 {
16854         Board board;
16855         int n, fromX, fromY, toX, toY;
16856         char promoChar;
16857         ChessMove moveType;
16858
16859         // [HGM] FENedit
16860         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16861                 EditPositionPasteFEN(move);
16862                 return;
16863         }
16864         // [HGM] movenum: allow move number to be typed in any mode
16865         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16866           ToNrEvent(2*n-1);
16867           return;
16868         }
16869         // undocumented kludge: allow command-line option to be typed in!
16870         // (potentially fatal, and does not implement the effect of the option.)
16871         // should only be used for options that are values on which future decisions will be made,
16872         // and definitely not on options that would be used during initialization.
16873         if(strstr(move, "!!! -") == move) {
16874             ParseArgsFromString(move+4);
16875             return;
16876         }
16877
16878       if (gameMode != EditGame && currentMove != forwardMostMove &&
16879         gameMode != Training) {
16880         DisplayMoveError(_("Displayed move is not current"));
16881       } else {
16882         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16883           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16884         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16885         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16886           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16887           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16888         } else {
16889           DisplayMoveError(_("Could not parse move"));
16890         }
16891       }
16892 }
16893
16894 void
16895 DisplayMove (int moveNumber)
16896 {
16897     char message[MSG_SIZ];
16898     char res[MSG_SIZ];
16899     char cpThinkOutput[MSG_SIZ];
16900
16901     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16902
16903     if (moveNumber == forwardMostMove - 1 ||
16904         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16905
16906         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16907
16908         if (strchr(cpThinkOutput, '\n')) {
16909             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16910         }
16911     } else {
16912         *cpThinkOutput = NULLCHAR;
16913     }
16914
16915     /* [AS] Hide thinking from human user */
16916     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16917         *cpThinkOutput = NULLCHAR;
16918         if( thinkOutput[0] != NULLCHAR ) {
16919             int i;
16920
16921             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16922                 cpThinkOutput[i] = '.';
16923             }
16924             cpThinkOutput[i] = NULLCHAR;
16925             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16926         }
16927     }
16928
16929     if (moveNumber == forwardMostMove - 1 &&
16930         gameInfo.resultDetails != NULL) {
16931         if (gameInfo.resultDetails[0] == NULLCHAR) {
16932           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16933         } else {
16934           snprintf(res, MSG_SIZ, " {%s} %s",
16935                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16936         }
16937     } else {
16938         res[0] = NULLCHAR;
16939     }
16940
16941     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16942         DisplayMessage(res, cpThinkOutput);
16943     } else {
16944       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16945                 WhiteOnMove(moveNumber) ? " " : ".. ",
16946                 parseList[moveNumber], res);
16947         DisplayMessage(message, cpThinkOutput);
16948     }
16949 }
16950
16951 void
16952 DisplayComment (int moveNumber, char *text)
16953 {
16954     char title[MSG_SIZ];
16955
16956     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16957       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16958     } else {
16959       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16960               WhiteOnMove(moveNumber) ? " " : ".. ",
16961               parseList[moveNumber]);
16962     }
16963     if (text != NULL && (appData.autoDisplayComment || commentUp))
16964         CommentPopUp(title, text);
16965 }
16966
16967 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16968  * might be busy thinking or pondering.  It can be omitted if your
16969  * gnuchess is configured to stop thinking immediately on any user
16970  * input.  However, that gnuchess feature depends on the FIONREAD
16971  * ioctl, which does not work properly on some flavors of Unix.
16972  */
16973 void
16974 Attention (ChessProgramState *cps)
16975 {
16976 #if ATTENTION
16977     if (!cps->useSigint) return;
16978     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16979     switch (gameMode) {
16980       case MachinePlaysWhite:
16981       case MachinePlaysBlack:
16982       case TwoMachinesPlay:
16983       case IcsPlayingWhite:
16984       case IcsPlayingBlack:
16985       case AnalyzeMode:
16986       case AnalyzeFile:
16987         /* Skip if we know it isn't thinking */
16988         if (!cps->maybeThinking) return;
16989         if (appData.debugMode)
16990           fprintf(debugFP, "Interrupting %s\n", cps->which);
16991         InterruptChildProcess(cps->pr);
16992         cps->maybeThinking = FALSE;
16993         break;
16994       default:
16995         break;
16996     }
16997 #endif /*ATTENTION*/
16998 }
16999
17000 int
17001 CheckFlags ()
17002 {
17003     if (whiteTimeRemaining <= 0) {
17004         if (!whiteFlag) {
17005             whiteFlag = TRUE;
17006             if (appData.icsActive) {
17007                 if (appData.autoCallFlag &&
17008                     gameMode == IcsPlayingBlack && !blackFlag) {
17009                   SendToICS(ics_prefix);
17010                   SendToICS("flag\n");
17011                 }
17012             } else {
17013                 if (blackFlag) {
17014                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17015                 } else {
17016                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17017                     if (appData.autoCallFlag) {
17018                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17019                         return TRUE;
17020                     }
17021                 }
17022             }
17023         }
17024     }
17025     if (blackTimeRemaining <= 0) {
17026         if (!blackFlag) {
17027             blackFlag = TRUE;
17028             if (appData.icsActive) {
17029                 if (appData.autoCallFlag &&
17030                     gameMode == IcsPlayingWhite && !whiteFlag) {
17031                   SendToICS(ics_prefix);
17032                   SendToICS("flag\n");
17033                 }
17034             } else {
17035                 if (whiteFlag) {
17036                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17037                 } else {
17038                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17039                     if (appData.autoCallFlag) {
17040                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17041                         return TRUE;
17042                     }
17043                 }
17044             }
17045         }
17046     }
17047     return FALSE;
17048 }
17049
17050 void
17051 CheckTimeControl ()
17052 {
17053     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17054         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17055
17056     /*
17057      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17058      */
17059     if ( !WhiteOnMove(forwardMostMove) ) {
17060         /* White made time control */
17061         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17062         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17063         /* [HGM] time odds: correct new time quota for time odds! */
17064                                             / WhitePlayer()->timeOdds;
17065         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17066     } else {
17067         lastBlack -= blackTimeRemaining;
17068         /* Black made time control */
17069         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17070                                             / WhitePlayer()->other->timeOdds;
17071         lastWhite = whiteTimeRemaining;
17072     }
17073 }
17074
17075 void
17076 DisplayBothClocks ()
17077 {
17078     int wom = gameMode == EditPosition ?
17079       !blackPlaysFirst : WhiteOnMove(currentMove);
17080     DisplayWhiteClock(whiteTimeRemaining, wom);
17081     DisplayBlackClock(blackTimeRemaining, !wom);
17082 }
17083
17084
17085 /* Timekeeping seems to be a portability nightmare.  I think everyone
17086    has ftime(), but I'm really not sure, so I'm including some ifdefs
17087    to use other calls if you don't.  Clocks will be less accurate if
17088    you have neither ftime nor gettimeofday.
17089 */
17090
17091 /* VS 2008 requires the #include outside of the function */
17092 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17093 #include <sys/timeb.h>
17094 #endif
17095
17096 /* Get the current time as a TimeMark */
17097 void
17098 GetTimeMark (TimeMark *tm)
17099 {
17100 #if HAVE_GETTIMEOFDAY
17101
17102     struct timeval timeVal;
17103     struct timezone timeZone;
17104
17105     gettimeofday(&timeVal, &timeZone);
17106     tm->sec = (long) timeVal.tv_sec;
17107     tm->ms = (int) (timeVal.tv_usec / 1000L);
17108
17109 #else /*!HAVE_GETTIMEOFDAY*/
17110 #if HAVE_FTIME
17111
17112 // include <sys/timeb.h> / moved to just above start of function
17113     struct timeb timeB;
17114
17115     ftime(&timeB);
17116     tm->sec = (long) timeB.time;
17117     tm->ms = (int) timeB.millitm;
17118
17119 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17120     tm->sec = (long) time(NULL);
17121     tm->ms = 0;
17122 #endif
17123 #endif
17124 }
17125
17126 /* Return the difference in milliseconds between two
17127    time marks.  We assume the difference will fit in a long!
17128 */
17129 long
17130 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17131 {
17132     return 1000L*(tm2->sec - tm1->sec) +
17133            (long) (tm2->ms - tm1->ms);
17134 }
17135
17136
17137 /*
17138  * Code to manage the game clocks.
17139  *
17140  * In tournament play, black starts the clock and then white makes a move.
17141  * We give the human user a slight advantage if he is playing white---the
17142  * clocks don't run until he makes his first move, so it takes zero time.
17143  * Also, we don't account for network lag, so we could get out of sync
17144  * with GNU Chess's clock -- but then, referees are always right.
17145  */
17146
17147 static TimeMark tickStartTM;
17148 static long intendedTickLength;
17149
17150 long
17151 NextTickLength (long timeRemaining)
17152 {
17153     long nominalTickLength, nextTickLength;
17154
17155     if (timeRemaining > 0L && timeRemaining <= 10000L)
17156       nominalTickLength = 100L;
17157     else
17158       nominalTickLength = 1000L;
17159     nextTickLength = timeRemaining % nominalTickLength;
17160     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17161
17162     return nextTickLength;
17163 }
17164
17165 /* Adjust clock one minute up or down */
17166 void
17167 AdjustClock (Boolean which, int dir)
17168 {
17169     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17170     if(which) blackTimeRemaining += 60000*dir;
17171     else      whiteTimeRemaining += 60000*dir;
17172     DisplayBothClocks();
17173     adjustedClock = TRUE;
17174 }
17175
17176 /* Stop clocks and reset to a fresh time control */
17177 void
17178 ResetClocks ()
17179 {
17180     (void) StopClockTimer();
17181     if (appData.icsActive) {
17182         whiteTimeRemaining = blackTimeRemaining = 0;
17183     } else if (searchTime) {
17184         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17185         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17186     } else { /* [HGM] correct new time quote for time odds */
17187         whiteTC = blackTC = fullTimeControlString;
17188         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17189         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17190     }
17191     if (whiteFlag || blackFlag) {
17192         DisplayTitle("");
17193         whiteFlag = blackFlag = FALSE;
17194     }
17195     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17196     DisplayBothClocks();
17197     adjustedClock = FALSE;
17198 }
17199
17200 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17201
17202 /* Decrement running clock by amount of time that has passed */
17203 void
17204 DecrementClocks ()
17205 {
17206     long timeRemaining;
17207     long lastTickLength, fudge;
17208     TimeMark now;
17209
17210     if (!appData.clockMode) return;
17211     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17212
17213     GetTimeMark(&now);
17214
17215     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17216
17217     /* Fudge if we woke up a little too soon */
17218     fudge = intendedTickLength - lastTickLength;
17219     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17220
17221     if (WhiteOnMove(forwardMostMove)) {
17222         if(whiteNPS >= 0) lastTickLength = 0;
17223         timeRemaining = whiteTimeRemaining -= lastTickLength;
17224         if(timeRemaining < 0 && !appData.icsActive) {
17225             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17226             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17227                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17228                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17229             }
17230         }
17231         DisplayWhiteClock(whiteTimeRemaining - fudge,
17232                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17233     } else {
17234         if(blackNPS >= 0) lastTickLength = 0;
17235         timeRemaining = blackTimeRemaining -= lastTickLength;
17236         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17237             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17238             if(suddenDeath) {
17239                 blackStartMove = forwardMostMove;
17240                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17241             }
17242         }
17243         DisplayBlackClock(blackTimeRemaining - fudge,
17244                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17245     }
17246     if (CheckFlags()) return;
17247
17248     if(twoBoards) { // count down secondary board's clocks as well
17249         activePartnerTime -= lastTickLength;
17250         partnerUp = 1;
17251         if(activePartner == 'W')
17252             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17253         else
17254             DisplayBlackClock(activePartnerTime, TRUE);
17255         partnerUp = 0;
17256     }
17257
17258     tickStartTM = now;
17259     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17260     StartClockTimer(intendedTickLength);
17261
17262     /* if the time remaining has fallen below the alarm threshold, sound the
17263      * alarm. if the alarm has sounded and (due to a takeback or time control
17264      * with increment) the time remaining has increased to a level above the
17265      * threshold, reset the alarm so it can sound again.
17266      */
17267
17268     if (appData.icsActive && appData.icsAlarm) {
17269
17270         /* make sure we are dealing with the user's clock */
17271         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17272                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17273            )) return;
17274
17275         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17276             alarmSounded = FALSE;
17277         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17278             PlayAlarmSound();
17279             alarmSounded = TRUE;
17280         }
17281     }
17282 }
17283
17284
17285 /* A player has just moved, so stop the previously running
17286    clock and (if in clock mode) start the other one.
17287    We redisplay both clocks in case we're in ICS mode, because
17288    ICS gives us an update to both clocks after every move.
17289    Note that this routine is called *after* forwardMostMove
17290    is updated, so the last fractional tick must be subtracted
17291    from the color that is *not* on move now.
17292 */
17293 void
17294 SwitchClocks (int newMoveNr)
17295 {
17296     long lastTickLength;
17297     TimeMark now;
17298     int flagged = FALSE;
17299
17300     GetTimeMark(&now);
17301
17302     if (StopClockTimer() && appData.clockMode) {
17303         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17304         if (!WhiteOnMove(forwardMostMove)) {
17305             if(blackNPS >= 0) lastTickLength = 0;
17306             blackTimeRemaining -= lastTickLength;
17307            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17308 //         if(pvInfoList[forwardMostMove].time == -1)
17309                  pvInfoList[forwardMostMove].time =               // use GUI time
17310                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17311         } else {
17312            if(whiteNPS >= 0) lastTickLength = 0;
17313            whiteTimeRemaining -= lastTickLength;
17314            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17315 //         if(pvInfoList[forwardMostMove].time == -1)
17316                  pvInfoList[forwardMostMove].time =
17317                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17318         }
17319         flagged = CheckFlags();
17320     }
17321     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17322     CheckTimeControl();
17323
17324     if (flagged || !appData.clockMode) return;
17325
17326     switch (gameMode) {
17327       case MachinePlaysBlack:
17328       case MachinePlaysWhite:
17329       case BeginningOfGame:
17330         if (pausing) return;
17331         break;
17332
17333       case EditGame:
17334       case PlayFromGameFile:
17335       case IcsExamining:
17336         return;
17337
17338       default:
17339         break;
17340     }
17341
17342     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17343         if(WhiteOnMove(forwardMostMove))
17344              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17345         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17346     }
17347
17348     tickStartTM = now;
17349     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17350       whiteTimeRemaining : blackTimeRemaining);
17351     StartClockTimer(intendedTickLength);
17352 }
17353
17354
17355 /* Stop both clocks */
17356 void
17357 StopClocks ()
17358 {
17359     long lastTickLength;
17360     TimeMark now;
17361
17362     if (!StopClockTimer()) return;
17363     if (!appData.clockMode) return;
17364
17365     GetTimeMark(&now);
17366
17367     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17368     if (WhiteOnMove(forwardMostMove)) {
17369         if(whiteNPS >= 0) lastTickLength = 0;
17370         whiteTimeRemaining -= lastTickLength;
17371         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17372     } else {
17373         if(blackNPS >= 0) lastTickLength = 0;
17374         blackTimeRemaining -= lastTickLength;
17375         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17376     }
17377     CheckFlags();
17378 }
17379
17380 /* Start clock of player on move.  Time may have been reset, so
17381    if clock is already running, stop and restart it. */
17382 void
17383 StartClocks ()
17384 {
17385     (void) StopClockTimer(); /* in case it was running already */
17386     DisplayBothClocks();
17387     if (CheckFlags()) return;
17388
17389     if (!appData.clockMode) return;
17390     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17391
17392     GetTimeMark(&tickStartTM);
17393     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17394       whiteTimeRemaining : blackTimeRemaining);
17395
17396    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17397     whiteNPS = blackNPS = -1;
17398     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17399        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17400         whiteNPS = first.nps;
17401     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17402        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17403         blackNPS = first.nps;
17404     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17405         whiteNPS = second.nps;
17406     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17407         blackNPS = second.nps;
17408     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17409
17410     StartClockTimer(intendedTickLength);
17411 }
17412
17413 char *
17414 TimeString (long ms)
17415 {
17416     long second, minute, hour, day;
17417     char *sign = "";
17418     static char buf[32];
17419
17420     if (ms > 0 && ms <= 9900) {
17421       /* convert milliseconds to tenths, rounding up */
17422       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17423
17424       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17425       return buf;
17426     }
17427
17428     /* convert milliseconds to seconds, rounding up */
17429     /* use floating point to avoid strangeness of integer division
17430        with negative dividends on many machines */
17431     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17432
17433     if (second < 0) {
17434         sign = "-";
17435         second = -second;
17436     }
17437
17438     day = second / (60 * 60 * 24);
17439     second = second % (60 * 60 * 24);
17440     hour = second / (60 * 60);
17441     second = second % (60 * 60);
17442     minute = second / 60;
17443     second = second % 60;
17444
17445     if (day > 0)
17446       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17447               sign, day, hour, minute, second);
17448     else if (hour > 0)
17449       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17450     else
17451       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17452
17453     return buf;
17454 }
17455
17456
17457 /*
17458  * This is necessary because some C libraries aren't ANSI C compliant yet.
17459  */
17460 char *
17461 StrStr (char *string, char *match)
17462 {
17463     int i, length;
17464
17465     length = strlen(match);
17466
17467     for (i = strlen(string) - length; i >= 0; i--, string++)
17468       if (!strncmp(match, string, length))
17469         return string;
17470
17471     return NULL;
17472 }
17473
17474 char *
17475 StrCaseStr (char *string, char *match)
17476 {
17477     int i, j, length;
17478
17479     length = strlen(match);
17480
17481     for (i = strlen(string) - length; i >= 0; i--, string++) {
17482         for (j = 0; j < length; j++) {
17483             if (ToLower(match[j]) != ToLower(string[j]))
17484               break;
17485         }
17486         if (j == length) return string;
17487     }
17488
17489     return NULL;
17490 }
17491
17492 #ifndef _amigados
17493 int
17494 StrCaseCmp (char *s1, char *s2)
17495 {
17496     char c1, c2;
17497
17498     for (;;) {
17499         c1 = ToLower(*s1++);
17500         c2 = ToLower(*s2++);
17501         if (c1 > c2) return 1;
17502         if (c1 < c2) return -1;
17503         if (c1 == NULLCHAR) return 0;
17504     }
17505 }
17506
17507
17508 int
17509 ToLower (int c)
17510 {
17511     return isupper(c) ? tolower(c) : c;
17512 }
17513
17514
17515 int
17516 ToUpper (int c)
17517 {
17518     return islower(c) ? toupper(c) : c;
17519 }
17520 #endif /* !_amigados    */
17521
17522 char *
17523 StrSave (char *s)
17524 {
17525   char *ret;
17526
17527   if ((ret = (char *) malloc(strlen(s) + 1)))
17528     {
17529       safeStrCpy(ret, s, strlen(s)+1);
17530     }
17531   return ret;
17532 }
17533
17534 char *
17535 StrSavePtr (char *s, char **savePtr)
17536 {
17537     if (*savePtr) {
17538         free(*savePtr);
17539     }
17540     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17541       safeStrCpy(*savePtr, s, strlen(s)+1);
17542     }
17543     return(*savePtr);
17544 }
17545
17546 char *
17547 PGNDate ()
17548 {
17549     time_t clock;
17550     struct tm *tm;
17551     char buf[MSG_SIZ];
17552
17553     clock = time((time_t *)NULL);
17554     tm = localtime(&clock);
17555     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17556             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17557     return StrSave(buf);
17558 }
17559
17560
17561 char *
17562 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17563 {
17564     int i, j, fromX, fromY, toX, toY;
17565     int whiteToPlay;
17566     char buf[MSG_SIZ];
17567     char *p, *q;
17568     int emptycount;
17569     ChessSquare piece;
17570
17571     whiteToPlay = (gameMode == EditPosition) ?
17572       !blackPlaysFirst : (move % 2 == 0);
17573     p = buf;
17574
17575     /* Piece placement data */
17576     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17577         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17578         emptycount = 0;
17579         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17580             if (boards[move][i][j] == EmptySquare) {
17581                 emptycount++;
17582             } else { ChessSquare piece = boards[move][i][j];
17583                 if (emptycount > 0) {
17584                     if(emptycount<10) /* [HGM] can be >= 10 */
17585                         *p++ = '0' + emptycount;
17586                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17587                     emptycount = 0;
17588                 }
17589                 if(PieceToChar(piece) == '+') {
17590                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17591                     *p++ = '+';
17592                     piece = (ChessSquare)(DEMOTED piece);
17593                 }
17594                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17595                 if(p[-1] == '~') {
17596                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17597                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17598                     *p++ = '~';
17599                 }
17600             }
17601         }
17602         if (emptycount > 0) {
17603             if(emptycount<10) /* [HGM] can be >= 10 */
17604                 *p++ = '0' + emptycount;
17605             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17606             emptycount = 0;
17607         }
17608         *p++ = '/';
17609     }
17610     *(p - 1) = ' ';
17611
17612     /* [HGM] print Crazyhouse or Shogi holdings */
17613     if( gameInfo.holdingsWidth ) {
17614         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17615         q = p;
17616         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17617             piece = boards[move][i][BOARD_WIDTH-1];
17618             if( piece != EmptySquare )
17619               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17620                   *p++ = PieceToChar(piece);
17621         }
17622         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17623             piece = boards[move][BOARD_HEIGHT-i-1][0];
17624             if( piece != EmptySquare )
17625               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17626                   *p++ = PieceToChar(piece);
17627         }
17628
17629         if( q == p ) *p++ = '-';
17630         *p++ = ']';
17631         *p++ = ' ';
17632     }
17633
17634     /* Active color */
17635     *p++ = whiteToPlay ? 'w' : 'b';
17636     *p++ = ' ';
17637
17638   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17639     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17640   } else {
17641   if(nrCastlingRights) {
17642      q = p;
17643      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17644        /* [HGM] write directly from rights */
17645            if(boards[move][CASTLING][2] != NoRights &&
17646               boards[move][CASTLING][0] != NoRights   )
17647                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17648            if(boards[move][CASTLING][2] != NoRights &&
17649               boards[move][CASTLING][1] != NoRights   )
17650                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17651            if(boards[move][CASTLING][5] != NoRights &&
17652               boards[move][CASTLING][3] != NoRights   )
17653                 *p++ = boards[move][CASTLING][3] + AAA;
17654            if(boards[move][CASTLING][5] != NoRights &&
17655               boards[move][CASTLING][4] != NoRights   )
17656                 *p++ = boards[move][CASTLING][4] + AAA;
17657      } else {
17658
17659         /* [HGM] write true castling rights */
17660         if( nrCastlingRights == 6 ) {
17661             int q, k=0;
17662             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17663                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17664             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17665                  boards[move][CASTLING][2] != NoRights  );
17666             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17667                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17668                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17669                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17670                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17671             }
17672             if(q) *p++ = 'Q';
17673             k = 0;
17674             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17675                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17676             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17677                  boards[move][CASTLING][5] != NoRights  );
17678             if(gameInfo.variant == VariantSChess) {
17679                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17680                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17681                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17682                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17683             }
17684             if(q) *p++ = 'q';
17685         }
17686      }
17687      if (q == p) *p++ = '-'; /* No castling rights */
17688      *p++ = ' ';
17689   }
17690
17691   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17692      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17693      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17694     /* En passant target square */
17695     if (move > backwardMostMove) {
17696         fromX = moveList[move - 1][0] - AAA;
17697         fromY = moveList[move - 1][1] - ONE;
17698         toX = moveList[move - 1][2] - AAA;
17699         toY = moveList[move - 1][3] - ONE;
17700         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17701             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17702             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17703             fromX == toX) {
17704             /* 2-square pawn move just happened */
17705             *p++ = toX + AAA;
17706             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17707         } else {
17708             *p++ = '-';
17709         }
17710     } else if(move == backwardMostMove) {
17711         // [HGM] perhaps we should always do it like this, and forget the above?
17712         if((signed char)boards[move][EP_STATUS] >= 0) {
17713             *p++ = boards[move][EP_STATUS] + AAA;
17714             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17715         } else {
17716             *p++ = '-';
17717         }
17718     } else {
17719         *p++ = '-';
17720     }
17721     *p++ = ' ';
17722   }
17723   }
17724
17725     if(moveCounts)
17726     {   int i = 0, j=move;
17727
17728         /* [HGM] find reversible plies */
17729         if (appData.debugMode) { int k;
17730             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17731             for(k=backwardMostMove; k<=forwardMostMove; k++)
17732                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17733
17734         }
17735
17736         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17737         if( j == backwardMostMove ) i += initialRulePlies;
17738         sprintf(p, "%d ", i);
17739         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17740
17741         /* Fullmove number */
17742         sprintf(p, "%d", (move / 2) + 1);
17743     } else *--p = NULLCHAR;
17744
17745     return StrSave(buf);
17746 }
17747
17748 Boolean
17749 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17750 {
17751     int i, j, k, w=0;
17752     char *p, c;
17753     int emptycount, virgin[BOARD_FILES];
17754     ChessSquare piece;
17755
17756     p = fen;
17757
17758     /* Piece placement data */
17759     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17760         j = 0;
17761         for (;;) {
17762             if (*p == '/' || *p == ' ' || *p == '[' ) {
17763                 if(j > w) w = j;
17764                 emptycount = gameInfo.boardWidth - j;
17765                 while (emptycount--)
17766                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17767                 if (*p == '/') p++;
17768                 else if(autoSize) { // we stumbled unexpectedly into end of board
17769                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17770                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17771                     }
17772                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17773                 }
17774                 break;
17775 #if(BOARD_FILES >= 10)
17776             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17777                 p++; emptycount=10;
17778                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17779                 while (emptycount--)
17780                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17781 #endif
17782             } else if (*p == '*') {
17783                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17784             } else if (isdigit(*p)) {
17785                 emptycount = *p++ - '0';
17786                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17787                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17788                 while (emptycount--)
17789                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17790             } else if (*p == '+' || isalpha(*p)) {
17791                 if (j >= gameInfo.boardWidth) return FALSE;
17792                 if(*p=='+') {
17793                     piece = CharToPiece(*++p);
17794                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17795                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17796                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17797                 } else piece = CharToPiece(*p++);
17798
17799                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17800                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17801                     piece = (ChessSquare) (PROMOTED piece);
17802                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17803                     p++;
17804                 }
17805                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17806             } else {
17807                 return FALSE;
17808             }
17809         }
17810     }
17811     while (*p == '/' || *p == ' ') p++;
17812
17813     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17814
17815     /* [HGM] by default clear Crazyhouse holdings, if present */
17816     if(gameInfo.holdingsWidth) {
17817        for(i=0; i<BOARD_HEIGHT; i++) {
17818            board[i][0]             = EmptySquare; /* black holdings */
17819            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17820            board[i][1]             = (ChessSquare) 0; /* black counts */
17821            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17822        }
17823     }
17824
17825     /* [HGM] look for Crazyhouse holdings here */
17826     while(*p==' ') p++;
17827     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17828         if(*p == '[') p++;
17829         if(*p == '-' ) p++; /* empty holdings */ else {
17830             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17831             /* if we would allow FEN reading to set board size, we would   */
17832             /* have to add holdings and shift the board read so far here   */
17833             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17834                 p++;
17835                 if((int) piece >= (int) BlackPawn ) {
17836                     i = (int)piece - (int)BlackPawn;
17837                     i = PieceToNumber((ChessSquare)i);
17838                     if( i >= gameInfo.holdingsSize ) return FALSE;
17839                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17840                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17841                 } else {
17842                     i = (int)piece - (int)WhitePawn;
17843                     i = PieceToNumber((ChessSquare)i);
17844                     if( i >= gameInfo.holdingsSize ) return FALSE;
17845                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17846                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17847                 }
17848             }
17849         }
17850         if(*p == ']') p++;
17851     }
17852
17853     while(*p == ' ') p++;
17854
17855     /* Active color */
17856     c = *p++;
17857     if(appData.colorNickNames) {
17858       if( c == appData.colorNickNames[0] ) c = 'w'; else
17859       if( c == appData.colorNickNames[1] ) c = 'b';
17860     }
17861     switch (c) {
17862       case 'w':
17863         *blackPlaysFirst = FALSE;
17864         break;
17865       case 'b':
17866         *blackPlaysFirst = TRUE;
17867         break;
17868       default:
17869         return FALSE;
17870     }
17871
17872     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17873     /* return the extra info in global variiables             */
17874
17875     /* set defaults in case FEN is incomplete */
17876     board[EP_STATUS] = EP_UNKNOWN;
17877     for(i=0; i<nrCastlingRights; i++ ) {
17878         board[CASTLING][i] =
17879             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17880     }   /* assume possible unless obviously impossible */
17881     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17882     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17883     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17884                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17885     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17886     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17887     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17888                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17889     FENrulePlies = 0;
17890
17891     while(*p==' ') p++;
17892     if(nrCastlingRights) {
17893       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17894       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17895           /* castling indicator present, so default becomes no castlings */
17896           for(i=0; i<nrCastlingRights; i++ ) {
17897                  board[CASTLING][i] = NoRights;
17898           }
17899       }
17900       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17901              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17902              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17903              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17904         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17905
17906         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17907             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17908             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17909         }
17910         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17911             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17912         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17913                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17914         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17915                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17916         switch(c) {
17917           case'K':
17918               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17919               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17920               board[CASTLING][2] = whiteKingFile;
17921               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17922               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17923               break;
17924           case'Q':
17925               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17926               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17927               board[CASTLING][2] = whiteKingFile;
17928               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17929               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17930               break;
17931           case'k':
17932               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17933               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17934               board[CASTLING][5] = blackKingFile;
17935               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17936               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17937               break;
17938           case'q':
17939               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17940               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17941               board[CASTLING][5] = blackKingFile;
17942               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17943               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17944           case '-':
17945               break;
17946           default: /* FRC castlings */
17947               if(c >= 'a') { /* black rights */
17948                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17949                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17950                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17951                   if(i == BOARD_RGHT) break;
17952                   board[CASTLING][5] = i;
17953                   c -= AAA;
17954                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17955                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17956                   if(c > i)
17957                       board[CASTLING][3] = c;
17958                   else
17959                       board[CASTLING][4] = c;
17960               } else { /* white rights */
17961                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17962                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17963                     if(board[0][i] == WhiteKing) break;
17964                   if(i == BOARD_RGHT) break;
17965                   board[CASTLING][2] = i;
17966                   c -= AAA - 'a' + 'A';
17967                   if(board[0][c] >= WhiteKing) break;
17968                   if(c > i)
17969                       board[CASTLING][0] = c;
17970                   else
17971                       board[CASTLING][1] = c;
17972               }
17973         }
17974       }
17975       for(i=0; i<nrCastlingRights; i++)
17976         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17977       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17978     if (appData.debugMode) {
17979         fprintf(debugFP, "FEN castling rights:");
17980         for(i=0; i<nrCastlingRights; i++)
17981         fprintf(debugFP, " %d", board[CASTLING][i]);
17982         fprintf(debugFP, "\n");
17983     }
17984
17985       while(*p==' ') p++;
17986     }
17987
17988     /* read e.p. field in games that know e.p. capture */
17989     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17990        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17991        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17992       if(*p=='-') {
17993         p++; board[EP_STATUS] = EP_NONE;
17994       } else {
17995          char c = *p++ - AAA;
17996
17997          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17998          if(*p >= '0' && *p <='9') p++;
17999          board[EP_STATUS] = c;
18000       }
18001     }
18002
18003
18004     if(sscanf(p, "%d", &i) == 1) {
18005         FENrulePlies = i; /* 50-move ply counter */
18006         /* (The move number is still ignored)    */
18007     }
18008
18009     return TRUE;
18010 }
18011
18012 void
18013 EditPositionPasteFEN (char *fen)
18014 {
18015   if (fen != NULL) {
18016     Board initial_position;
18017
18018     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18019       DisplayError(_("Bad FEN position in clipboard"), 0);
18020       return ;
18021     } else {
18022       int savedBlackPlaysFirst = blackPlaysFirst;
18023       EditPositionEvent();
18024       blackPlaysFirst = savedBlackPlaysFirst;
18025       CopyBoard(boards[0], initial_position);
18026       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18027       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18028       DisplayBothClocks();
18029       DrawPosition(FALSE, boards[currentMove]);
18030     }
18031   }
18032 }
18033
18034 static char cseq[12] = "\\   ";
18035
18036 Boolean
18037 set_cont_sequence (char *new_seq)
18038 {
18039     int len;
18040     Boolean ret;
18041
18042     // handle bad attempts to set the sequence
18043         if (!new_seq)
18044                 return 0; // acceptable error - no debug
18045
18046     len = strlen(new_seq);
18047     ret = (len > 0) && (len < sizeof(cseq));
18048     if (ret)
18049       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18050     else if (appData.debugMode)
18051       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18052     return ret;
18053 }
18054
18055 /*
18056     reformat a source message so words don't cross the width boundary.  internal
18057     newlines are not removed.  returns the wrapped size (no null character unless
18058     included in source message).  If dest is NULL, only calculate the size required
18059     for the dest buffer.  lp argument indicats line position upon entry, and it's
18060     passed back upon exit.
18061 */
18062 int
18063 wrap (char *dest, char *src, int count, int width, int *lp)
18064 {
18065     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18066
18067     cseq_len = strlen(cseq);
18068     old_line = line = *lp;
18069     ansi = len = clen = 0;
18070
18071     for (i=0; i < count; i++)
18072     {
18073         if (src[i] == '\033')
18074             ansi = 1;
18075
18076         // if we hit the width, back up
18077         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18078         {
18079             // store i & len in case the word is too long
18080             old_i = i, old_len = len;
18081
18082             // find the end of the last word
18083             while (i && src[i] != ' ' && src[i] != '\n')
18084             {
18085                 i--;
18086                 len--;
18087             }
18088
18089             // word too long?  restore i & len before splitting it
18090             if ((old_i-i+clen) >= width)
18091             {
18092                 i = old_i;
18093                 len = old_len;
18094             }
18095
18096             // extra space?
18097             if (i && src[i-1] == ' ')
18098                 len--;
18099
18100             if (src[i] != ' ' && src[i] != '\n')
18101             {
18102                 i--;
18103                 if (len)
18104                     len--;
18105             }
18106
18107             // now append the newline and continuation sequence
18108             if (dest)
18109                 dest[len] = '\n';
18110             len++;
18111             if (dest)
18112                 strncpy(dest+len, cseq, cseq_len);
18113             len += cseq_len;
18114             line = cseq_len;
18115             clen = cseq_len;
18116             continue;
18117         }
18118
18119         if (dest)
18120             dest[len] = src[i];
18121         len++;
18122         if (!ansi)
18123             line++;
18124         if (src[i] == '\n')
18125             line = 0;
18126         if (src[i] == 'm')
18127             ansi = 0;
18128     }
18129     if (dest && appData.debugMode)
18130     {
18131         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18132             count, width, line, len, *lp);
18133         show_bytes(debugFP, src, count);
18134         fprintf(debugFP, "\ndest: ");
18135         show_bytes(debugFP, dest, len);
18136         fprintf(debugFP, "\n");
18137     }
18138     *lp = dest ? line : old_line;
18139
18140     return len;
18141 }
18142
18143 // [HGM] vari: routines for shelving variations
18144 Boolean modeRestore = FALSE;
18145
18146 void
18147 PushInner (int firstMove, int lastMove)
18148 {
18149         int i, j, nrMoves = lastMove - firstMove;
18150
18151         // push current tail of game on stack
18152         savedResult[storedGames] = gameInfo.result;
18153         savedDetails[storedGames] = gameInfo.resultDetails;
18154         gameInfo.resultDetails = NULL;
18155         savedFirst[storedGames] = firstMove;
18156         savedLast [storedGames] = lastMove;
18157         savedFramePtr[storedGames] = framePtr;
18158         framePtr -= nrMoves; // reserve space for the boards
18159         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18160             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18161             for(j=0; j<MOVE_LEN; j++)
18162                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18163             for(j=0; j<2*MOVE_LEN; j++)
18164                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18165             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18166             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18167             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18168             pvInfoList[firstMove+i-1].depth = 0;
18169             commentList[framePtr+i] = commentList[firstMove+i];
18170             commentList[firstMove+i] = NULL;
18171         }
18172
18173         storedGames++;
18174         forwardMostMove = firstMove; // truncate game so we can start variation
18175 }
18176
18177 void
18178 PushTail (int firstMove, int lastMove)
18179 {
18180         if(appData.icsActive) { // only in local mode
18181                 forwardMostMove = currentMove; // mimic old ICS behavior
18182                 return;
18183         }
18184         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18185
18186         PushInner(firstMove, lastMove);
18187         if(storedGames == 1) GreyRevert(FALSE);
18188         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18189 }
18190
18191 void
18192 PopInner (Boolean annotate)
18193 {
18194         int i, j, nrMoves;
18195         char buf[8000], moveBuf[20];
18196
18197         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18198         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18199         nrMoves = savedLast[storedGames] - currentMove;
18200         if(annotate) {
18201                 int cnt = 10;
18202                 if(!WhiteOnMove(currentMove))
18203                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18204                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18205                 for(i=currentMove; i<forwardMostMove; i++) {
18206                         if(WhiteOnMove(i))
18207                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18208                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18209                         strcat(buf, moveBuf);
18210                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18211                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18212                 }
18213                 strcat(buf, ")");
18214         }
18215         for(i=1; i<=nrMoves; i++) { // copy last variation back
18216             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18217             for(j=0; j<MOVE_LEN; j++)
18218                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18219             for(j=0; j<2*MOVE_LEN; j++)
18220                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18221             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18222             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18223             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18224             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18225             commentList[currentMove+i] = commentList[framePtr+i];
18226             commentList[framePtr+i] = NULL;
18227         }
18228         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18229         framePtr = savedFramePtr[storedGames];
18230         gameInfo.result = savedResult[storedGames];
18231         if(gameInfo.resultDetails != NULL) {
18232             free(gameInfo.resultDetails);
18233       }
18234         gameInfo.resultDetails = savedDetails[storedGames];
18235         forwardMostMove = currentMove + nrMoves;
18236 }
18237
18238 Boolean
18239 PopTail (Boolean annotate)
18240 {
18241         if(appData.icsActive) return FALSE; // only in local mode
18242         if(!storedGames) return FALSE; // sanity
18243         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18244
18245         PopInner(annotate);
18246         if(currentMove < forwardMostMove) ForwardEvent(); else
18247         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18248
18249         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18250         return TRUE;
18251 }
18252
18253 void
18254 CleanupTail ()
18255 {       // remove all shelved variations
18256         int i;
18257         for(i=0; i<storedGames; i++) {
18258             if(savedDetails[i])
18259                 free(savedDetails[i]);
18260             savedDetails[i] = NULL;
18261         }
18262         for(i=framePtr; i<MAX_MOVES; i++) {
18263                 if(commentList[i]) free(commentList[i]);
18264                 commentList[i] = NULL;
18265         }
18266         framePtr = MAX_MOVES-1;
18267         storedGames = 0;
18268 }
18269
18270 void
18271 LoadVariation (int index, char *text)
18272 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18273         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18274         int level = 0, move;
18275
18276         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18277         // first find outermost bracketing variation
18278         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18279             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18280                 if(*p == '{') wait = '}'; else
18281                 if(*p == '[') wait = ']'; else
18282                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18283                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18284             }
18285             if(*p == wait) wait = NULLCHAR; // closing ]} found
18286             p++;
18287         }
18288         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18289         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18290         end[1] = NULLCHAR; // clip off comment beyond variation
18291         ToNrEvent(currentMove-1);
18292         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18293         // kludge: use ParsePV() to append variation to game
18294         move = currentMove;
18295         ParsePV(start, TRUE, TRUE);
18296         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18297         ClearPremoveHighlights();
18298         CommentPopDown();
18299         ToNrEvent(currentMove+1);
18300 }
18301
18302 void
18303 LoadTheme ()
18304 {
18305     char *p, *q, buf[MSG_SIZ];
18306     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18307         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18308         ParseArgsFromString(buf);
18309         ActivateTheme(TRUE); // also redo colors
18310         return;
18311     }
18312     p = nickName;
18313     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18314     {
18315         int len;
18316         q = appData.themeNames;
18317         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18318       if(appData.useBitmaps) {
18319         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18320                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18321                 appData.liteBackTextureMode,
18322                 appData.darkBackTextureMode );
18323       } else {
18324         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18325                 Col2Text(2),   // lightSquareColor
18326                 Col2Text(3) ); // darkSquareColor
18327       }
18328       if(appData.useBorder) {
18329         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18330                 appData.border);
18331       } else {
18332         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18333       }
18334       if(appData.useFont) {
18335         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18336                 appData.renderPiecesWithFont,
18337                 appData.fontToPieceTable,
18338                 Col2Text(9),    // appData.fontBackColorWhite
18339                 Col2Text(10) ); // appData.fontForeColorBlack
18340       } else {
18341         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18342                 appData.pieceDirectory);
18343         if(!appData.pieceDirectory[0])
18344           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18345                 Col2Text(0),   // whitePieceColor
18346                 Col2Text(1) ); // blackPieceColor
18347       }
18348       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18349                 Col2Text(4),   // highlightSquareColor
18350                 Col2Text(5) ); // premoveHighlightColor
18351         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18352         if(insert != q) insert[-1] = NULLCHAR;
18353         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18354         if(q)   free(q);
18355     }
18356     ActivateTheme(FALSE);
18357 }