Fix printing of engine-output headers
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 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         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5651                 Collapse(origIndex - lineStart);
5652                 return FALSE;
5653         }
5654         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5655         *start = startPV; *end = index-1;
5656         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5657         return TRUE;
5658 }
5659
5660 char *
5661 PvToSAN (char *pv)
5662 {
5663         static char buf[10*MSG_SIZ];
5664         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5665         *buf = NULLCHAR;
5666         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5667         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5668         for(i = forwardMostMove; i<endPV; i++){
5669             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5670             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5671             k += strlen(buf+k);
5672         }
5673         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5674         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5675         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5676         endPV = savedEnd;
5677         return buf;
5678 }
5679
5680 Boolean
5681 LoadPV (int x, int y)
5682 { // called on right mouse click to load PV
5683   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5684   lastX = x; lastY = y;
5685   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5686   extendGame = FALSE;
5687   return TRUE;
5688 }
5689
5690 void
5691 UnLoadPV ()
5692 {
5693   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5694   if(endPV < 0) return;
5695   if(appData.autoCopyPV) CopyFENToClipboard();
5696   endPV = -1;
5697   if(extendGame && currentMove > forwardMostMove) {
5698         Boolean saveAnimate = appData.animate;
5699         if(pushed) {
5700             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5701                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5702             } else storedGames--; // abandon shelved tail of original game
5703         }
5704         pushed = FALSE;
5705         forwardMostMove = currentMove;
5706         currentMove = oldFMM;
5707         appData.animate = FALSE;
5708         ToNrEvent(forwardMostMove);
5709         appData.animate = saveAnimate;
5710   }
5711   currentMove = forwardMostMove;
5712   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5713   ClearPremoveHighlights();
5714   DrawPosition(TRUE, boards[currentMove]);
5715 }
5716
5717 void
5718 MovePV (int x, int y, int h)
5719 { // step through PV based on mouse coordinates (called on mouse move)
5720   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5721
5722   // we must somehow check if right button is still down (might be released off board!)
5723   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5724   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5725   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5726   if(!step) return;
5727   lastX = x; lastY = y;
5728
5729   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5730   if(endPV < 0) return;
5731   if(y < margin) step = 1; else
5732   if(y > h - margin) step = -1;
5733   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5734   currentMove += step;
5735   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5736   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5737                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5738   DrawPosition(FALSE, boards[currentMove]);
5739 }
5740
5741
5742 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5743 // All positions will have equal probability, but the current method will not provide a unique
5744 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5745 #define DARK 1
5746 #define LITE 2
5747 #define ANY 3
5748
5749 int squaresLeft[4];
5750 int piecesLeft[(int)BlackPawn];
5751 int seed, nrOfShuffles;
5752
5753 void
5754 GetPositionNumber ()
5755 {       // sets global variable seed
5756         int i;
5757
5758         seed = appData.defaultFrcPosition;
5759         if(seed < 0) { // randomize based on time for negative FRC position numbers
5760                 for(i=0; i<50; i++) seed += random();
5761                 seed = random() ^ random() >> 8 ^ random() << 8;
5762                 if(seed<0) seed = -seed;
5763         }
5764 }
5765
5766 int
5767 put (Board board, int pieceType, int rank, int n, int shade)
5768 // put the piece on the (n-1)-th empty squares of the given shade
5769 {
5770         int i;
5771
5772         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5773                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5774                         board[rank][i] = (ChessSquare) pieceType;
5775                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5776                         squaresLeft[ANY]--;
5777                         piecesLeft[pieceType]--;
5778                         return i;
5779                 }
5780         }
5781         return -1;
5782 }
5783
5784
5785 void
5786 AddOnePiece (Board board, int pieceType, int rank, int shade)
5787 // calculate where the next piece goes, (any empty square), and put it there
5788 {
5789         int i;
5790
5791         i = seed % squaresLeft[shade];
5792         nrOfShuffles *= squaresLeft[shade];
5793         seed /= squaresLeft[shade];
5794         put(board, pieceType, rank, i, shade);
5795 }
5796
5797 void
5798 AddTwoPieces (Board board, int pieceType, int rank)
5799 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5800 {
5801         int i, n=squaresLeft[ANY], j=n-1, k;
5802
5803         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5804         i = seed % k;  // pick one
5805         nrOfShuffles *= k;
5806         seed /= k;
5807         while(i >= j) i -= j--;
5808         j = n - 1 - j; i += j;
5809         put(board, pieceType, rank, j, ANY);
5810         put(board, pieceType, rank, i, ANY);
5811 }
5812
5813 void
5814 SetUpShuffle (Board board, int number)
5815 {
5816         int i, p, first=1;
5817
5818         GetPositionNumber(); nrOfShuffles = 1;
5819
5820         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5821         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5822         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5823
5824         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5825
5826         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5827             p = (int) board[0][i];
5828             if(p < (int) BlackPawn) piecesLeft[p] ++;
5829             board[0][i] = EmptySquare;
5830         }
5831
5832         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5833             // shuffles restricted to allow normal castling put KRR first
5834             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5835                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5836             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5837                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5838             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5839                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5840             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5841                 put(board, WhiteRook, 0, 0, ANY);
5842             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5843         }
5844
5845         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5846             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5847             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5848                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5849                 while(piecesLeft[p] >= 2) {
5850                     AddOnePiece(board, p, 0, LITE);
5851                     AddOnePiece(board, p, 0, DARK);
5852                 }
5853                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5854             }
5855
5856         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5857             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5858             // but we leave King and Rooks for last, to possibly obey FRC restriction
5859             if(p == (int)WhiteRook) continue;
5860             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5861             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5862         }
5863
5864         // now everything is placed, except perhaps King (Unicorn) and Rooks
5865
5866         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5867             // Last King gets castling rights
5868             while(piecesLeft[(int)WhiteUnicorn]) {
5869                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5870                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5871             }
5872
5873             while(piecesLeft[(int)WhiteKing]) {
5874                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5875                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5876             }
5877
5878
5879         } else {
5880             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5881             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5882         }
5883
5884         // Only Rooks can be left; simply place them all
5885         while(piecesLeft[(int)WhiteRook]) {
5886                 i = put(board, WhiteRook, 0, 0, ANY);
5887                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5888                         if(first) {
5889                                 first=0;
5890                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5891                         }
5892                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5893                 }
5894         }
5895         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5896             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5897         }
5898
5899         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5900 }
5901
5902 int
5903 SetCharTable (char *table, const char * map)
5904 /* [HGM] moved here from winboard.c because of its general usefulness */
5905 /*       Basically a safe strcpy that uses the last character as King */
5906 {
5907     int result = FALSE; int NrPieces;
5908
5909     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5910                     && NrPieces >= 12 && !(NrPieces&1)) {
5911         int i; /* [HGM] Accept even length from 12 to 34 */
5912
5913         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5914         for( i=0; i<NrPieces/2-1; i++ ) {
5915             table[i] = map[i];
5916             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5917         }
5918         table[(int) WhiteKing]  = map[NrPieces/2-1];
5919         table[(int) BlackKing]  = map[NrPieces-1];
5920
5921         result = TRUE;
5922     }
5923
5924     return result;
5925 }
5926
5927 void
5928 Prelude (Board board)
5929 {       // [HGM] superchess: random selection of exo-pieces
5930         int i, j, k; ChessSquare p;
5931         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5932
5933         GetPositionNumber(); // use FRC position number
5934
5935         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5936             SetCharTable(pieceToChar, appData.pieceToCharTable);
5937             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5938                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5939         }
5940
5941         j = seed%4;                 seed /= 4;
5942         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5943         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5944         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5945         j = seed%3 + (seed%3 >= j); seed /= 3;
5946         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5947         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5948         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5949         j = seed%3;                 seed /= 3;
5950         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5951         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5952         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5953         j = seed%2 + (seed%2 >= j); seed /= 2;
5954         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5955         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5956         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5957         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5958         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5959         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5960         put(board, exoPieces[0],    0, 0, ANY);
5961         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5962 }
5963
5964 void
5965 InitPosition (int redraw)
5966 {
5967     ChessSquare (* pieces)[BOARD_FILES];
5968     int i, j, pawnRow=1, pieceRows=1, overrule,
5969     oldx = gameInfo.boardWidth,
5970     oldy = gameInfo.boardHeight,
5971     oldh = gameInfo.holdingsWidth;
5972     static int oldv;
5973
5974     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5975
5976     /* [AS] Initialize pv info list [HGM] and game status */
5977     {
5978         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5979             pvInfoList[i].depth = 0;
5980             boards[i][EP_STATUS] = EP_NONE;
5981             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5982         }
5983
5984         initialRulePlies = 0; /* 50-move counter start */
5985
5986         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5987         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5988     }
5989
5990
5991     /* [HGM] logic here is completely changed. In stead of full positions */
5992     /* the initialized data only consist of the two backranks. The switch */
5993     /* selects which one we will use, which is than copied to the Board   */
5994     /* initialPosition, which for the rest is initialized by Pawns and    */
5995     /* empty squares. This initial position is then copied to boards[0],  */
5996     /* possibly after shuffling, so that it remains available.            */
5997
5998     gameInfo.holdingsWidth = 0; /* default board sizes */
5999     gameInfo.boardWidth    = 8;
6000     gameInfo.boardHeight   = 8;
6001     gameInfo.holdingsSize  = 0;
6002     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6003     for(i=0; i<BOARD_FILES-2; i++)
6004       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6005     initialPosition[EP_STATUS] = EP_NONE;
6006     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6007     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6008          SetCharTable(pieceNickName, appData.pieceNickNames);
6009     else SetCharTable(pieceNickName, "............");
6010     pieces = FIDEArray;
6011
6012     switch (gameInfo.variant) {
6013     case VariantFischeRandom:
6014       shuffleOpenings = TRUE;
6015     default:
6016       break;
6017     case VariantShatranj:
6018       pieces = ShatranjArray;
6019       nrCastlingRights = 0;
6020       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6021       break;
6022     case VariantMakruk:
6023       pieces = makrukArray;
6024       nrCastlingRights = 0;
6025       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6026       break;
6027     case VariantASEAN:
6028       pieces = aseanArray;
6029       nrCastlingRights = 0;
6030       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6031       break;
6032     case VariantTwoKings:
6033       pieces = twoKingsArray;
6034       break;
6035     case VariantGrand:
6036       pieces = GrandArray;
6037       nrCastlingRights = 0;
6038       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6039       gameInfo.boardWidth = 10;
6040       gameInfo.boardHeight = 10;
6041       gameInfo.holdingsSize = 7;
6042       break;
6043     case VariantCapaRandom:
6044       shuffleOpenings = TRUE;
6045     case VariantCapablanca:
6046       pieces = CapablancaArray;
6047       gameInfo.boardWidth = 10;
6048       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6049       break;
6050     case VariantGothic:
6051       pieces = GothicArray;
6052       gameInfo.boardWidth = 10;
6053       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6054       break;
6055     case VariantSChess:
6056       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6057       gameInfo.holdingsSize = 7;
6058       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6059       break;
6060     case VariantJanus:
6061       pieces = JanusArray;
6062       gameInfo.boardWidth = 10;
6063       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6064       nrCastlingRights = 6;
6065         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6066         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6067         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6068         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6069         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6070         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6071       break;
6072     case VariantFalcon:
6073       pieces = FalconArray;
6074       gameInfo.boardWidth = 10;
6075       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6076       break;
6077     case VariantXiangqi:
6078       pieces = XiangqiArray;
6079       gameInfo.boardWidth  = 9;
6080       gameInfo.boardHeight = 10;
6081       nrCastlingRights = 0;
6082       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6083       break;
6084     case VariantShogi:
6085       pieces = ShogiArray;
6086       gameInfo.boardWidth  = 9;
6087       gameInfo.boardHeight = 9;
6088       gameInfo.holdingsSize = 7;
6089       nrCastlingRights = 0;
6090       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6091       break;
6092     case VariantChu:
6093       pieces = ChuArray; pieceRows = 3;
6094       gameInfo.boardWidth  = 12;
6095       gameInfo.boardHeight = 12;
6096       nrCastlingRights = 0;
6097       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6098                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6099       break;
6100     case VariantCourier:
6101       pieces = CourierArray;
6102       gameInfo.boardWidth  = 12;
6103       nrCastlingRights = 0;
6104       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6105       break;
6106     case VariantKnightmate:
6107       pieces = KnightmateArray;
6108       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6109       break;
6110     case VariantSpartan:
6111       pieces = SpartanArray;
6112       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6113       break;
6114     case VariantLion:
6115       pieces = lionArray;
6116       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6117       break;
6118     case VariantChuChess:
6119       pieces = ChuChessArray;
6120       gameInfo.boardWidth = 10;
6121       gameInfo.boardHeight = 10;
6122       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6123       break;
6124     case VariantFairy:
6125       pieces = fairyArray;
6126       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6127       break;
6128     case VariantGreat:
6129       pieces = GreatArray;
6130       gameInfo.boardWidth = 10;
6131       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6132       gameInfo.holdingsSize = 8;
6133       break;
6134     case VariantSuper:
6135       pieces = FIDEArray;
6136       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6137       gameInfo.holdingsSize = 8;
6138       startedFromSetupPosition = TRUE;
6139       break;
6140     case VariantCrazyhouse:
6141     case VariantBughouse:
6142       pieces = FIDEArray;
6143       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6144       gameInfo.holdingsSize = 5;
6145       break;
6146     case VariantWildCastle:
6147       pieces = FIDEArray;
6148       /* !!?shuffle with kings guaranteed to be on d or e file */
6149       shuffleOpenings = 1;
6150       break;
6151     case VariantNoCastle:
6152       pieces = FIDEArray;
6153       nrCastlingRights = 0;
6154       /* !!?unconstrained back-rank shuffle */
6155       shuffleOpenings = 1;
6156       break;
6157     }
6158
6159     overrule = 0;
6160     if(appData.NrFiles >= 0) {
6161         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6162         gameInfo.boardWidth = appData.NrFiles;
6163     }
6164     if(appData.NrRanks >= 0) {
6165         gameInfo.boardHeight = appData.NrRanks;
6166     }
6167     if(appData.holdingsSize >= 0) {
6168         i = appData.holdingsSize;
6169         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6170         gameInfo.holdingsSize = i;
6171     }
6172     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6173     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6174         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6175
6176     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6177     if(pawnRow < 1) pawnRow = 1;
6178     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6179        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6180     if(gameInfo.variant == VariantChu) pawnRow = 3;
6181
6182     /* User pieceToChar list overrules defaults */
6183     if(appData.pieceToCharTable != NULL)
6184         SetCharTable(pieceToChar, appData.pieceToCharTable);
6185
6186     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6187
6188         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6189             s = (ChessSquare) 0; /* account holding counts in guard band */
6190         for( i=0; i<BOARD_HEIGHT; i++ )
6191             initialPosition[i][j] = s;
6192
6193         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6194         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6195         initialPosition[pawnRow][j] = WhitePawn;
6196         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6197         if(gameInfo.variant == VariantXiangqi) {
6198             if(j&1) {
6199                 initialPosition[pawnRow][j] =
6200                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6201                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6202                    initialPosition[2][j] = WhiteCannon;
6203                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6204                 }
6205             }
6206         }
6207         if(gameInfo.variant == VariantChu) {
6208              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6209                initialPosition[pawnRow+1][j] = WhiteCobra,
6210                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6211              for(i=1; i<pieceRows; i++) {
6212                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6213                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6214              }
6215         }
6216         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6217             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6218                initialPosition[0][j] = WhiteRook;
6219                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6220             }
6221         }
6222         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6223     }
6224     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6225     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6226
6227             j=BOARD_LEFT+1;
6228             initialPosition[1][j] = WhiteBishop;
6229             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6230             j=BOARD_RGHT-2;
6231             initialPosition[1][j] = WhiteRook;
6232             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6233     }
6234
6235     if( nrCastlingRights == -1) {
6236         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6237         /*       This sets default castling rights from none to normal corners   */
6238         /* Variants with other castling rights must set them themselves above    */
6239         nrCastlingRights = 6;
6240
6241         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6242         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6243         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6244         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6245         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6246         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6247      }
6248
6249      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6250      if(gameInfo.variant == VariantGreat) { // promotion commoners
6251         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6252         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6253         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6254         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6255      }
6256      if( gameInfo.variant == VariantSChess ) {
6257       initialPosition[1][0] = BlackMarshall;
6258       initialPosition[2][0] = BlackAngel;
6259       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6260       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6261       initialPosition[1][1] = initialPosition[2][1] =
6262       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6263      }
6264   if (appData.debugMode) {
6265     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6266   }
6267     if(shuffleOpenings) {
6268         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6269         startedFromSetupPosition = TRUE;
6270     }
6271     if(startedFromPositionFile) {
6272       /* [HGM] loadPos: use PositionFile for every new game */
6273       CopyBoard(initialPosition, filePosition);
6274       for(i=0; i<nrCastlingRights; i++)
6275           initialRights[i] = filePosition[CASTLING][i];
6276       startedFromSetupPosition = TRUE;
6277     }
6278
6279     CopyBoard(boards[0], initialPosition);
6280
6281     if(oldx != gameInfo.boardWidth ||
6282        oldy != gameInfo.boardHeight ||
6283        oldv != gameInfo.variant ||
6284        oldh != gameInfo.holdingsWidth
6285                                          )
6286             InitDrawingSizes(-2 ,0);
6287
6288     oldv = gameInfo.variant;
6289     if (redraw)
6290       DrawPosition(TRUE, boards[currentMove]);
6291 }
6292
6293 void
6294 SendBoard (ChessProgramState *cps, int moveNum)
6295 {
6296     char message[MSG_SIZ];
6297
6298     if (cps->useSetboard) {
6299       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6300       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6301       SendToProgram(message, cps);
6302       free(fen);
6303
6304     } else {
6305       ChessSquare *bp;
6306       int i, j, left=0, right=BOARD_WIDTH;
6307       /* Kludge to set black to move, avoiding the troublesome and now
6308        * deprecated "black" command.
6309        */
6310       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6311         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6312
6313       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6314
6315       SendToProgram("edit\n", cps);
6316       SendToProgram("#\n", cps);
6317       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6318         bp = &boards[moveNum][i][left];
6319         for (j = left; j < right; j++, bp++) {
6320           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6321           if ((int) *bp < (int) BlackPawn) {
6322             if(j == BOARD_RGHT+1)
6323                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6324             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6325             if(message[0] == '+' || message[0] == '~') {
6326               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6327                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6328                         AAA + j, ONE + i);
6329             }
6330             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6331                 message[1] = BOARD_RGHT   - 1 - j + '1';
6332                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6333             }
6334             SendToProgram(message, cps);
6335           }
6336         }
6337       }
6338
6339       SendToProgram("c\n", cps);
6340       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6341         bp = &boards[moveNum][i][left];
6342         for (j = left; j < right; j++, bp++) {
6343           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6344           if (((int) *bp != (int) EmptySquare)
6345               && ((int) *bp >= (int) BlackPawn)) {
6346             if(j == BOARD_LEFT-2)
6347                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6348             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6349                     AAA + j, ONE + i);
6350             if(message[0] == '+' || message[0] == '~') {
6351               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6352                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6353                         AAA + j, ONE + i);
6354             }
6355             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6356                 message[1] = BOARD_RGHT   - 1 - j + '1';
6357                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6358             }
6359             SendToProgram(message, cps);
6360           }
6361         }
6362       }
6363
6364       SendToProgram(".\n", cps);
6365     }
6366     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6367 }
6368
6369 char exclusionHeader[MSG_SIZ];
6370 int exCnt, excludePtr;
6371 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6372 static Exclusion excluTab[200];
6373 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6374
6375 static void
6376 WriteMap (int s)
6377 {
6378     int j;
6379     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6380     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6381 }
6382
6383 static void
6384 ClearMap ()
6385 {
6386     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6387     excludePtr = 24; exCnt = 0;
6388     WriteMap(0);
6389 }
6390
6391 static void
6392 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6393 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6394     char buf[2*MOVE_LEN], *p;
6395     Exclusion *e = excluTab;
6396     int i;
6397     for(i=0; i<exCnt; i++)
6398         if(e[i].ff == fromX && e[i].fr == fromY &&
6399            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6400     if(i == exCnt) { // was not in exclude list; add it
6401         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6402         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6403             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6404             return; // abort
6405         }
6406         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6407         excludePtr++; e[i].mark = excludePtr++;
6408         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6409         exCnt++;
6410     }
6411     exclusionHeader[e[i].mark] = state;
6412 }
6413
6414 static int
6415 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6416 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6417     char buf[MSG_SIZ];
6418     int j, k;
6419     ChessMove moveType;
6420     if((signed char)promoChar == -1) { // kludge to indicate best move
6421         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6422             return 1; // if unparsable, abort
6423     }
6424     // update exclusion map (resolving toggle by consulting existing state)
6425     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6426     j = k%8; k >>= 3;
6427     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6428     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6429          excludeMap[k] |=   1<<j;
6430     else excludeMap[k] &= ~(1<<j);
6431     // update header
6432     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6433     // inform engine
6434     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6435     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6436     SendToBoth(buf);
6437     return (state == '+');
6438 }
6439
6440 static void
6441 ExcludeClick (int index)
6442 {
6443     int i, j;
6444     Exclusion *e = excluTab;
6445     if(index < 25) { // none, best or tail clicked
6446         if(index < 13) { // none: include all
6447             WriteMap(0); // clear map
6448             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6449             SendToBoth("include all\n"); // and inform engine
6450         } else if(index > 18) { // tail
6451             if(exclusionHeader[19] == '-') { // tail was excluded
6452                 SendToBoth("include all\n");
6453                 WriteMap(0); // clear map completely
6454                 // now re-exclude selected moves
6455                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6456                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6457             } else { // tail was included or in mixed state
6458                 SendToBoth("exclude all\n");
6459                 WriteMap(0xFF); // fill map completely
6460                 // now re-include selected moves
6461                 j = 0; // count them
6462                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6463                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6464                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6465             }
6466         } else { // best
6467             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6468         }
6469     } else {
6470         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6471             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6472             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6473             break;
6474         }
6475     }
6476 }
6477
6478 ChessSquare
6479 DefaultPromoChoice (int white)
6480 {
6481     ChessSquare result;
6482     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6483        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6484         result = WhiteFerz; // no choice
6485     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6486         result= WhiteKing; // in Suicide Q is the last thing we want
6487     else if(gameInfo.variant == VariantSpartan)
6488         result = white ? WhiteQueen : WhiteAngel;
6489     else result = WhiteQueen;
6490     if(!white) result = WHITE_TO_BLACK result;
6491     return result;
6492 }
6493
6494 static int autoQueen; // [HGM] oneclick
6495
6496 int
6497 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6498 {
6499     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6500     /* [HGM] add Shogi promotions */
6501     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6502     ChessSquare piece, partner;
6503     ChessMove moveType;
6504     Boolean premove;
6505
6506     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6507     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6508
6509     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6510       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6511         return FALSE;
6512
6513     piece = boards[currentMove][fromY][fromX];
6514     if(gameInfo.variant == VariantChu) {
6515         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6516         promotionZoneSize = BOARD_HEIGHT/3;
6517         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6518     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6519         promotionZoneSize = BOARD_HEIGHT/3;
6520         highestPromotingPiece = (int)WhiteAlfil;
6521     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6522         promotionZoneSize = 3;
6523     }
6524
6525     // Treat Lance as Pawn when it is not representing Amazon or Lance
6526     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6527         if(piece == WhiteLance) piece = WhitePawn; else
6528         if(piece == BlackLance) piece = BlackPawn;
6529     }
6530
6531     // next weed out all moves that do not touch the promotion zone at all
6532     if((int)piece >= BlackPawn) {
6533         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6534              return FALSE;
6535         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6536         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6537     } else {
6538         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6539            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6540         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6541              return FALSE;
6542     }
6543
6544     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6545
6546     // weed out mandatory Shogi promotions
6547     if(gameInfo.variant == VariantShogi) {
6548         if(piece >= BlackPawn) {
6549             if(toY == 0 && piece == BlackPawn ||
6550                toY == 0 && piece == BlackQueen ||
6551                toY <= 1 && piece == BlackKnight) {
6552                 *promoChoice = '+';
6553                 return FALSE;
6554             }
6555         } else {
6556             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6557                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6558                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6559                 *promoChoice = '+';
6560                 return FALSE;
6561             }
6562         }
6563     }
6564
6565     // weed out obviously illegal Pawn moves
6566     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6567         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6568         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6569         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6570         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6571         // note we are not allowed to test for valid (non-)capture, due to premove
6572     }
6573
6574     // we either have a choice what to promote to, or (in Shogi) whether to promote
6575     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6576        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6577         ChessSquare p=BlackFerz;  // no choice
6578         while(p < EmptySquare) {  //but make sure we use piece that exists
6579             *promoChoice = PieceToChar(p++);
6580             if(*promoChoice != '.') break;
6581         }
6582         return FALSE;
6583     }
6584     // no sense asking what we must promote to if it is going to explode...
6585     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6586         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6587         return FALSE;
6588     }
6589     // give caller the default choice even if we will not make it
6590     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6591     partner = piece; // pieces can promote if the pieceToCharTable says so
6592     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6593     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6594     if(        sweepSelect && gameInfo.variant != VariantGreat
6595                            && gameInfo.variant != VariantGrand
6596                            && gameInfo.variant != VariantSuper) return FALSE;
6597     if(autoQueen) return FALSE; // predetermined
6598
6599     // suppress promotion popup on illegal moves that are not premoves
6600     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6601               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6602     if(appData.testLegality && !premove) {
6603         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6604                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6605         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6606         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6607             return FALSE;
6608     }
6609
6610     return TRUE;
6611 }
6612
6613 int
6614 InPalace (int row, int column)
6615 {   /* [HGM] for Xiangqi */
6616     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6617          column < (BOARD_WIDTH + 4)/2 &&
6618          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6619     return FALSE;
6620 }
6621
6622 int
6623 PieceForSquare (int x, int y)
6624 {
6625   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6626      return -1;
6627   else
6628      return boards[currentMove][y][x];
6629 }
6630
6631 int
6632 OKToStartUserMove (int x, int y)
6633 {
6634     ChessSquare from_piece;
6635     int white_piece;
6636
6637     if (matchMode) return FALSE;
6638     if (gameMode == EditPosition) return TRUE;
6639
6640     if (x >= 0 && y >= 0)
6641       from_piece = boards[currentMove][y][x];
6642     else
6643       from_piece = EmptySquare;
6644
6645     if (from_piece == EmptySquare) return FALSE;
6646
6647     white_piece = (int)from_piece >= (int)WhitePawn &&
6648       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6649
6650     switch (gameMode) {
6651       case AnalyzeFile:
6652       case TwoMachinesPlay:
6653       case EndOfGame:
6654         return FALSE;
6655
6656       case IcsObserving:
6657       case IcsIdle:
6658         return FALSE;
6659
6660       case MachinePlaysWhite:
6661       case IcsPlayingBlack:
6662         if (appData.zippyPlay) return FALSE;
6663         if (white_piece) {
6664             DisplayMoveError(_("You are playing Black"));
6665             return FALSE;
6666         }
6667         break;
6668
6669       case MachinePlaysBlack:
6670       case IcsPlayingWhite:
6671         if (appData.zippyPlay) return FALSE;
6672         if (!white_piece) {
6673             DisplayMoveError(_("You are playing White"));
6674             return FALSE;
6675         }
6676         break;
6677
6678       case PlayFromGameFile:
6679             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6680       case EditGame:
6681         if (!white_piece && WhiteOnMove(currentMove)) {
6682             DisplayMoveError(_("It is White's turn"));
6683             return FALSE;
6684         }
6685         if (white_piece && !WhiteOnMove(currentMove)) {
6686             DisplayMoveError(_("It is Black's turn"));
6687             return FALSE;
6688         }
6689         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6690             /* Editing correspondence game history */
6691             /* Could disallow this or prompt for confirmation */
6692             cmailOldMove = -1;
6693         }
6694         break;
6695
6696       case BeginningOfGame:
6697         if (appData.icsActive) return FALSE;
6698         if (!appData.noChessProgram) {
6699             if (!white_piece) {
6700                 DisplayMoveError(_("You are playing White"));
6701                 return FALSE;
6702             }
6703         }
6704         break;
6705
6706       case Training:
6707         if (!white_piece && WhiteOnMove(currentMove)) {
6708             DisplayMoveError(_("It is White's turn"));
6709             return FALSE;
6710         }
6711         if (white_piece && !WhiteOnMove(currentMove)) {
6712             DisplayMoveError(_("It is Black's turn"));
6713             return FALSE;
6714         }
6715         break;
6716
6717       default:
6718       case IcsExamining:
6719         break;
6720     }
6721     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6722         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6723         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6724         && gameMode != AnalyzeFile && gameMode != Training) {
6725         DisplayMoveError(_("Displayed position is not current"));
6726         return FALSE;
6727     }
6728     return TRUE;
6729 }
6730
6731 Boolean
6732 OnlyMove (int *x, int *y, Boolean captures)
6733 {
6734     DisambiguateClosure cl;
6735     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6736     switch(gameMode) {
6737       case MachinePlaysBlack:
6738       case IcsPlayingWhite:
6739       case BeginningOfGame:
6740         if(!WhiteOnMove(currentMove)) return FALSE;
6741         break;
6742       case MachinePlaysWhite:
6743       case IcsPlayingBlack:
6744         if(WhiteOnMove(currentMove)) return FALSE;
6745         break;
6746       case EditGame:
6747         break;
6748       default:
6749         return FALSE;
6750     }
6751     cl.pieceIn = EmptySquare;
6752     cl.rfIn = *y;
6753     cl.ffIn = *x;
6754     cl.rtIn = -1;
6755     cl.ftIn = -1;
6756     cl.promoCharIn = NULLCHAR;
6757     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6758     if( cl.kind == NormalMove ||
6759         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6760         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6761         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6762       fromX = cl.ff;
6763       fromY = cl.rf;
6764       *x = cl.ft;
6765       *y = cl.rt;
6766       return TRUE;
6767     }
6768     if(cl.kind != ImpossibleMove) return FALSE;
6769     cl.pieceIn = EmptySquare;
6770     cl.rfIn = -1;
6771     cl.ffIn = -1;
6772     cl.rtIn = *y;
6773     cl.ftIn = *x;
6774     cl.promoCharIn = NULLCHAR;
6775     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6776     if( cl.kind == NormalMove ||
6777         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6778         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6779         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6780       fromX = cl.ff;
6781       fromY = cl.rf;
6782       *x = cl.ft;
6783       *y = cl.rt;
6784       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6785       return TRUE;
6786     }
6787     return FALSE;
6788 }
6789
6790 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6791 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6792 int lastLoadGameUseList = FALSE;
6793 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6794 ChessMove lastLoadGameStart = EndOfFile;
6795 int doubleClick;
6796
6797 void
6798 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6799 {
6800     ChessMove moveType;
6801     ChessSquare pup;
6802     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6803
6804     /* Check if the user is playing in turn.  This is complicated because we
6805        let the user "pick up" a piece before it is his turn.  So the piece he
6806        tried to pick up may have been captured by the time he puts it down!
6807        Therefore we use the color the user is supposed to be playing in this
6808        test, not the color of the piece that is currently on the starting
6809        square---except in EditGame mode, where the user is playing both
6810        sides; fortunately there the capture race can't happen.  (It can
6811        now happen in IcsExamining mode, but that's just too bad.  The user
6812        will get a somewhat confusing message in that case.)
6813        */
6814
6815     switch (gameMode) {
6816       case AnalyzeFile:
6817       case TwoMachinesPlay:
6818       case EndOfGame:
6819       case IcsObserving:
6820       case IcsIdle:
6821         /* We switched into a game mode where moves are not accepted,
6822            perhaps while the mouse button was down. */
6823         return;
6824
6825       case MachinePlaysWhite:
6826         /* User is moving for Black */
6827         if (WhiteOnMove(currentMove)) {
6828             DisplayMoveError(_("It is White's turn"));
6829             return;
6830         }
6831         break;
6832
6833       case MachinePlaysBlack:
6834         /* User is moving for White */
6835         if (!WhiteOnMove(currentMove)) {
6836             DisplayMoveError(_("It is Black's turn"));
6837             return;
6838         }
6839         break;
6840
6841       case PlayFromGameFile:
6842             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6843       case EditGame:
6844       case IcsExamining:
6845       case BeginningOfGame:
6846       case AnalyzeMode:
6847       case Training:
6848         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6849         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6850             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6851             /* User is moving for Black */
6852             if (WhiteOnMove(currentMove)) {
6853                 DisplayMoveError(_("It is White's turn"));
6854                 return;
6855             }
6856         } else {
6857             /* User is moving for White */
6858             if (!WhiteOnMove(currentMove)) {
6859                 DisplayMoveError(_("It is Black's turn"));
6860                 return;
6861             }
6862         }
6863         break;
6864
6865       case IcsPlayingBlack:
6866         /* User is moving for Black */
6867         if (WhiteOnMove(currentMove)) {
6868             if (!appData.premove) {
6869                 DisplayMoveError(_("It is White's turn"));
6870             } else if (toX >= 0 && toY >= 0) {
6871                 premoveToX = toX;
6872                 premoveToY = toY;
6873                 premoveFromX = fromX;
6874                 premoveFromY = fromY;
6875                 premovePromoChar = promoChar;
6876                 gotPremove = 1;
6877                 if (appData.debugMode)
6878                     fprintf(debugFP, "Got premove: fromX %d,"
6879                             "fromY %d, toX %d, toY %d\n",
6880                             fromX, fromY, toX, toY);
6881             }
6882             return;
6883         }
6884         break;
6885
6886       case IcsPlayingWhite:
6887         /* User is moving for White */
6888         if (!WhiteOnMove(currentMove)) {
6889             if (!appData.premove) {
6890                 DisplayMoveError(_("It is Black's turn"));
6891             } else if (toX >= 0 && toY >= 0) {
6892                 premoveToX = toX;
6893                 premoveToY = toY;
6894                 premoveFromX = fromX;
6895                 premoveFromY = fromY;
6896                 premovePromoChar = promoChar;
6897                 gotPremove = 1;
6898                 if (appData.debugMode)
6899                     fprintf(debugFP, "Got premove: fromX %d,"
6900                             "fromY %d, toX %d, toY %d\n",
6901                             fromX, fromY, toX, toY);
6902             }
6903             return;
6904         }
6905         break;
6906
6907       default:
6908         break;
6909
6910       case EditPosition:
6911         /* EditPosition, empty square, or different color piece;
6912            click-click move is possible */
6913         if (toX == -2 || toY == -2) {
6914             boards[0][fromY][fromX] = EmptySquare;
6915             DrawPosition(FALSE, boards[currentMove]);
6916             return;
6917         } else if (toX >= 0 && toY >= 0) {
6918             boards[0][toY][toX] = boards[0][fromY][fromX];
6919             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6920                 if(boards[0][fromY][0] != EmptySquare) {
6921                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6922                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6923                 }
6924             } else
6925             if(fromX == BOARD_RGHT+1) {
6926                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6927                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6928                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6929                 }
6930             } else
6931             boards[0][fromY][fromX] = gatingPiece;
6932             DrawPosition(FALSE, boards[currentMove]);
6933             return;
6934         }
6935         return;
6936     }
6937
6938     if(toX < 0 || toY < 0) return;
6939     pup = boards[currentMove][toY][toX];
6940
6941     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6942     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6943          if( pup != EmptySquare ) return;
6944          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6945            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6946                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6947            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6948            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6949            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6950            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6951          fromY = DROP_RANK;
6952     }
6953
6954     /* [HGM] always test for legality, to get promotion info */
6955     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6956                                          fromY, fromX, toY, toX, promoChar);
6957
6958     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6959
6960     /* [HGM] but possibly ignore an IllegalMove result */
6961     if (appData.testLegality) {
6962         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6963             DisplayMoveError(_("Illegal move"));
6964             return;
6965         }
6966     }
6967
6968     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6969         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6970              ClearPremoveHighlights(); // was included
6971         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6972         return;
6973     }
6974
6975     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6976 }
6977
6978 /* Common tail of UserMoveEvent and DropMenuEvent */
6979 int
6980 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6981 {
6982     char *bookHit = 0;
6983
6984     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6985         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6986         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6987         if(WhiteOnMove(currentMove)) {
6988             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6989         } else {
6990             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6991         }
6992     }
6993
6994     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6995        move type in caller when we know the move is a legal promotion */
6996     if(moveType == NormalMove && promoChar)
6997         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6998
6999     /* [HGM] <popupFix> The following if has been moved here from
7000        UserMoveEvent(). Because it seemed to belong here (why not allow
7001        piece drops in training games?), and because it can only be
7002        performed after it is known to what we promote. */
7003     if (gameMode == Training) {
7004       /* compare the move played on the board to the next move in the
7005        * game. If they match, display the move and the opponent's response.
7006        * If they don't match, display an error message.
7007        */
7008       int saveAnimate;
7009       Board testBoard;
7010       CopyBoard(testBoard, boards[currentMove]);
7011       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7012
7013       if (CompareBoards(testBoard, boards[currentMove+1])) {
7014         ForwardInner(currentMove+1);
7015
7016         /* Autoplay the opponent's response.
7017          * if appData.animate was TRUE when Training mode was entered,
7018          * the response will be animated.
7019          */
7020         saveAnimate = appData.animate;
7021         appData.animate = animateTraining;
7022         ForwardInner(currentMove+1);
7023         appData.animate = saveAnimate;
7024
7025         /* check for the end of the game */
7026         if (currentMove >= forwardMostMove) {
7027           gameMode = PlayFromGameFile;
7028           ModeHighlight();
7029           SetTrainingModeOff();
7030           DisplayInformation(_("End of game"));
7031         }
7032       } else {
7033         DisplayError(_("Incorrect move"), 0);
7034       }
7035       return 1;
7036     }
7037
7038   /* Ok, now we know that the move is good, so we can kill
7039      the previous line in Analysis Mode */
7040   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7041                                 && currentMove < forwardMostMove) {
7042     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7043     else forwardMostMove = currentMove;
7044   }
7045
7046   ClearMap();
7047
7048   /* If we need the chess program but it's dead, restart it */
7049   ResurrectChessProgram();
7050
7051   /* A user move restarts a paused game*/
7052   if (pausing)
7053     PauseEvent();
7054
7055   thinkOutput[0] = NULLCHAR;
7056
7057   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7058
7059   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7060     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7061     return 1;
7062   }
7063
7064   if (gameMode == BeginningOfGame) {
7065     if (appData.noChessProgram) {
7066       gameMode = EditGame;
7067       SetGameInfo();
7068     } else {
7069       char buf[MSG_SIZ];
7070       gameMode = MachinePlaysBlack;
7071       StartClocks();
7072       SetGameInfo();
7073       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7074       DisplayTitle(buf);
7075       if (first.sendName) {
7076         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7077         SendToProgram(buf, &first);
7078       }
7079       StartClocks();
7080     }
7081     ModeHighlight();
7082   }
7083
7084   /* Relay move to ICS or chess engine */
7085   if (appData.icsActive) {
7086     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7087         gameMode == IcsExamining) {
7088       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7089         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7090         SendToICS("draw ");
7091         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7092       }
7093       // also send plain move, in case ICS does not understand atomic claims
7094       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7095       ics_user_moved = 1;
7096     }
7097   } else {
7098     if (first.sendTime && (gameMode == BeginningOfGame ||
7099                            gameMode == MachinePlaysWhite ||
7100                            gameMode == MachinePlaysBlack)) {
7101       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7102     }
7103     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7104          // [HGM] book: if program might be playing, let it use book
7105         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7106         first.maybeThinking = TRUE;
7107     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7108         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7109         SendBoard(&first, currentMove+1);
7110         if(second.analyzing) {
7111             if(!second.useSetboard) SendToProgram("undo\n", &second);
7112             SendBoard(&second, currentMove+1);
7113         }
7114     } else {
7115         SendMoveToProgram(forwardMostMove-1, &first);
7116         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7117     }
7118     if (currentMove == cmailOldMove + 1) {
7119       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7120     }
7121   }
7122
7123   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7124
7125   switch (gameMode) {
7126   case EditGame:
7127     if(appData.testLegality)
7128     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7129     case MT_NONE:
7130     case MT_CHECK:
7131       break;
7132     case MT_CHECKMATE:
7133     case MT_STAINMATE:
7134       if (WhiteOnMove(currentMove)) {
7135         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7136       } else {
7137         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7138       }
7139       break;
7140     case MT_STALEMATE:
7141       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7142       break;
7143     }
7144     break;
7145
7146   case MachinePlaysBlack:
7147   case MachinePlaysWhite:
7148     /* disable certain menu options while machine is thinking */
7149     SetMachineThinkingEnables();
7150     break;
7151
7152   default:
7153     break;
7154   }
7155
7156   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7157   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7158
7159   if(bookHit) { // [HGM] book: simulate book reply
7160         static char bookMove[MSG_SIZ]; // a bit generous?
7161
7162         programStats.nodes = programStats.depth = programStats.time =
7163         programStats.score = programStats.got_only_move = 0;
7164         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7165
7166         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7167         strcat(bookMove, bookHit);
7168         HandleMachineMove(bookMove, &first);
7169   }
7170   return 1;
7171 }
7172
7173 void
7174 MarkByFEN(char *fen)
7175 {
7176         int r, f;
7177         if(!appData.markers || !appData.highlightDragging) return;
7178         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7179         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7180         while(*fen) {
7181             int s = 0;
7182             marker[r][f] = 0;
7183             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7184             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7185             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7186             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7187             if(*fen == 'T') marker[r][f++] = 0; else
7188             if(*fen == 'Y') marker[r][f++] = 1; else
7189             if(*fen == 'G') marker[r][f++] = 3; else
7190             if(*fen == 'B') marker[r][f++] = 4; else
7191             if(*fen == 'C') marker[r][f++] = 5; else
7192             if(*fen == 'M') marker[r][f++] = 6; else
7193             if(*fen == 'W') marker[r][f++] = 7; else
7194             if(*fen == 'D') marker[r][f++] = 8; else
7195             if(*fen == 'R') marker[r][f++] = 2; else {
7196                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7197               f += s; fen -= s>0;
7198             }
7199             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7200             if(r < 0) break;
7201             fen++;
7202         }
7203         DrawPosition(TRUE, NULL);
7204 }
7205
7206 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7207
7208 void
7209 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7210 {
7211     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7212     Markers *m = (Markers *) closure;
7213     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7214         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7215                          || kind == WhiteCapturesEnPassant
7216                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7217     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7218 }
7219
7220 static int hoverSavedValid;
7221
7222 void
7223 MarkTargetSquares (int clear)
7224 {
7225   int x, y, sum=0;
7226   if(clear) { // no reason to ever suppress clearing
7227     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7228     hoverSavedValid = 0;
7229     if(!sum) return; // nothing was cleared,no redraw needed
7230   } else {
7231     int capt = 0;
7232     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7233        !appData.testLegality || gameMode == EditPosition) return;
7234     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7235     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7236       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7237       if(capt)
7238       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7239     }
7240   }
7241   DrawPosition(FALSE, NULL);
7242 }
7243
7244 int
7245 Explode (Board board, int fromX, int fromY, int toX, int toY)
7246 {
7247     if(gameInfo.variant == VariantAtomic &&
7248        (board[toY][toX] != EmptySquare ||                     // capture?
7249         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7250                          board[fromY][fromX] == BlackPawn   )
7251       )) {
7252         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7253         return TRUE;
7254     }
7255     return FALSE;
7256 }
7257
7258 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7259
7260 int
7261 CanPromote (ChessSquare piece, int y)
7262 {
7263         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7264         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7265         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7266         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7267            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7268            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7269          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7270         return (piece == BlackPawn && y <= zone ||
7271                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7272                 piece == BlackLance && y == 1 ||
7273                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7274 }
7275
7276 void
7277 HoverEvent (int xPix, int yPix, int x, int y)
7278 {
7279         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7280         int r, f;
7281         if(!first.highlight) return;
7282         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7283         if(x == oldX && y == oldY) return; // only do something if we enter new square
7284         oldFromX = fromX; oldFromY = fromY;
7285         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7286           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7287             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7288           hoverSavedValid = 1;
7289         } else if(oldX != x || oldY != y) {
7290           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7291           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7292           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7293             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7294           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7295             char buf[MSG_SIZ];
7296             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7297             SendToProgram(buf, &first);
7298           }
7299           oldX = x; oldY = y;
7300 //        SetHighlights(fromX, fromY, x, y);
7301         }
7302 }
7303
7304 void ReportClick(char *action, int x, int y)
7305 {
7306         char buf[MSG_SIZ]; // Inform engine of what user does
7307         int r, f;
7308         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7309           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7310         if(!first.highlight || gameMode == EditPosition) return;
7311         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7312         SendToProgram(buf, &first);
7313 }
7314
7315 void
7316 LeftClick (ClickType clickType, int xPix, int yPix)
7317 {
7318     int x, y;
7319     Boolean saveAnimate;
7320     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7321     char promoChoice = NULLCHAR;
7322     ChessSquare piece;
7323     static TimeMark lastClickTime, prevClickTime;
7324
7325     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7326
7327     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7328
7329     if (clickType == Press) ErrorPopDown();
7330     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7331
7332     x = EventToSquare(xPix, BOARD_WIDTH);
7333     y = EventToSquare(yPix, BOARD_HEIGHT);
7334     if (!flipView && y >= 0) {
7335         y = BOARD_HEIGHT - 1 - y;
7336     }
7337     if (flipView && x >= 0) {
7338         x = BOARD_WIDTH - 1 - x;
7339     }
7340
7341     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7342         defaultPromoChoice = promoSweep;
7343         promoSweep = EmptySquare;   // terminate sweep
7344         promoDefaultAltered = TRUE;
7345         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7346     }
7347
7348     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7349         if(clickType == Release) return; // ignore upclick of click-click destination
7350         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7351         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7352         if(gameInfo.holdingsWidth &&
7353                 (WhiteOnMove(currentMove)
7354                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7355                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7356             // click in right holdings, for determining promotion piece
7357             ChessSquare p = boards[currentMove][y][x];
7358             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7359             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7360             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7361                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7362                 fromX = fromY = -1;
7363                 return;
7364             }
7365         }
7366         DrawPosition(FALSE, boards[currentMove]);
7367         return;
7368     }
7369
7370     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7371     if(clickType == Press
7372             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7373               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7374               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7375         return;
7376
7377     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7378         // could be static click on premove from-square: abort premove
7379         gotPremove = 0;
7380         ClearPremoveHighlights();
7381     }
7382
7383     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7384         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7385
7386     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7387         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7388                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7389         defaultPromoChoice = DefaultPromoChoice(side);
7390     }
7391
7392     autoQueen = appData.alwaysPromoteToQueen;
7393
7394     if (fromX == -1) {
7395       int originalY = y;
7396       gatingPiece = EmptySquare;
7397       if (clickType != Press) {
7398         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7399             DragPieceEnd(xPix, yPix); dragging = 0;
7400             DrawPosition(FALSE, NULL);
7401         }
7402         return;
7403       }
7404       doubleClick = FALSE;
7405       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7406         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7407       }
7408       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7409       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7410          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7411          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7412             /* First square */
7413             if (OKToStartUserMove(fromX, fromY)) {
7414                 second = 0;
7415                 ReportClick("lift", x, y);
7416                 MarkTargetSquares(0);
7417                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7418                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7419                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7420                     promoSweep = defaultPromoChoice;
7421                     selectFlag = 0; lastX = xPix; lastY = yPix;
7422                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7423                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7424                 }
7425                 if (appData.highlightDragging) {
7426                     SetHighlights(fromX, fromY, -1, -1);
7427                 } else {
7428                     ClearHighlights();
7429                 }
7430             } else fromX = fromY = -1;
7431             return;
7432         }
7433     }
7434
7435     /* fromX != -1 */
7436     if (clickType == Press && gameMode != EditPosition) {
7437         ChessSquare fromP;
7438         ChessSquare toP;
7439         int frc;
7440
7441         // ignore off-board to clicks
7442         if(y < 0 || x < 0) return;
7443
7444         /* Check if clicking again on the same color piece */
7445         fromP = boards[currentMove][fromY][fromX];
7446         toP = boards[currentMove][y][x];
7447         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7448         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7449            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7450              WhitePawn <= toP && toP <= WhiteKing &&
7451              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7452              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7453             (BlackPawn <= fromP && fromP <= BlackKing &&
7454              BlackPawn <= toP && toP <= BlackKing &&
7455              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7456              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7457             /* Clicked again on same color piece -- changed his mind */
7458             second = (x == fromX && y == fromY);
7459             killX = killY = -1;
7460             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7461                 second = FALSE; // first double-click rather than scond click
7462                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7463             }
7464             promoDefaultAltered = FALSE;
7465             MarkTargetSquares(1);
7466            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7467             if (appData.highlightDragging) {
7468                 SetHighlights(x, y, -1, -1);
7469             } else {
7470                 ClearHighlights();
7471             }
7472             if (OKToStartUserMove(x, y)) {
7473                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7474                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7475                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7476                  gatingPiece = boards[currentMove][fromY][fromX];
7477                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7478                 fromX = x;
7479                 fromY = y; dragging = 1;
7480                 ReportClick("lift", x, y);
7481                 MarkTargetSquares(0);
7482                 DragPieceBegin(xPix, yPix, FALSE);
7483                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7484                     promoSweep = defaultPromoChoice;
7485                     selectFlag = 0; lastX = xPix; lastY = yPix;
7486                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7487                 }
7488             }
7489            }
7490            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7491            second = FALSE;
7492         }
7493         // ignore clicks on holdings
7494         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7495     }
7496
7497     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7498         DragPieceEnd(xPix, yPix); dragging = 0;
7499         if(clearFlag) {
7500             // a deferred attempt to click-click move an empty square on top of a piece
7501             boards[currentMove][y][x] = EmptySquare;
7502             ClearHighlights();
7503             DrawPosition(FALSE, boards[currentMove]);
7504             fromX = fromY = -1; clearFlag = 0;
7505             return;
7506         }
7507         if (appData.animateDragging) {
7508             /* Undo animation damage if any */
7509             DrawPosition(FALSE, NULL);
7510         }
7511         if (second || sweepSelecting) {
7512             /* Second up/down in same square; just abort move */
7513             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7514             second = sweepSelecting = 0;
7515             fromX = fromY = -1;
7516             gatingPiece = EmptySquare;
7517             MarkTargetSquares(1);
7518             ClearHighlights();
7519             gotPremove = 0;
7520             ClearPremoveHighlights();
7521         } else {
7522             /* First upclick in same square; start click-click mode */
7523             SetHighlights(x, y, -1, -1);
7524         }
7525         return;
7526     }
7527
7528     clearFlag = 0;
7529
7530     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7531         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7532         DisplayMessage(_("only marked squares are legal"),"");
7533         DrawPosition(TRUE, NULL);
7534         return; // ignore to-click
7535     }
7536
7537     /* we now have a different from- and (possibly off-board) to-square */
7538     /* Completed move */
7539     if(!sweepSelecting) {
7540         toX = x;
7541         toY = y;
7542     }
7543
7544     piece = boards[currentMove][fromY][fromX];
7545
7546     saveAnimate = appData.animate;
7547     if (clickType == Press) {
7548         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7549         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7550             // must be Edit Position mode with empty-square selected
7551             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7552             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7553             return;
7554         }
7555         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7556             return;
7557         }
7558         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7559             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7560         } else
7561         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7562         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7563           if(appData.sweepSelect) {
7564             promoSweep = defaultPromoChoice;
7565             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7566             selectFlag = 0; lastX = xPix; lastY = yPix;
7567             Sweep(0); // Pawn that is going to promote: preview promotion piece
7568             sweepSelecting = 1;
7569             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7570             MarkTargetSquares(1);
7571           }
7572           return; // promo popup appears on up-click
7573         }
7574         /* Finish clickclick move */
7575         if (appData.animate || appData.highlightLastMove) {
7576             SetHighlights(fromX, fromY, toX, toY);
7577         } else {
7578             ClearHighlights();
7579         }
7580     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7581         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7582         if (appData.animate || appData.highlightLastMove) {
7583             SetHighlights(fromX, fromY, toX, toY);
7584         } else {
7585             ClearHighlights();
7586         }
7587     } else {
7588 #if 0
7589 // [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
7590         /* Finish drag move */
7591         if (appData.highlightLastMove) {
7592             SetHighlights(fromX, fromY, toX, toY);
7593         } else {
7594             ClearHighlights();
7595         }
7596 #endif
7597         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7598         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7599           dragging *= 2;            // flag button-less dragging if we are dragging
7600           MarkTargetSquares(1);
7601           if(x == killX && y == killY) killX = killY = -1; else {
7602             killX = x; killY = y;     //remeber this square as intermediate
7603             ReportClick("put", x, y); // and inform engine
7604             ReportClick("lift", x, y);
7605             MarkTargetSquares(0);
7606             return;
7607           }
7608         }
7609         DragPieceEnd(xPix, yPix); dragging = 0;
7610         /* Don't animate move and drag both */
7611         appData.animate = FALSE;
7612     }
7613
7614     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7615     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7616         ChessSquare piece = boards[currentMove][fromY][fromX];
7617         if(gameMode == EditPosition && piece != EmptySquare &&
7618            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7619             int n;
7620
7621             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7622                 n = PieceToNumber(piece - (int)BlackPawn);
7623                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7624                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7625                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7626             } else
7627             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7628                 n = PieceToNumber(piece);
7629                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7630                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7631                 boards[currentMove][n][BOARD_WIDTH-2]++;
7632             }
7633             boards[currentMove][fromY][fromX] = EmptySquare;
7634         }
7635         ClearHighlights();
7636         fromX = fromY = -1;
7637         MarkTargetSquares(1);
7638         DrawPosition(TRUE, boards[currentMove]);
7639         return;
7640     }
7641
7642     // off-board moves should not be highlighted
7643     if(x < 0 || y < 0) ClearHighlights();
7644     else ReportClick("put", x, y);
7645
7646     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7647
7648     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7649         SetHighlights(fromX, fromY, toX, toY);
7650         MarkTargetSquares(1);
7651         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7652             // [HGM] super: promotion to captured piece selected from holdings
7653             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7654             promotionChoice = TRUE;
7655             // kludge follows to temporarily execute move on display, without promoting yet
7656             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7657             boards[currentMove][toY][toX] = p;
7658             DrawPosition(FALSE, boards[currentMove]);
7659             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7660             boards[currentMove][toY][toX] = q;
7661             DisplayMessage("Click in holdings to choose piece", "");
7662             return;
7663         }
7664         PromotionPopUp(promoChoice);
7665     } else {
7666         int oldMove = currentMove;
7667         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7668         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7669         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7670         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7671            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7672             DrawPosition(TRUE, boards[currentMove]);
7673         MarkTargetSquares(1);
7674         fromX = fromY = -1;
7675     }
7676     appData.animate = saveAnimate;
7677     if (appData.animate || appData.animateDragging) {
7678         /* Undo animation damage if needed */
7679         DrawPosition(FALSE, NULL);
7680     }
7681 }
7682
7683 int
7684 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7685 {   // front-end-free part taken out of PieceMenuPopup
7686     int whichMenu; int xSqr, ySqr;
7687
7688     if(seekGraphUp) { // [HGM] seekgraph
7689         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7690         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7691         return -2;
7692     }
7693
7694     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7695          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7696         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7697         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7698         if(action == Press)   {
7699             originalFlip = flipView;
7700             flipView = !flipView; // temporarily flip board to see game from partners perspective
7701             DrawPosition(TRUE, partnerBoard);
7702             DisplayMessage(partnerStatus, "");
7703             partnerUp = TRUE;
7704         } else if(action == Release) {
7705             flipView = originalFlip;
7706             DrawPosition(TRUE, boards[currentMove]);
7707             partnerUp = FALSE;
7708         }
7709         return -2;
7710     }
7711
7712     xSqr = EventToSquare(x, BOARD_WIDTH);
7713     ySqr = EventToSquare(y, BOARD_HEIGHT);
7714     if (action == Release) {
7715         if(pieceSweep != EmptySquare) {
7716             EditPositionMenuEvent(pieceSweep, toX, toY);
7717             pieceSweep = EmptySquare;
7718         } else UnLoadPV(); // [HGM] pv
7719     }
7720     if (action != Press) return -2; // return code to be ignored
7721     switch (gameMode) {
7722       case IcsExamining:
7723         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7724       case EditPosition:
7725         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7726         if (xSqr < 0 || ySqr < 0) return -1;
7727         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7728         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7729         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7730         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7731         NextPiece(0);
7732         return 2; // grab
7733       case IcsObserving:
7734         if(!appData.icsEngineAnalyze) return -1;
7735       case IcsPlayingWhite:
7736       case IcsPlayingBlack:
7737         if(!appData.zippyPlay) goto noZip;
7738       case AnalyzeMode:
7739       case AnalyzeFile:
7740       case MachinePlaysWhite:
7741       case MachinePlaysBlack:
7742       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7743         if (!appData.dropMenu) {
7744           LoadPV(x, y);
7745           return 2; // flag front-end to grab mouse events
7746         }
7747         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7748            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7749       case EditGame:
7750       noZip:
7751         if (xSqr < 0 || ySqr < 0) return -1;
7752         if (!appData.dropMenu || appData.testLegality &&
7753             gameInfo.variant != VariantBughouse &&
7754             gameInfo.variant != VariantCrazyhouse) return -1;
7755         whichMenu = 1; // drop menu
7756         break;
7757       default:
7758         return -1;
7759     }
7760
7761     if (((*fromX = xSqr) < 0) ||
7762         ((*fromY = ySqr) < 0)) {
7763         *fromX = *fromY = -1;
7764         return -1;
7765     }
7766     if (flipView)
7767       *fromX = BOARD_WIDTH - 1 - *fromX;
7768     else
7769       *fromY = BOARD_HEIGHT - 1 - *fromY;
7770
7771     return whichMenu;
7772 }
7773
7774 void
7775 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7776 {
7777 //    char * hint = lastHint;
7778     FrontEndProgramStats stats;
7779
7780     stats.which = cps == &first ? 0 : 1;
7781     stats.depth = cpstats->depth;
7782     stats.nodes = cpstats->nodes;
7783     stats.score = cpstats->score;
7784     stats.time = cpstats->time;
7785     stats.pv = cpstats->movelist;
7786     stats.hint = lastHint;
7787     stats.an_move_index = 0;
7788     stats.an_move_count = 0;
7789
7790     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7791         stats.hint = cpstats->move_name;
7792         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7793         stats.an_move_count = cpstats->nr_moves;
7794     }
7795
7796     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
7797
7798     SetProgramStats( &stats );
7799 }
7800
7801 void
7802 ClearEngineOutputPane (int which)
7803 {
7804     static FrontEndProgramStats dummyStats;
7805     dummyStats.which = which;
7806     dummyStats.pv = "#";
7807     SetProgramStats( &dummyStats );
7808 }
7809
7810 #define MAXPLAYERS 500
7811
7812 char *
7813 TourneyStandings (int display)
7814 {
7815     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7816     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7817     char result, *p, *names[MAXPLAYERS];
7818
7819     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7820         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7821     names[0] = p = strdup(appData.participants);
7822     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7823
7824     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7825
7826     while(result = appData.results[nr]) {
7827         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7828         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7829         wScore = bScore = 0;
7830         switch(result) {
7831           case '+': wScore = 2; break;
7832           case '-': bScore = 2; break;
7833           case '=': wScore = bScore = 1; break;
7834           case ' ':
7835           case '*': return strdup("busy"); // tourney not finished
7836         }
7837         score[w] += wScore;
7838         score[b] += bScore;
7839         games[w]++;
7840         games[b]++;
7841         nr++;
7842     }
7843     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7844     for(w=0; w<nPlayers; w++) {
7845         bScore = -1;
7846         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7847         ranking[w] = b; points[w] = bScore; score[b] = -2;
7848     }
7849     p = malloc(nPlayers*34+1);
7850     for(w=0; w<nPlayers && w<display; w++)
7851         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7852     free(names[0]);
7853     return p;
7854 }
7855
7856 void
7857 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7858 {       // count all piece types
7859         int p, f, r;
7860         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7861         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7862         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7863                 p = board[r][f];
7864                 pCnt[p]++;
7865                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7866                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7867                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7868                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7869                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7870                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7871         }
7872 }
7873
7874 int
7875 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7876 {
7877         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7878         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7879
7880         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7881         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7882         if(myPawns == 2 && nMine == 3) // KPP
7883             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7884         if(myPawns == 1 && nMine == 2) // KP
7885             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7886         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7887             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7888         if(myPawns) return FALSE;
7889         if(pCnt[WhiteRook+side])
7890             return pCnt[BlackRook-side] ||
7891                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7892                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7893                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7894         if(pCnt[WhiteCannon+side]) {
7895             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7896             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7897         }
7898         if(pCnt[WhiteKnight+side])
7899             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7900         return FALSE;
7901 }
7902
7903 int
7904 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7905 {
7906         VariantClass v = gameInfo.variant;
7907
7908         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7909         if(v == VariantShatranj) return TRUE; // always winnable through baring
7910         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7911         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7912
7913         if(v == VariantXiangqi) {
7914                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7915
7916                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7917                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7918                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7919                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7920                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7921                 if(stale) // we have at least one last-rank P plus perhaps C
7922                     return majors // KPKX
7923                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7924                 else // KCA*E*
7925                     return pCnt[WhiteFerz+side] // KCAK
7926                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7927                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7928                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7929
7930         } else if(v == VariantKnightmate) {
7931                 if(nMine == 1) return FALSE;
7932                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7933         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7934                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7935
7936                 if(nMine == 1) return FALSE; // bare King
7937                 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
7938                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7939                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7940                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7941                 if(pCnt[WhiteKnight+side])
7942                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7943                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7944                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7945                 if(nBishops)
7946                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7947                 if(pCnt[WhiteAlfil+side])
7948                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7949                 if(pCnt[WhiteWazir+side])
7950                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7951         }
7952
7953         return TRUE;
7954 }
7955
7956 int
7957 CompareWithRights (Board b1, Board b2)
7958 {
7959     int rights = 0;
7960     if(!CompareBoards(b1, b2)) return FALSE;
7961     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7962     /* compare castling rights */
7963     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7964            rights++; /* King lost rights, while rook still had them */
7965     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7966         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7967            rights++; /* but at least one rook lost them */
7968     }
7969     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7970            rights++;
7971     if( b1[CASTLING][5] != NoRights ) {
7972         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7973            rights++;
7974     }
7975     return rights == 0;
7976 }
7977
7978 int
7979 Adjudicate (ChessProgramState *cps)
7980 {       // [HGM] some adjudications useful with buggy engines
7981         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7982         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7983         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7984         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7985         int k, drop, count = 0; static int bare = 1;
7986         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7987         Boolean canAdjudicate = !appData.icsActive;
7988
7989         // most tests only when we understand the game, i.e. legality-checking on
7990             if( appData.testLegality )
7991             {   /* [HGM] Some more adjudications for obstinate engines */
7992                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7993                 static int moveCount = 6;
7994                 ChessMove result;
7995                 char *reason = NULL;
7996
7997                 /* Count what is on board. */
7998                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7999
8000                 /* Some material-based adjudications that have to be made before stalemate test */
8001                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8002                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8003                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8004                      if(canAdjudicate && appData.checkMates) {
8005                          if(engineOpponent)
8006                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8007                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8008                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8009                          return 1;
8010                      }
8011                 }
8012
8013                 /* Bare King in Shatranj (loses) or Losers (wins) */
8014                 if( nrW == 1 || nrB == 1) {
8015                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8016                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8017                      if(canAdjudicate && appData.checkMates) {
8018                          if(engineOpponent)
8019                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8020                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8021                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8022                          return 1;
8023                      }
8024                   } else
8025                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8026                   {    /* bare King */
8027                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8028                         if(canAdjudicate && appData.checkMates) {
8029                             /* but only adjudicate if adjudication enabled */
8030                             if(engineOpponent)
8031                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8032                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8033                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8034                             return 1;
8035                         }
8036                   }
8037                 } else bare = 1;
8038
8039
8040             // don't wait for engine to announce game end if we can judge ourselves
8041             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8042               case MT_CHECK:
8043                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8044                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8045                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8046                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8047                             checkCnt++;
8048                         if(checkCnt >= 2) {
8049                             reason = "Xboard adjudication: 3rd check";
8050                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8051                             break;
8052                         }
8053                     }
8054                 }
8055               case MT_NONE:
8056               default:
8057                 break;
8058               case MT_STEALMATE:
8059               case MT_STALEMATE:
8060               case MT_STAINMATE:
8061                 reason = "Xboard adjudication: Stalemate";
8062                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8063                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8064                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8065                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8066                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8067                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8068                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8069                                                                         EP_CHECKMATE : EP_WINS);
8070                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8071                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8072                 }
8073                 break;
8074               case MT_CHECKMATE:
8075                 reason = "Xboard adjudication: Checkmate";
8076                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8077                 if(gameInfo.variant == VariantShogi) {
8078                     if(forwardMostMove > backwardMostMove
8079                        && moveList[forwardMostMove-1][1] == '@'
8080                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8081                         reason = "XBoard adjudication: pawn-drop mate";
8082                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8083                     }
8084                 }
8085                 break;
8086             }
8087
8088                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8089                     case EP_STALEMATE:
8090                         result = GameIsDrawn; break;
8091                     case EP_CHECKMATE:
8092                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8093                     case EP_WINS:
8094                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8095                     default:
8096                         result = EndOfFile;
8097                 }
8098                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8099                     if(engineOpponent)
8100                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8101                     GameEnds( result, reason, GE_XBOARD );
8102                     return 1;
8103                 }
8104
8105                 /* Next absolutely insufficient mating material. */
8106                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8107                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8108                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8109
8110                      /* always flag draws, for judging claims */
8111                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8112
8113                      if(canAdjudicate && appData.materialDraws) {
8114                          /* but only adjudicate them if adjudication enabled */
8115                          if(engineOpponent) {
8116                            SendToProgram("force\n", engineOpponent); // suppress reply
8117                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8118                          }
8119                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8120                          return 1;
8121                      }
8122                 }
8123
8124                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8125                 if(gameInfo.variant == VariantXiangqi ?
8126                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8127                  : nrW + nrB == 4 &&
8128                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8129                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8130                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8131                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8132                    ) ) {
8133                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8134                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8135                           if(engineOpponent) {
8136                             SendToProgram("force\n", engineOpponent); // suppress reply
8137                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8138                           }
8139                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8140                           return 1;
8141                      }
8142                 } else moveCount = 6;
8143             }
8144
8145         // Repetition draws and 50-move rule can be applied independently of legality testing
8146
8147                 /* Check for rep-draws */
8148                 count = 0;
8149                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8150                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8151                 for(k = forwardMostMove-2;
8152                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8153                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8154                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8155                     k-=2)
8156                 {   int rights=0;
8157                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8158                         /* compare castling rights */
8159                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8160                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8161                                 rights++; /* King lost rights, while rook still had them */
8162                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8163                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8164                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8165                                    rights++; /* but at least one rook lost them */
8166                         }
8167                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8168                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8169                                 rights++;
8170                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8171                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8172                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8173                                    rights++;
8174                         }
8175                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8176                             && appData.drawRepeats > 1) {
8177                              /* adjudicate after user-specified nr of repeats */
8178                              int result = GameIsDrawn;
8179                              char *details = "XBoard adjudication: repetition draw";
8180                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8181                                 // [HGM] xiangqi: check for forbidden perpetuals
8182                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8183                                 for(m=forwardMostMove; m>k; m-=2) {
8184                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8185                                         ourPerpetual = 0; // the current mover did not always check
8186                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8187                                         hisPerpetual = 0; // the opponent did not always check
8188                                 }
8189                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8190                                                                         ourPerpetual, hisPerpetual);
8191                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8192                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8193                                     details = "Xboard adjudication: perpetual checking";
8194                                 } else
8195                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8196                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8197                                 } else
8198                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8199                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8200                                         result = BlackWins;
8201                                         details = "Xboard adjudication: repetition";
8202                                     }
8203                                 } else // it must be XQ
8204                                 // Now check for perpetual chases
8205                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8206                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8207                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8208                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8209                                         static char resdet[MSG_SIZ];
8210                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8211                                         details = resdet;
8212                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8213                                     } else
8214                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8215                                         break; // Abort repetition-checking loop.
8216                                 }
8217                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8218                              }
8219                              if(engineOpponent) {
8220                                SendToProgram("force\n", engineOpponent); // suppress reply
8221                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8222                              }
8223                              GameEnds( result, details, GE_XBOARD );
8224                              return 1;
8225                         }
8226                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8227                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8228                     }
8229                 }
8230
8231                 /* Now we test for 50-move draws. Determine ply count */
8232                 count = forwardMostMove;
8233                 /* look for last irreversble move */
8234                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8235                     count--;
8236                 /* if we hit starting position, add initial plies */
8237                 if( count == backwardMostMove )
8238                     count -= initialRulePlies;
8239                 count = forwardMostMove - count;
8240                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8241                         // adjust reversible move counter for checks in Xiangqi
8242                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8243                         if(i < backwardMostMove) i = backwardMostMove;
8244                         while(i <= forwardMostMove) {
8245                                 lastCheck = inCheck; // check evasion does not count
8246                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8247                                 if(inCheck || lastCheck) count--; // check does not count
8248                                 i++;
8249                         }
8250                 }
8251                 if( count >= 100)
8252                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8253                          /* this is used to judge if draw claims are legal */
8254                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8255                          if(engineOpponent) {
8256                            SendToProgram("force\n", engineOpponent); // suppress reply
8257                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8258                          }
8259                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8260                          return 1;
8261                 }
8262
8263                 /* if draw offer is pending, treat it as a draw claim
8264                  * when draw condition present, to allow engines a way to
8265                  * claim draws before making their move to avoid a race
8266                  * condition occurring after their move
8267                  */
8268                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8269                          char *p = NULL;
8270                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8271                              p = "Draw claim: 50-move rule";
8272                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8273                              p = "Draw claim: 3-fold repetition";
8274                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8275                              p = "Draw claim: insufficient mating material";
8276                          if( p != NULL && canAdjudicate) {
8277                              if(engineOpponent) {
8278                                SendToProgram("force\n", engineOpponent); // suppress reply
8279                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8280                              }
8281                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8282                              return 1;
8283                          }
8284                 }
8285
8286                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8287                     if(engineOpponent) {
8288                       SendToProgram("force\n", engineOpponent); // suppress reply
8289                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8290                     }
8291                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8292                     return 1;
8293                 }
8294         return 0;
8295 }
8296
8297 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8298 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8299 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8300
8301 static int
8302 BitbaseProbe ()
8303 {
8304     int pieces[10], squares[10], cnt=0, r, f, res;
8305     static int loaded;
8306     static PPROBE_EGBB probeBB;
8307     if(!appData.testLegality) return 10;
8308     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8309     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8310     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8311     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8312         ChessSquare piece = boards[forwardMostMove][r][f];
8313         int black = (piece >= BlackPawn);
8314         int type = piece - black*BlackPawn;
8315         if(piece == EmptySquare) continue;
8316         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8317         if(type == WhiteKing) type = WhiteQueen + 1;
8318         type = egbbCode[type];
8319         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8320         pieces[cnt] = type + black*6;
8321         if(++cnt > 5) return 11;
8322     }
8323     pieces[cnt] = squares[cnt] = 0;
8324     // probe EGBB
8325     if(loaded == 2) return 13; // loading failed before
8326     if(loaded == 0) {
8327         loaded = 2; // prepare for failure
8328         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8329         HMODULE lib;
8330         PLOAD_EGBB loadBB;
8331         if(!path) return 13; // no egbb installed
8332         strncpy(buf, path + 8, MSG_SIZ);
8333         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8334         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8335         lib = LoadLibrary(buf);
8336         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8337         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8338         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8339         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8340         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8341         loaded = 1; // success!
8342     }
8343     res = probeBB(forwardMostMove & 1, pieces, squares);
8344     return res > 0 ? 1 : res < 0 ? -1 : 0;
8345 }
8346
8347 char *
8348 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8349 {   // [HGM] book: this routine intercepts moves to simulate book replies
8350     char *bookHit = NULL;
8351
8352     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8353         char buf[MSG_SIZ];
8354         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8355         SendToProgram(buf, cps);
8356     }
8357     //first determine if the incoming move brings opponent into his book
8358     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8359         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8360     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8361     if(bookHit != NULL && !cps->bookSuspend) {
8362         // make sure opponent is not going to reply after receiving move to book position
8363         SendToProgram("force\n", cps);
8364         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8365     }
8366     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8367     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8368     // now arrange restart after book miss
8369     if(bookHit) {
8370         // after a book hit we never send 'go', and the code after the call to this routine
8371         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8372         char buf[MSG_SIZ], *move = bookHit;
8373         if(cps->useSAN) {
8374             int fromX, fromY, toX, toY;
8375             char promoChar;
8376             ChessMove moveType;
8377             move = buf + 30;
8378             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8379                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8380                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8381                                     PosFlags(forwardMostMove),
8382                                     fromY, fromX, toY, toX, promoChar, move);
8383             } else {
8384                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8385                 bookHit = NULL;
8386             }
8387         }
8388         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8389         SendToProgram(buf, cps);
8390         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8391     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8392         SendToProgram("go\n", cps);
8393         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8394     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8395         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8396             SendToProgram("go\n", cps);
8397         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8398     }
8399     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8400 }
8401
8402 int
8403 LoadError (char *errmess, ChessProgramState *cps)
8404 {   // unloads engine and switches back to -ncp mode if it was first
8405     if(cps->initDone) return FALSE;
8406     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8407     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8408     cps->pr = NoProc;
8409     if(cps == &first) {
8410         appData.noChessProgram = TRUE;
8411         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8412         gameMode = BeginningOfGame; ModeHighlight();
8413         SetNCPMode();
8414     }
8415     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8416     DisplayMessage("", ""); // erase waiting message
8417     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8418     return TRUE;
8419 }
8420
8421 char *savedMessage;
8422 ChessProgramState *savedState;
8423 void
8424 DeferredBookMove (void)
8425 {
8426         if(savedState->lastPing != savedState->lastPong)
8427                     ScheduleDelayedEvent(DeferredBookMove, 10);
8428         else
8429         HandleMachineMove(savedMessage, savedState);
8430 }
8431
8432 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8433 static ChessProgramState *stalledEngine;
8434 static char stashedInputMove[MSG_SIZ];
8435
8436 void
8437 HandleMachineMove (char *message, ChessProgramState *cps)
8438 {
8439     static char firstLeg[20];
8440     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8441     char realname[MSG_SIZ];
8442     int fromX, fromY, toX, toY;
8443     ChessMove moveType;
8444     char promoChar, roar;
8445     char *p, *pv=buf1;
8446     int machineWhite, oldError;
8447     char *bookHit;
8448
8449     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8450         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8451         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8452             DisplayError(_("Invalid pairing from pairing engine"), 0);
8453             return;
8454         }
8455         pairingReceived = 1;
8456         NextMatchGame();
8457         return; // Skim the pairing messages here.
8458     }
8459
8460     oldError = cps->userError; cps->userError = 0;
8461
8462 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8463     /*
8464      * Kludge to ignore BEL characters
8465      */
8466     while (*message == '\007') message++;
8467
8468     /*
8469      * [HGM] engine debug message: ignore lines starting with '#' character
8470      */
8471     if(cps->debug && *message == '#') return;
8472
8473     /*
8474      * Look for book output
8475      */
8476     if (cps == &first && bookRequested) {
8477         if (message[0] == '\t' || message[0] == ' ') {
8478             /* Part of the book output is here; append it */
8479             strcat(bookOutput, message);
8480             strcat(bookOutput, "  \n");
8481             return;
8482         } else if (bookOutput[0] != NULLCHAR) {
8483             /* All of book output has arrived; display it */
8484             char *p = bookOutput;
8485             while (*p != NULLCHAR) {
8486                 if (*p == '\t') *p = ' ';
8487                 p++;
8488             }
8489             DisplayInformation(bookOutput);
8490             bookRequested = FALSE;
8491             /* Fall through to parse the current output */
8492         }
8493     }
8494
8495     /*
8496      * Look for machine move.
8497      */
8498     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8499         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8500     {
8501         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8502             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8503             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8504             stalledEngine = cps;
8505             if(appData.ponderNextMove) { // bring opponent out of ponder
8506                 if(gameMode == TwoMachinesPlay) {
8507                     if(cps->other->pause)
8508                         PauseEngine(cps->other);
8509                     else
8510                         SendToProgram("easy\n", cps->other);
8511                 }
8512             }
8513             StopClocks();
8514             return;
8515         }
8516
8517         /* This method is only useful on engines that support ping */
8518         if (cps->lastPing != cps->lastPong) {
8519           if (gameMode == BeginningOfGame) {
8520             /* Extra move from before last new; ignore */
8521             if (appData.debugMode) {
8522                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8523             }
8524           } else {
8525             if (appData.debugMode) {
8526                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8527                         cps->which, gameMode);
8528             }
8529
8530             SendToProgram("undo\n", cps);
8531           }
8532           return;
8533         }
8534
8535         switch (gameMode) {
8536           case BeginningOfGame:
8537             /* Extra move from before last reset; ignore */
8538             if (appData.debugMode) {
8539                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8540             }
8541             return;
8542
8543           case EndOfGame:
8544           case IcsIdle:
8545           default:
8546             /* Extra move after we tried to stop.  The mode test is
8547                not a reliable way of detecting this problem, but it's
8548                the best we can do on engines that don't support ping.
8549             */
8550             if (appData.debugMode) {
8551                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8552                         cps->which, gameMode);
8553             }
8554             SendToProgram("undo\n", cps);
8555             return;
8556
8557           case MachinePlaysWhite:
8558           case IcsPlayingWhite:
8559             machineWhite = TRUE;
8560             break;
8561
8562           case MachinePlaysBlack:
8563           case IcsPlayingBlack:
8564             machineWhite = FALSE;
8565             break;
8566
8567           case TwoMachinesPlay:
8568             machineWhite = (cps->twoMachinesColor[0] == 'w');
8569             break;
8570         }
8571         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8572             if (appData.debugMode) {
8573                 fprintf(debugFP,
8574                         "Ignoring move out of turn by %s, gameMode %d"
8575                         ", forwardMost %d\n",
8576                         cps->which, gameMode, forwardMostMove);
8577             }
8578             return;
8579         }
8580
8581         if(cps->alphaRank) AlphaRank(machineMove, 4);
8582
8583         // [HGM] lion: (some very limited) support for Alien protocol
8584         killX = killY = -1;
8585         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8586             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8587             return;
8588         } else if(firstLeg[0]) { // there was a previous leg;
8589             // only support case where same piece makes two step (and don't even test that!)
8590             char buf[20], *p = machineMove+1, *q = buf+1, f;
8591             safeStrCpy(buf, machineMove, 20);
8592             while(isdigit(*q)) q++; // find start of to-square
8593             safeStrCpy(machineMove, firstLeg, 20);
8594             while(isdigit(*p)) p++;
8595             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8596             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8597             firstLeg[0] = NULLCHAR;
8598         }
8599
8600         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8601                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8602             /* Machine move could not be parsed; ignore it. */
8603           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8604                     machineMove, _(cps->which));
8605             DisplayMoveError(buf1);
8606             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8607                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8608             if (gameMode == TwoMachinesPlay) {
8609               GameEnds(machineWhite ? BlackWins : WhiteWins,
8610                        buf1, GE_XBOARD);
8611             }
8612             return;
8613         }
8614
8615         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8616         /* So we have to redo legality test with true e.p. status here,  */
8617         /* to make sure an illegal e.p. capture does not slip through,   */
8618         /* to cause a forfeit on a justified illegal-move complaint      */
8619         /* of the opponent.                                              */
8620         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8621            ChessMove moveType;
8622            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8623                              fromY, fromX, toY, toX, promoChar);
8624             if(moveType == IllegalMove) {
8625               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8626                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8627                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8628                            buf1, GE_XBOARD);
8629                 return;
8630            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8631            /* [HGM] Kludge to handle engines that send FRC-style castling
8632               when they shouldn't (like TSCP-Gothic) */
8633            switch(moveType) {
8634              case WhiteASideCastleFR:
8635              case BlackASideCastleFR:
8636                toX+=2;
8637                currentMoveString[2]++;
8638                break;
8639              case WhiteHSideCastleFR:
8640              case BlackHSideCastleFR:
8641                toX--;
8642                currentMoveString[2]--;
8643                break;
8644              default: ; // nothing to do, but suppresses warning of pedantic compilers
8645            }
8646         }
8647         hintRequested = FALSE;
8648         lastHint[0] = NULLCHAR;
8649         bookRequested = FALSE;
8650         /* Program may be pondering now */
8651         cps->maybeThinking = TRUE;
8652         if (cps->sendTime == 2) cps->sendTime = 1;
8653         if (cps->offeredDraw) cps->offeredDraw--;
8654
8655         /* [AS] Save move info*/
8656         pvInfoList[ forwardMostMove ].score = programStats.score;
8657         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8658         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8659
8660         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8661
8662         /* Test suites abort the 'game' after one move */
8663         if(*appData.finger) {
8664            static FILE *f;
8665            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8666            if(!f) f = fopen(appData.finger, "w");
8667            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8668            else { DisplayFatalError("Bad output file", errno, 0); return; }
8669            free(fen);
8670            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8671         }
8672
8673         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8674         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8675             int count = 0;
8676
8677             while( count < adjudicateLossPlies ) {
8678                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8679
8680                 if( count & 1 ) {
8681                     score = -score; /* Flip score for winning side */
8682                 }
8683
8684                 if( score > adjudicateLossThreshold ) {
8685                     break;
8686                 }
8687
8688                 count++;
8689             }
8690
8691             if( count >= adjudicateLossPlies ) {
8692                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8693
8694                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8695                     "Xboard adjudication",
8696                     GE_XBOARD );
8697
8698                 return;
8699             }
8700         }
8701
8702         if(Adjudicate(cps)) {
8703             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8704             return; // [HGM] adjudicate: for all automatic game ends
8705         }
8706
8707 #if ZIPPY
8708         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8709             first.initDone) {
8710           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8711                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8712                 SendToICS("draw ");
8713                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8714           }
8715           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8716           ics_user_moved = 1;
8717           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8718                 char buf[3*MSG_SIZ];
8719
8720                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8721                         programStats.score / 100.,
8722                         programStats.depth,
8723                         programStats.time / 100.,
8724                         (unsigned int)programStats.nodes,
8725                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8726                         programStats.movelist);
8727                 SendToICS(buf);
8728 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8729           }
8730         }
8731 #endif
8732
8733         /* [AS] Clear stats for next move */
8734         ClearProgramStats();
8735         thinkOutput[0] = NULLCHAR;
8736         hiddenThinkOutputState = 0;
8737
8738         bookHit = NULL;
8739         if (gameMode == TwoMachinesPlay) {
8740             /* [HGM] relaying draw offers moved to after reception of move */
8741             /* and interpreting offer as claim if it brings draw condition */
8742             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8743                 SendToProgram("draw\n", cps->other);
8744             }
8745             if (cps->other->sendTime) {
8746                 SendTimeRemaining(cps->other,
8747                                   cps->other->twoMachinesColor[0] == 'w');
8748             }
8749             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8750             if (firstMove && !bookHit) {
8751                 firstMove = FALSE;
8752                 if (cps->other->useColors) {
8753                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8754                 }
8755                 SendToProgram("go\n", cps->other);
8756             }
8757             cps->other->maybeThinking = TRUE;
8758         }
8759
8760         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8761
8762         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8763
8764         if (!pausing && appData.ringBellAfterMoves) {
8765             if(!roar) RingBell();
8766         }
8767
8768         /*
8769          * Reenable menu items that were disabled while
8770          * machine was thinking
8771          */
8772         if (gameMode != TwoMachinesPlay)
8773             SetUserThinkingEnables();
8774
8775         // [HGM] book: after book hit opponent has received move and is now in force mode
8776         // force the book reply into it, and then fake that it outputted this move by jumping
8777         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8778         if(bookHit) {
8779                 static char bookMove[MSG_SIZ]; // a bit generous?
8780
8781                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8782                 strcat(bookMove, bookHit);
8783                 message = bookMove;
8784                 cps = cps->other;
8785                 programStats.nodes = programStats.depth = programStats.time =
8786                 programStats.score = programStats.got_only_move = 0;
8787                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8788
8789                 if(cps->lastPing != cps->lastPong) {
8790                     savedMessage = message; // args for deferred call
8791                     savedState = cps;
8792                     ScheduleDelayedEvent(DeferredBookMove, 10);
8793                     return;
8794                 }
8795                 goto FakeBookMove;
8796         }
8797
8798         return;
8799     }
8800
8801     /* Set special modes for chess engines.  Later something general
8802      *  could be added here; for now there is just one kludge feature,
8803      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8804      *  when "xboard" is given as an interactive command.
8805      */
8806     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8807         cps->useSigint = FALSE;
8808         cps->useSigterm = FALSE;
8809     }
8810     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8811       ParseFeatures(message+8, cps);
8812       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8813     }
8814
8815     if (!strncmp(message, "setup ", 6) && 
8816         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8817           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8818                                         ) { // [HGM] allow first engine to define opening position
8819       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8820       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8821       *buf = NULLCHAR;
8822       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8823       if(startedFromSetupPosition) return;
8824       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8825       if(dummy >= 3) {
8826         while(message[s] && message[s++] != ' ');
8827         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8828            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8829             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8830             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8831           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8832           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8833         }
8834       }
8835       ParseFEN(boards[0], &dummy, message+s, FALSE);
8836       DrawPosition(TRUE, boards[0]);
8837       startedFromSetupPosition = TRUE;
8838       return;
8839     }
8840     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8841      * want this, I was asked to put it in, and obliged.
8842      */
8843     if (!strncmp(message, "setboard ", 9)) {
8844         Board initial_position;
8845
8846         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8847
8848         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8849             DisplayError(_("Bad FEN received from engine"), 0);
8850             return ;
8851         } else {
8852            Reset(TRUE, FALSE);
8853            CopyBoard(boards[0], initial_position);
8854            initialRulePlies = FENrulePlies;
8855            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8856            else gameMode = MachinePlaysBlack;
8857            DrawPosition(FALSE, boards[currentMove]);
8858         }
8859         return;
8860     }
8861
8862     /*
8863      * Look for communication commands
8864      */
8865     if (!strncmp(message, "telluser ", 9)) {
8866         if(message[9] == '\\' && message[10] == '\\')
8867             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8868         PlayTellSound();
8869         DisplayNote(message + 9);
8870         return;
8871     }
8872     if (!strncmp(message, "tellusererror ", 14)) {
8873         cps->userError = 1;
8874         if(message[14] == '\\' && message[15] == '\\')
8875             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8876         PlayTellSound();
8877         DisplayError(message + 14, 0);
8878         return;
8879     }
8880     if (!strncmp(message, "tellopponent ", 13)) {
8881       if (appData.icsActive) {
8882         if (loggedOn) {
8883           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8884           SendToICS(buf1);
8885         }
8886       } else {
8887         DisplayNote(message + 13);
8888       }
8889       return;
8890     }
8891     if (!strncmp(message, "tellothers ", 11)) {
8892       if (appData.icsActive) {
8893         if (loggedOn) {
8894           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8895           SendToICS(buf1);
8896         }
8897       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8898       return;
8899     }
8900     if (!strncmp(message, "tellall ", 8)) {
8901       if (appData.icsActive) {
8902         if (loggedOn) {
8903           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8904           SendToICS(buf1);
8905         }
8906       } else {
8907         DisplayNote(message + 8);
8908       }
8909       return;
8910     }
8911     if (strncmp(message, "warning", 7) == 0) {
8912         /* Undocumented feature, use tellusererror in new code */
8913         DisplayError(message, 0);
8914         return;
8915     }
8916     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8917         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8918         strcat(realname, " query");
8919         AskQuestion(realname, buf2, buf1, cps->pr);
8920         return;
8921     }
8922     /* Commands from the engine directly to ICS.  We don't allow these to be
8923      *  sent until we are logged on. Crafty kibitzes have been known to
8924      *  interfere with the login process.
8925      */
8926     if (loggedOn) {
8927         if (!strncmp(message, "tellics ", 8)) {
8928             SendToICS(message + 8);
8929             SendToICS("\n");
8930             return;
8931         }
8932         if (!strncmp(message, "tellicsnoalias ", 15)) {
8933             SendToICS(ics_prefix);
8934             SendToICS(message + 15);
8935             SendToICS("\n");
8936             return;
8937         }
8938         /* The following are for backward compatibility only */
8939         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8940             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8941             SendToICS(ics_prefix);
8942             SendToICS(message);
8943             SendToICS("\n");
8944             return;
8945         }
8946     }
8947     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8948         if(initPing == cps->lastPong) {
8949             if(gameInfo.variant == VariantUnknown) {
8950                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8951                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8952                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8953             }
8954             initPing = -1;
8955         }
8956         return;
8957     }
8958     if(!strncmp(message, "highlight ", 10)) {
8959         if(appData.testLegality && appData.markers) return;
8960         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8961         return;
8962     }
8963     if(!strncmp(message, "click ", 6)) {
8964         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8965         if(appData.testLegality || !appData.oneClick) return;
8966         sscanf(message+6, "%c%d%c", &f, &y, &c);
8967         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8968         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8969         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8970         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8971         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8972         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8973             LeftClick(Release, lastLeftX, lastLeftY);
8974         controlKey  = (c == ',');
8975         LeftClick(Press, x, y);
8976         LeftClick(Release, x, y);
8977         first.highlight = f;
8978         return;
8979     }
8980     /*
8981      * If the move is illegal, cancel it and redraw the board.
8982      * Also deal with other error cases.  Matching is rather loose
8983      * here to accommodate engines written before the spec.
8984      */
8985     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8986         strncmp(message, "Error", 5) == 0) {
8987         if (StrStr(message, "name") ||
8988             StrStr(message, "rating") || StrStr(message, "?") ||
8989             StrStr(message, "result") || StrStr(message, "board") ||
8990             StrStr(message, "bk") || StrStr(message, "computer") ||
8991             StrStr(message, "variant") || StrStr(message, "hint") ||
8992             StrStr(message, "random") || StrStr(message, "depth") ||
8993             StrStr(message, "accepted")) {
8994             return;
8995         }
8996         if (StrStr(message, "protover")) {
8997           /* Program is responding to input, so it's apparently done
8998              initializing, and this error message indicates it is
8999              protocol version 1.  So we don't need to wait any longer
9000              for it to initialize and send feature commands. */
9001           FeatureDone(cps, 1);
9002           cps->protocolVersion = 1;
9003           return;
9004         }
9005         cps->maybeThinking = FALSE;
9006
9007         if (StrStr(message, "draw")) {
9008             /* Program doesn't have "draw" command */
9009             cps->sendDrawOffers = 0;
9010             return;
9011         }
9012         if (cps->sendTime != 1 &&
9013             (StrStr(message, "time") || StrStr(message, "otim"))) {
9014           /* Program apparently doesn't have "time" or "otim" command */
9015           cps->sendTime = 0;
9016           return;
9017         }
9018         if (StrStr(message, "analyze")) {
9019             cps->analysisSupport = FALSE;
9020             cps->analyzing = FALSE;
9021 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9022             EditGameEvent(); // [HGM] try to preserve loaded game
9023             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9024             DisplayError(buf2, 0);
9025             return;
9026         }
9027         if (StrStr(message, "(no matching move)st")) {
9028           /* Special kludge for GNU Chess 4 only */
9029           cps->stKludge = TRUE;
9030           SendTimeControl(cps, movesPerSession, timeControl,
9031                           timeIncrement, appData.searchDepth,
9032                           searchTime);
9033           return;
9034         }
9035         if (StrStr(message, "(no matching move)sd")) {
9036           /* Special kludge for GNU Chess 4 only */
9037           cps->sdKludge = TRUE;
9038           SendTimeControl(cps, movesPerSession, timeControl,
9039                           timeIncrement, appData.searchDepth,
9040                           searchTime);
9041           return;
9042         }
9043         if (!StrStr(message, "llegal")) {
9044             return;
9045         }
9046         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9047             gameMode == IcsIdle) return;
9048         if (forwardMostMove <= backwardMostMove) return;
9049         if (pausing) PauseEvent();
9050       if(appData.forceIllegal) {
9051             // [HGM] illegal: machine refused move; force position after move into it
9052           SendToProgram("force\n", cps);
9053           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9054                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9055                 // when black is to move, while there might be nothing on a2 or black
9056                 // might already have the move. So send the board as if white has the move.
9057                 // But first we must change the stm of the engine, as it refused the last move
9058                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9059                 if(WhiteOnMove(forwardMostMove)) {
9060                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9061                     SendBoard(cps, forwardMostMove); // kludgeless board
9062                 } else {
9063                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9064                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9065                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9066                 }
9067           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9068             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9069                  gameMode == TwoMachinesPlay)
9070               SendToProgram("go\n", cps);
9071             return;
9072       } else
9073         if (gameMode == PlayFromGameFile) {
9074             /* Stop reading this game file */
9075             gameMode = EditGame;
9076             ModeHighlight();
9077         }
9078         /* [HGM] illegal-move claim should forfeit game when Xboard */
9079         /* only passes fully legal moves                            */
9080         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9081             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9082                                 "False illegal-move claim", GE_XBOARD );
9083             return; // do not take back move we tested as valid
9084         }
9085         currentMove = forwardMostMove-1;
9086         DisplayMove(currentMove-1); /* before DisplayMoveError */
9087         SwitchClocks(forwardMostMove-1); // [HGM] race
9088         DisplayBothClocks();
9089         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9090                 parseList[currentMove], _(cps->which));
9091         DisplayMoveError(buf1);
9092         DrawPosition(FALSE, boards[currentMove]);
9093
9094         SetUserThinkingEnables();
9095         return;
9096     }
9097     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9098         /* Program has a broken "time" command that
9099            outputs a string not ending in newline.
9100            Don't use it. */
9101         cps->sendTime = 0;
9102     }
9103
9104     /*
9105      * If chess program startup fails, exit with an error message.
9106      * Attempts to recover here are futile. [HGM] Well, we try anyway
9107      */
9108     if ((StrStr(message, "unknown host") != NULL)
9109         || (StrStr(message, "No remote directory") != NULL)
9110         || (StrStr(message, "not found") != NULL)
9111         || (StrStr(message, "No such file") != NULL)
9112         || (StrStr(message, "can't alloc") != NULL)
9113         || (StrStr(message, "Permission denied") != NULL)) {
9114
9115         cps->maybeThinking = FALSE;
9116         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9117                 _(cps->which), cps->program, cps->host, message);
9118         RemoveInputSource(cps->isr);
9119         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9120             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9121             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9122         }
9123         return;
9124     }
9125
9126     /*
9127      * Look for hint output
9128      */
9129     if (sscanf(message, "Hint: %s", buf1) == 1) {
9130         if (cps == &first && hintRequested) {
9131             hintRequested = FALSE;
9132             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9133                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9134                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9135                                     PosFlags(forwardMostMove),
9136                                     fromY, fromX, toY, toX, promoChar, buf1);
9137                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9138                 DisplayInformation(buf2);
9139             } else {
9140                 /* Hint move could not be parsed!? */
9141               snprintf(buf2, sizeof(buf2),
9142                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9143                         buf1, _(cps->which));
9144                 DisplayError(buf2, 0);
9145             }
9146         } else {
9147           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9148         }
9149         return;
9150     }
9151
9152     /*
9153      * Ignore other messages if game is not in progress
9154      */
9155     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9156         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9157
9158     /*
9159      * look for win, lose, draw, or draw offer
9160      */
9161     if (strncmp(message, "1-0", 3) == 0) {
9162         char *p, *q, *r = "";
9163         p = strchr(message, '{');
9164         if (p) {
9165             q = strchr(p, '}');
9166             if (q) {
9167                 *q = NULLCHAR;
9168                 r = p + 1;
9169             }
9170         }
9171         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9172         return;
9173     } else if (strncmp(message, "0-1", 3) == 0) {
9174         char *p, *q, *r = "";
9175         p = strchr(message, '{');
9176         if (p) {
9177             q = strchr(p, '}');
9178             if (q) {
9179                 *q = NULLCHAR;
9180                 r = p + 1;
9181             }
9182         }
9183         /* Kludge for Arasan 4.1 bug */
9184         if (strcmp(r, "Black resigns") == 0) {
9185             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9186             return;
9187         }
9188         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9189         return;
9190     } else if (strncmp(message, "1/2", 3) == 0) {
9191         char *p, *q, *r = "";
9192         p = strchr(message, '{');
9193         if (p) {
9194             q = strchr(p, '}');
9195             if (q) {
9196                 *q = NULLCHAR;
9197                 r = p + 1;
9198             }
9199         }
9200
9201         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9202         return;
9203
9204     } else if (strncmp(message, "White resign", 12) == 0) {
9205         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9206         return;
9207     } else if (strncmp(message, "Black resign", 12) == 0) {
9208         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9209         return;
9210     } else if (strncmp(message, "White matches", 13) == 0 ||
9211                strncmp(message, "Black matches", 13) == 0   ) {
9212         /* [HGM] ignore GNUShogi noises */
9213         return;
9214     } else if (strncmp(message, "White", 5) == 0 &&
9215                message[5] != '(' &&
9216                StrStr(message, "Black") == NULL) {
9217         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9218         return;
9219     } else if (strncmp(message, "Black", 5) == 0 &&
9220                message[5] != '(') {
9221         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9222         return;
9223     } else if (strcmp(message, "resign") == 0 ||
9224                strcmp(message, "computer resigns") == 0) {
9225         switch (gameMode) {
9226           case MachinePlaysBlack:
9227           case IcsPlayingBlack:
9228             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9229             break;
9230           case MachinePlaysWhite:
9231           case IcsPlayingWhite:
9232             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9233             break;
9234           case TwoMachinesPlay:
9235             if (cps->twoMachinesColor[0] == 'w')
9236               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9237             else
9238               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9239             break;
9240           default:
9241             /* can't happen */
9242             break;
9243         }
9244         return;
9245     } else if (strncmp(message, "opponent mates", 14) == 0) {
9246         switch (gameMode) {
9247           case MachinePlaysBlack:
9248           case IcsPlayingBlack:
9249             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9250             break;
9251           case MachinePlaysWhite:
9252           case IcsPlayingWhite:
9253             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9254             break;
9255           case TwoMachinesPlay:
9256             if (cps->twoMachinesColor[0] == 'w')
9257               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9258             else
9259               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9260             break;
9261           default:
9262             /* can't happen */
9263             break;
9264         }
9265         return;
9266     } else if (strncmp(message, "computer mates", 14) == 0) {
9267         switch (gameMode) {
9268           case MachinePlaysBlack:
9269           case IcsPlayingBlack:
9270             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9271             break;
9272           case MachinePlaysWhite:
9273           case IcsPlayingWhite:
9274             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9275             break;
9276           case TwoMachinesPlay:
9277             if (cps->twoMachinesColor[0] == 'w')
9278               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9279             else
9280               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9281             break;
9282           default:
9283             /* can't happen */
9284             break;
9285         }
9286         return;
9287     } else if (strncmp(message, "checkmate", 9) == 0) {
9288         if (WhiteOnMove(forwardMostMove)) {
9289             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9290         } else {
9291             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9292         }
9293         return;
9294     } else if (strstr(message, "Draw") != NULL ||
9295                strstr(message, "game is a draw") != NULL) {
9296         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9297         return;
9298     } else if (strstr(message, "offer") != NULL &&
9299                strstr(message, "draw") != NULL) {
9300 #if ZIPPY
9301         if (appData.zippyPlay && first.initDone) {
9302             /* Relay offer to ICS */
9303             SendToICS(ics_prefix);
9304             SendToICS("draw\n");
9305         }
9306 #endif
9307         cps->offeredDraw = 2; /* valid until this engine moves twice */
9308         if (gameMode == TwoMachinesPlay) {
9309             if (cps->other->offeredDraw) {
9310                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9311             /* [HGM] in two-machine mode we delay relaying draw offer      */
9312             /* until after we also have move, to see if it is really claim */
9313             }
9314         } else if (gameMode == MachinePlaysWhite ||
9315                    gameMode == MachinePlaysBlack) {
9316           if (userOfferedDraw) {
9317             DisplayInformation(_("Machine accepts your draw offer"));
9318             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9319           } else {
9320             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9321           }
9322         }
9323     }
9324
9325
9326     /*
9327      * Look for thinking output
9328      */
9329     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9330           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9331                                 ) {
9332         int plylev, mvleft, mvtot, curscore, time;
9333         char mvname[MOVE_LEN];
9334         u64 nodes; // [DM]
9335         char plyext;
9336         int ignore = FALSE;
9337         int prefixHint = FALSE;
9338         mvname[0] = NULLCHAR;
9339
9340         switch (gameMode) {
9341           case MachinePlaysBlack:
9342           case IcsPlayingBlack:
9343             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9344             break;
9345           case MachinePlaysWhite:
9346           case IcsPlayingWhite:
9347             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9348             break;
9349           case AnalyzeMode:
9350           case AnalyzeFile:
9351             break;
9352           case IcsObserving: /* [DM] icsEngineAnalyze */
9353             if (!appData.icsEngineAnalyze) ignore = TRUE;
9354             break;
9355           case TwoMachinesPlay:
9356             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9357                 ignore = TRUE;
9358             }
9359             break;
9360           default:
9361             ignore = TRUE;
9362             break;
9363         }
9364
9365         if (!ignore) {
9366             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9367             buf1[0] = NULLCHAR;
9368             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9369                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9370
9371                 if (plyext != ' ' && plyext != '\t') {
9372                     time *= 100;
9373                 }
9374
9375                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9376                 if( cps->scoreIsAbsolute &&
9377                     ( gameMode == MachinePlaysBlack ||
9378                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9379                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9380                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9381                      !WhiteOnMove(currentMove)
9382                     ) )
9383                 {
9384                     curscore = -curscore;
9385                 }
9386
9387                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9388
9389                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9390                         char buf[MSG_SIZ];
9391                         FILE *f;
9392                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9393                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9394                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9395                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9396                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9397                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9398                                 fclose(f);
9399                         }
9400                         else
9401                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9402                           DisplayError(_("failed writing PV"), 0);
9403                 }
9404
9405                 tempStats.depth = plylev;
9406                 tempStats.nodes = nodes;
9407                 tempStats.time = time;
9408                 tempStats.score = curscore;
9409                 tempStats.got_only_move = 0;
9410
9411                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9412                         int ticklen;
9413
9414                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9415                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9416                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9417                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9418                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9419                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9420                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9421                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9422                 }
9423
9424                 /* Buffer overflow protection */
9425                 if (pv[0] != NULLCHAR) {
9426                     if (strlen(pv) >= sizeof(tempStats.movelist)
9427                         && appData.debugMode) {
9428                         fprintf(debugFP,
9429                                 "PV is too long; using the first %u bytes.\n",
9430                                 (unsigned) sizeof(tempStats.movelist) - 1);
9431                     }
9432
9433                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9434                 } else {
9435                     sprintf(tempStats.movelist, " no PV\n");
9436                 }
9437
9438                 if (tempStats.seen_stat) {
9439                     tempStats.ok_to_send = 1;
9440                 }
9441
9442                 if (strchr(tempStats.movelist, '(') != NULL) {
9443                     tempStats.line_is_book = 1;
9444                     tempStats.nr_moves = 0;
9445                     tempStats.moves_left = 0;
9446                 } else {
9447                     tempStats.line_is_book = 0;
9448                 }
9449
9450                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9451                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9452
9453                 SendProgramStatsToFrontend( cps, &tempStats );
9454
9455                 /*
9456                     [AS] Protect the thinkOutput buffer from overflow... this
9457                     is only useful if buf1 hasn't overflowed first!
9458                 */
9459                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9460                          plylev,
9461                          (gameMode == TwoMachinesPlay ?
9462                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9463                          ((double) curscore) / 100.0,
9464                          prefixHint ? lastHint : "",
9465                          prefixHint ? " " : "" );
9466
9467                 if( buf1[0] != NULLCHAR ) {
9468                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9469
9470                     if( strlen(pv) > max_len ) {
9471                         if( appData.debugMode) {
9472                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9473                         }
9474                         pv[max_len+1] = '\0';
9475                     }
9476
9477                     strcat( thinkOutput, pv);
9478                 }
9479
9480                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9481                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9482                     DisplayMove(currentMove - 1);
9483                 }
9484                 return;
9485
9486             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9487                 /* crafty (9.25+) says "(only move) <move>"
9488                  * if there is only 1 legal move
9489                  */
9490                 sscanf(p, "(only move) %s", buf1);
9491                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9492                 sprintf(programStats.movelist, "%s (only move)", buf1);
9493                 programStats.depth = 1;
9494                 programStats.nr_moves = 1;
9495                 programStats.moves_left = 1;
9496                 programStats.nodes = 1;
9497                 programStats.time = 1;
9498                 programStats.got_only_move = 1;
9499
9500                 /* Not really, but we also use this member to
9501                    mean "line isn't going to change" (Crafty
9502                    isn't searching, so stats won't change) */
9503                 programStats.line_is_book = 1;
9504
9505                 SendProgramStatsToFrontend( cps, &programStats );
9506
9507                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9508                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9509                     DisplayMove(currentMove - 1);
9510                 }
9511                 return;
9512             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9513                               &time, &nodes, &plylev, &mvleft,
9514                               &mvtot, mvname) >= 5) {
9515                 /* The stat01: line is from Crafty (9.29+) in response
9516                    to the "." command */
9517                 programStats.seen_stat = 1;
9518                 cps->maybeThinking = TRUE;
9519
9520                 if (programStats.got_only_move || !appData.periodicUpdates)
9521                   return;
9522
9523                 programStats.depth = plylev;
9524                 programStats.time = time;
9525                 programStats.nodes = nodes;
9526                 programStats.moves_left = mvleft;
9527                 programStats.nr_moves = mvtot;
9528                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9529                 programStats.ok_to_send = 1;
9530                 programStats.movelist[0] = '\0';
9531
9532                 SendProgramStatsToFrontend( cps, &programStats );
9533
9534                 return;
9535
9536             } else if (strncmp(message,"++",2) == 0) {
9537                 /* Crafty 9.29+ outputs this */
9538                 programStats.got_fail = 2;
9539                 return;
9540
9541             } else if (strncmp(message,"--",2) == 0) {
9542                 /* Crafty 9.29+ outputs this */
9543                 programStats.got_fail = 1;
9544                 return;
9545
9546             } else if (thinkOutput[0] != NULLCHAR &&
9547                        strncmp(message, "    ", 4) == 0) {
9548                 unsigned message_len;
9549
9550                 p = message;
9551                 while (*p && *p == ' ') p++;
9552
9553                 message_len = strlen( p );
9554
9555                 /* [AS] Avoid buffer overflow */
9556                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9557                     strcat(thinkOutput, " ");
9558                     strcat(thinkOutput, p);
9559                 }
9560
9561                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9562                     strcat(programStats.movelist, " ");
9563                     strcat(programStats.movelist, p);
9564                 }
9565
9566                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9567                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9568                     DisplayMove(currentMove - 1);
9569                 }
9570                 return;
9571             }
9572         }
9573         else {
9574             buf1[0] = NULLCHAR;
9575
9576             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9577                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9578             {
9579                 ChessProgramStats cpstats;
9580
9581                 if (plyext != ' ' && plyext != '\t') {
9582                     time *= 100;
9583                 }
9584
9585                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9586                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9587                     curscore = -curscore;
9588                 }
9589
9590                 cpstats.depth = plylev;
9591                 cpstats.nodes = nodes;
9592                 cpstats.time = time;
9593                 cpstats.score = curscore;
9594                 cpstats.got_only_move = 0;
9595                 cpstats.movelist[0] = '\0';
9596
9597                 if (buf1[0] != NULLCHAR) {
9598                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9599                 }
9600
9601                 cpstats.ok_to_send = 0;
9602                 cpstats.line_is_book = 0;
9603                 cpstats.nr_moves = 0;
9604                 cpstats.moves_left = 0;
9605
9606                 SendProgramStatsToFrontend( cps, &cpstats );
9607             }
9608         }
9609     }
9610 }
9611
9612
9613 /* Parse a game score from the character string "game", and
9614    record it as the history of the current game.  The game
9615    score is NOT assumed to start from the standard position.
9616    The display is not updated in any way.
9617    */
9618 void
9619 ParseGameHistory (char *game)
9620 {
9621     ChessMove moveType;
9622     int fromX, fromY, toX, toY, boardIndex;
9623     char promoChar;
9624     char *p, *q;
9625     char buf[MSG_SIZ];
9626
9627     if (appData.debugMode)
9628       fprintf(debugFP, "Parsing game history: %s\n", game);
9629
9630     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9631     gameInfo.site = StrSave(appData.icsHost);
9632     gameInfo.date = PGNDate();
9633     gameInfo.round = StrSave("-");
9634
9635     /* Parse out names of players */
9636     while (*game == ' ') game++;
9637     p = buf;
9638     while (*game != ' ') *p++ = *game++;
9639     *p = NULLCHAR;
9640     gameInfo.white = StrSave(buf);
9641     while (*game == ' ') game++;
9642     p = buf;
9643     while (*game != ' ' && *game != '\n') *p++ = *game++;
9644     *p = NULLCHAR;
9645     gameInfo.black = StrSave(buf);
9646
9647     /* Parse moves */
9648     boardIndex = blackPlaysFirst ? 1 : 0;
9649     yynewstr(game);
9650     for (;;) {
9651         yyboardindex = boardIndex;
9652         moveType = (ChessMove) Myylex();
9653         switch (moveType) {
9654           case IllegalMove:             /* maybe suicide chess, etc. */
9655   if (appData.debugMode) {
9656     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9657     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9658     setbuf(debugFP, NULL);
9659   }
9660           case WhitePromotion:
9661           case BlackPromotion:
9662           case WhiteNonPromotion:
9663           case BlackNonPromotion:
9664           case NormalMove:
9665           case FirstLeg:
9666           case WhiteCapturesEnPassant:
9667           case BlackCapturesEnPassant:
9668           case WhiteKingSideCastle:
9669           case WhiteQueenSideCastle:
9670           case BlackKingSideCastle:
9671           case BlackQueenSideCastle:
9672           case WhiteKingSideCastleWild:
9673           case WhiteQueenSideCastleWild:
9674           case BlackKingSideCastleWild:
9675           case BlackQueenSideCastleWild:
9676           /* PUSH Fabien */
9677           case WhiteHSideCastleFR:
9678           case WhiteASideCastleFR:
9679           case BlackHSideCastleFR:
9680           case BlackASideCastleFR:
9681           /* POP Fabien */
9682             fromX = currentMoveString[0] - AAA;
9683             fromY = currentMoveString[1] - ONE;
9684             toX = currentMoveString[2] - AAA;
9685             toY = currentMoveString[3] - ONE;
9686             promoChar = currentMoveString[4];
9687             break;
9688           case WhiteDrop:
9689           case BlackDrop:
9690             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9691             fromX = moveType == WhiteDrop ?
9692               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9693             (int) CharToPiece(ToLower(currentMoveString[0]));
9694             fromY = DROP_RANK;
9695             toX = currentMoveString[2] - AAA;
9696             toY = currentMoveString[3] - ONE;
9697             promoChar = NULLCHAR;
9698             break;
9699           case AmbiguousMove:
9700             /* bug? */
9701             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9702   if (appData.debugMode) {
9703     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9704     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9705     setbuf(debugFP, NULL);
9706   }
9707             DisplayError(buf, 0);
9708             return;
9709           case ImpossibleMove:
9710             /* bug? */
9711             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9712   if (appData.debugMode) {
9713     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9714     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9715     setbuf(debugFP, NULL);
9716   }
9717             DisplayError(buf, 0);
9718             return;
9719           case EndOfFile:
9720             if (boardIndex < backwardMostMove) {
9721                 /* Oops, gap.  How did that happen? */
9722                 DisplayError(_("Gap in move list"), 0);
9723                 return;
9724             }
9725             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9726             if (boardIndex > forwardMostMove) {
9727                 forwardMostMove = boardIndex;
9728             }
9729             return;
9730           case ElapsedTime:
9731             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9732                 strcat(parseList[boardIndex-1], " ");
9733                 strcat(parseList[boardIndex-1], yy_text);
9734             }
9735             continue;
9736           case Comment:
9737           case PGNTag:
9738           case NAG:
9739           default:
9740             /* ignore */
9741             continue;
9742           case WhiteWins:
9743           case BlackWins:
9744           case GameIsDrawn:
9745           case GameUnfinished:
9746             if (gameMode == IcsExamining) {
9747                 if (boardIndex < backwardMostMove) {
9748                     /* Oops, gap.  How did that happen? */
9749                     return;
9750                 }
9751                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9752                 return;
9753             }
9754             gameInfo.result = moveType;
9755             p = strchr(yy_text, '{');
9756             if (p == NULL) p = strchr(yy_text, '(');
9757             if (p == NULL) {
9758                 p = yy_text;
9759                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9760             } else {
9761                 q = strchr(p, *p == '{' ? '}' : ')');
9762                 if (q != NULL) *q = NULLCHAR;
9763                 p++;
9764             }
9765             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9766             gameInfo.resultDetails = StrSave(p);
9767             continue;
9768         }
9769         if (boardIndex >= forwardMostMove &&
9770             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9771             backwardMostMove = blackPlaysFirst ? 1 : 0;
9772             return;
9773         }
9774         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9775                                  fromY, fromX, toY, toX, promoChar,
9776                                  parseList[boardIndex]);
9777         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9778         /* currentMoveString is set as a side-effect of yylex */
9779         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9780         strcat(moveList[boardIndex], "\n");
9781         boardIndex++;
9782         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9783         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9784           case MT_NONE:
9785           case MT_STALEMATE:
9786           default:
9787             break;
9788           case MT_CHECK:
9789             if(!IS_SHOGI(gameInfo.variant))
9790                 strcat(parseList[boardIndex - 1], "+");
9791             break;
9792           case MT_CHECKMATE:
9793           case MT_STAINMATE:
9794             strcat(parseList[boardIndex - 1], "#");
9795             break;
9796         }
9797     }
9798 }
9799
9800
9801 /* Apply a move to the given board  */
9802 void
9803 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9804 {
9805   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9806   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9807
9808     /* [HGM] compute & store e.p. status and castling rights for new position */
9809     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9810
9811       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9812       oldEP = (signed char)board[EP_STATUS];
9813       board[EP_STATUS] = EP_NONE;
9814
9815   if (fromY == DROP_RANK) {
9816         /* must be first */
9817         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9818             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9819             return;
9820         }
9821         piece = board[toY][toX] = (ChessSquare) fromX;
9822   } else {
9823       ChessSquare victim;
9824       int i;
9825
9826       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9827            victim = board[killY][killX],
9828            board[killY][killX] = EmptySquare,
9829            board[EP_STATUS] = EP_CAPTURE;
9830
9831       if( board[toY][toX] != EmptySquare ) {
9832            board[EP_STATUS] = EP_CAPTURE;
9833            if( (fromX != toX || fromY != toY) && // not igui!
9834                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9835                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9836                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9837            }
9838       }
9839
9840       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9841            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9842                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9843       } else
9844       if( board[fromY][fromX] == WhitePawn ) {
9845            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9846                board[EP_STATUS] = EP_PAWN_MOVE;
9847            if( toY-fromY==2) {
9848                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9849                         gameInfo.variant != VariantBerolina || toX < fromX)
9850                       board[EP_STATUS] = toX | berolina;
9851                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9852                         gameInfo.variant != VariantBerolina || toX > fromX)
9853                       board[EP_STATUS] = toX;
9854            }
9855       } else
9856       if( board[fromY][fromX] == BlackPawn ) {
9857            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9858                board[EP_STATUS] = EP_PAWN_MOVE;
9859            if( toY-fromY== -2) {
9860                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9861                         gameInfo.variant != VariantBerolina || toX < fromX)
9862                       board[EP_STATUS] = toX | berolina;
9863                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9864                         gameInfo.variant != VariantBerolina || toX > fromX)
9865                       board[EP_STATUS] = toX;
9866            }
9867        }
9868
9869        for(i=0; i<nrCastlingRights; i++) {
9870            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9871               board[CASTLING][i] == toX   && castlingRank[i] == toY
9872              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9873        }
9874
9875        if(gameInfo.variant == VariantSChess) { // update virginity
9876            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9877            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9878            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9879            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9880        }
9881
9882      if (fromX == toX && fromY == toY) return;
9883
9884      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9885      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9886      if(gameInfo.variant == VariantKnightmate)
9887          king += (int) WhiteUnicorn - (int) WhiteKing;
9888
9889     /* Code added by Tord: */
9890     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9891     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9892         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9893       board[fromY][fromX] = EmptySquare;
9894       board[toY][toX] = EmptySquare;
9895       if((toX > fromX) != (piece == WhiteRook)) {
9896         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9897       } else {
9898         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9899       }
9900     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9901                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9902       board[fromY][fromX] = EmptySquare;
9903       board[toY][toX] = EmptySquare;
9904       if((toX > fromX) != (piece == BlackRook)) {
9905         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9906       } else {
9907         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9908       }
9909     /* End of code added by Tord */
9910
9911     } else if (board[fromY][fromX] == king
9912         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9913         && toY == fromY && toX > fromX+1) {
9914         board[fromY][fromX] = EmptySquare;
9915         board[toY][toX] = king;
9916         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9917         board[fromY][BOARD_RGHT-1] = EmptySquare;
9918     } else if (board[fromY][fromX] == king
9919         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9920                && toY == fromY && toX < fromX-1) {
9921         board[fromY][fromX] = EmptySquare;
9922         board[toY][toX] = king;
9923         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9924         board[fromY][BOARD_LEFT] = EmptySquare;
9925     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9926                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9927                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9928                ) {
9929         /* white pawn promotion */
9930         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9931         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9932             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9933         board[fromY][fromX] = EmptySquare;
9934     } else if ((fromY >= BOARD_HEIGHT>>1)
9935                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9936                && (toX != fromX)
9937                && gameInfo.variant != VariantXiangqi
9938                && gameInfo.variant != VariantBerolina
9939                && (board[fromY][fromX] == WhitePawn)
9940                && (board[toY][toX] == EmptySquare)) {
9941         board[fromY][fromX] = EmptySquare;
9942         board[toY][toX] = WhitePawn;
9943         captured = board[toY - 1][toX];
9944         board[toY - 1][toX] = EmptySquare;
9945     } else if ((fromY == BOARD_HEIGHT-4)
9946                && (toX == fromX)
9947                && gameInfo.variant == VariantBerolina
9948                && (board[fromY][fromX] == WhitePawn)
9949                && (board[toY][toX] == EmptySquare)) {
9950         board[fromY][fromX] = EmptySquare;
9951         board[toY][toX] = WhitePawn;
9952         if(oldEP & EP_BEROLIN_A) {
9953                 captured = board[fromY][fromX-1];
9954                 board[fromY][fromX-1] = EmptySquare;
9955         }else{  captured = board[fromY][fromX+1];
9956                 board[fromY][fromX+1] = EmptySquare;
9957         }
9958     } else if (board[fromY][fromX] == king
9959         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9960                && toY == fromY && toX > fromX+1) {
9961         board[fromY][fromX] = EmptySquare;
9962         board[toY][toX] = king;
9963         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9964         board[fromY][BOARD_RGHT-1] = EmptySquare;
9965     } else if (board[fromY][fromX] == king
9966         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9967                && toY == fromY && toX < fromX-1) {
9968         board[fromY][fromX] = EmptySquare;
9969         board[toY][toX] = king;
9970         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9971         board[fromY][BOARD_LEFT] = EmptySquare;
9972     } else if (fromY == 7 && fromX == 3
9973                && board[fromY][fromX] == BlackKing
9974                && toY == 7 && toX == 5) {
9975         board[fromY][fromX] = EmptySquare;
9976         board[toY][toX] = BlackKing;
9977         board[fromY][7] = EmptySquare;
9978         board[toY][4] = BlackRook;
9979     } else if (fromY == 7 && fromX == 3
9980                && board[fromY][fromX] == BlackKing
9981                && toY == 7 && toX == 1) {
9982         board[fromY][fromX] = EmptySquare;
9983         board[toY][toX] = BlackKing;
9984         board[fromY][0] = EmptySquare;
9985         board[toY][2] = BlackRook;
9986     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9987                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9988                && toY < promoRank && promoChar
9989                ) {
9990         /* black pawn promotion */
9991         board[toY][toX] = CharToPiece(ToLower(promoChar));
9992         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9993             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9994         board[fromY][fromX] = EmptySquare;
9995     } else if ((fromY < BOARD_HEIGHT>>1)
9996                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9997                && (toX != fromX)
9998                && gameInfo.variant != VariantXiangqi
9999                && gameInfo.variant != VariantBerolina
10000                && (board[fromY][fromX] == BlackPawn)
10001                && (board[toY][toX] == EmptySquare)) {
10002         board[fromY][fromX] = EmptySquare;
10003         board[toY][toX] = BlackPawn;
10004         captured = board[toY + 1][toX];
10005         board[toY + 1][toX] = EmptySquare;
10006     } else if ((fromY == 3)
10007                && (toX == fromX)
10008                && gameInfo.variant == VariantBerolina
10009                && (board[fromY][fromX] == BlackPawn)
10010                && (board[toY][toX] == EmptySquare)) {
10011         board[fromY][fromX] = EmptySquare;
10012         board[toY][toX] = BlackPawn;
10013         if(oldEP & EP_BEROLIN_A) {
10014                 captured = board[fromY][fromX-1];
10015                 board[fromY][fromX-1] = EmptySquare;
10016         }else{  captured = board[fromY][fromX+1];
10017                 board[fromY][fromX+1] = EmptySquare;
10018         }
10019     } else {
10020         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10021         board[fromY][fromX] = EmptySquare;
10022         board[toY][toX] = piece;
10023     }
10024   }
10025
10026     if (gameInfo.holdingsWidth != 0) {
10027
10028       /* !!A lot more code needs to be written to support holdings  */
10029       /* [HGM] OK, so I have written it. Holdings are stored in the */
10030       /* penultimate board files, so they are automaticlly stored   */
10031       /* in the game history.                                       */
10032       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10033                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10034         /* Delete from holdings, by decreasing count */
10035         /* and erasing image if necessary            */
10036         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10037         if(p < (int) BlackPawn) { /* white drop */
10038              p -= (int)WhitePawn;
10039                  p = PieceToNumber((ChessSquare)p);
10040              if(p >= gameInfo.holdingsSize) p = 0;
10041              if(--board[p][BOARD_WIDTH-2] <= 0)
10042                   board[p][BOARD_WIDTH-1] = EmptySquare;
10043              if((int)board[p][BOARD_WIDTH-2] < 0)
10044                         board[p][BOARD_WIDTH-2] = 0;
10045         } else {                  /* black drop */
10046              p -= (int)BlackPawn;
10047                  p = PieceToNumber((ChessSquare)p);
10048              if(p >= gameInfo.holdingsSize) p = 0;
10049              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10050                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10051              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10052                         board[BOARD_HEIGHT-1-p][1] = 0;
10053         }
10054       }
10055       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10056           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10057         /* [HGM] holdings: Add to holdings, if holdings exist */
10058         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10059                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10060                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10061         }
10062         p = (int) captured;
10063         if (p >= (int) BlackPawn) {
10064           p -= (int)BlackPawn;
10065           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10066                   /* in Shogi restore piece to its original  first */
10067                   captured = (ChessSquare) (DEMOTED captured);
10068                   p = DEMOTED p;
10069           }
10070           p = PieceToNumber((ChessSquare)p);
10071           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10072           board[p][BOARD_WIDTH-2]++;
10073           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10074         } else {
10075           p -= (int)WhitePawn;
10076           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10077                   captured = (ChessSquare) (DEMOTED captured);
10078                   p = DEMOTED p;
10079           }
10080           p = PieceToNumber((ChessSquare)p);
10081           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10082           board[BOARD_HEIGHT-1-p][1]++;
10083           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10084         }
10085       }
10086     } else if (gameInfo.variant == VariantAtomic) {
10087       if (captured != EmptySquare) {
10088         int y, x;
10089         for (y = toY-1; y <= toY+1; y++) {
10090           for (x = toX-1; x <= toX+1; x++) {
10091             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10092                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10093               board[y][x] = EmptySquare;
10094             }
10095           }
10096         }
10097         board[toY][toX] = EmptySquare;
10098       }
10099     }
10100
10101     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10102         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10103     } else
10104     if(promoChar == '+') {
10105         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10106         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10107         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10108           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10109     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10110         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10111         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10112            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10113         board[toY][toX] = newPiece;
10114     }
10115     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10116                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10117         // [HGM] superchess: take promotion piece out of holdings
10118         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10119         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10120             if(!--board[k][BOARD_WIDTH-2])
10121                 board[k][BOARD_WIDTH-1] = EmptySquare;
10122         } else {
10123             if(!--board[BOARD_HEIGHT-1-k][1])
10124                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10125         }
10126     }
10127 }
10128
10129 /* Updates forwardMostMove */
10130 void
10131 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10132 {
10133     int x = toX, y = toY;
10134     char *s = parseList[forwardMostMove];
10135     ChessSquare p = boards[forwardMostMove][toY][toX];
10136 //    forwardMostMove++; // [HGM] bare: moved downstream
10137
10138     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10139     (void) CoordsToAlgebraic(boards[forwardMostMove],
10140                              PosFlags(forwardMostMove),
10141                              fromY, fromX, y, x, promoChar,
10142                              s);
10143     if(killX >= 0 && killY >= 0)
10144         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10145
10146     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10147         int timeLeft; static int lastLoadFlag=0; int king, piece;
10148         piece = boards[forwardMostMove][fromY][fromX];
10149         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10150         if(gameInfo.variant == VariantKnightmate)
10151             king += (int) WhiteUnicorn - (int) WhiteKing;
10152         if(forwardMostMove == 0) {
10153             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10154                 fprintf(serverMoves, "%s;", UserName());
10155             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10156                 fprintf(serverMoves, "%s;", second.tidy);
10157             fprintf(serverMoves, "%s;", first.tidy);
10158             if(gameMode == MachinePlaysWhite)
10159                 fprintf(serverMoves, "%s;", UserName());
10160             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10161                 fprintf(serverMoves, "%s;", second.tidy);
10162         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10163         lastLoadFlag = loadFlag;
10164         // print base move
10165         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10166         // print castling suffix
10167         if( toY == fromY && piece == king ) {
10168             if(toX-fromX > 1)
10169                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10170             if(fromX-toX >1)
10171                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10172         }
10173         // e.p. suffix
10174         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10175              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10176              boards[forwardMostMove][toY][toX] == EmptySquare
10177              && fromX != toX && fromY != toY)
10178                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10179         // promotion suffix
10180         if(promoChar != NULLCHAR) {
10181             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10182                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10183                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10184             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10185         }
10186         if(!loadFlag) {
10187                 char buf[MOVE_LEN*2], *p; int len;
10188             fprintf(serverMoves, "/%d/%d",
10189                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10190             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10191             else                      timeLeft = blackTimeRemaining/1000;
10192             fprintf(serverMoves, "/%d", timeLeft);
10193                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10194                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10195                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10196                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10197             fprintf(serverMoves, "/%s", buf);
10198         }
10199         fflush(serverMoves);
10200     }
10201
10202     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10203         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10204       return;
10205     }
10206     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10207     if (commentList[forwardMostMove+1] != NULL) {
10208         free(commentList[forwardMostMove+1]);
10209         commentList[forwardMostMove+1] = NULL;
10210     }
10211     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10212     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10213     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10214     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10215     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10216     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10217     adjustedClock = FALSE;
10218     gameInfo.result = GameUnfinished;
10219     if (gameInfo.resultDetails != NULL) {
10220         free(gameInfo.resultDetails);
10221         gameInfo.resultDetails = NULL;
10222     }
10223     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10224                               moveList[forwardMostMove - 1]);
10225     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10226       case MT_NONE:
10227       case MT_STALEMATE:
10228       default:
10229         break;
10230       case MT_CHECK:
10231         if(!IS_SHOGI(gameInfo.variant))
10232             strcat(parseList[forwardMostMove - 1], "+");
10233         break;
10234       case MT_CHECKMATE:
10235       case MT_STAINMATE:
10236         strcat(parseList[forwardMostMove - 1], "#");
10237         break;
10238     }
10239 }
10240
10241 /* Updates currentMove if not pausing */
10242 void
10243 ShowMove (int fromX, int fromY, int toX, int toY)
10244 {
10245     int instant = (gameMode == PlayFromGameFile) ?
10246         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10247     if(appData.noGUI) return;
10248     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10249         if (!instant) {
10250             if (forwardMostMove == currentMove + 1) {
10251                 AnimateMove(boards[forwardMostMove - 1],
10252                             fromX, fromY, toX, toY);
10253             }
10254         }
10255         currentMove = forwardMostMove;
10256     }
10257
10258     killX = killY = -1; // [HGM] lion: used up
10259
10260     if (instant) return;
10261
10262     DisplayMove(currentMove - 1);
10263     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10264             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10265                 SetHighlights(fromX, fromY, toX, toY);
10266             }
10267     }
10268     DrawPosition(FALSE, boards[currentMove]);
10269     DisplayBothClocks();
10270     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10271 }
10272
10273 void
10274 SendEgtPath (ChessProgramState *cps)
10275 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10276         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10277
10278         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10279
10280         while(*p) {
10281             char c, *q = name+1, *r, *s;
10282
10283             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10284             while(*p && *p != ',') *q++ = *p++;
10285             *q++ = ':'; *q = 0;
10286             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10287                 strcmp(name, ",nalimov:") == 0 ) {
10288                 // take nalimov path from the menu-changeable option first, if it is defined
10289               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10290                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10291             } else
10292             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10293                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10294                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10295                 s = r = StrStr(s, ":") + 1; // beginning of path info
10296                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10297                 c = *r; *r = 0;             // temporarily null-terminate path info
10298                     *--q = 0;               // strip of trailig ':' from name
10299                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10300                 *r = c;
10301                 SendToProgram(buf,cps);     // send egtbpath command for this format
10302             }
10303             if(*p == ',') p++; // read away comma to position for next format name
10304         }
10305 }
10306
10307 static int
10308 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10309 {
10310       int width = 8, height = 8, holdings = 0;             // most common sizes
10311       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10312       // correct the deviations default for each variant
10313       if( v == VariantXiangqi ) width = 9,  height = 10;
10314       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10315       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10316       if( v == VariantCapablanca || v == VariantCapaRandom ||
10317           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10318                                 width = 10;
10319       if( v == VariantCourier ) width = 12;
10320       if( v == VariantSuper )                            holdings = 8;
10321       if( v == VariantGreat )   width = 10,              holdings = 8;
10322       if( v == VariantSChess )                           holdings = 7;
10323       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10324       if( v == VariantChuChess) width = 10, height = 10;
10325       if( v == VariantChu )     width = 12, height = 12;
10326       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10327              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10328              holdingsSize >= 0 && holdingsSize != holdings;
10329 }
10330
10331 char variantError[MSG_SIZ];
10332
10333 char *
10334 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10335 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10336       char *p, *variant = VariantName(v);
10337       static char b[MSG_SIZ];
10338       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10339            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10340                                                holdingsSize, variant); // cook up sized variant name
10341            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10342            if(StrStr(list, b) == NULL) {
10343                // specific sized variant not known, check if general sizing allowed
10344                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10345                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10346                             boardWidth, boardHeight, holdingsSize, engine);
10347                    return NULL;
10348                }
10349                /* [HGM] here we really should compare with the maximum supported board size */
10350            }
10351       } else snprintf(b, MSG_SIZ,"%s", variant);
10352       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10353       p = StrStr(list, b);
10354       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10355       if(p == NULL) {
10356           // occurs not at all in list, or only as sub-string
10357           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10358           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10359               int l = strlen(variantError);
10360               char *q;
10361               while(p != list && p[-1] != ',') p--;
10362               q = strchr(p, ',');
10363               if(q) *q = NULLCHAR;
10364               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10365               if(q) *q= ',';
10366           }
10367           return NULL;
10368       }
10369       return b;
10370 }
10371
10372 void
10373 InitChessProgram (ChessProgramState *cps, int setup)
10374 /* setup needed to setup FRC opening position */
10375 {
10376     char buf[MSG_SIZ], *b;
10377     if (appData.noChessProgram) return;
10378     hintRequested = FALSE;
10379     bookRequested = FALSE;
10380
10381     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10382     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10383     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10384     if(cps->memSize) { /* [HGM] memory */
10385       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10386         SendToProgram(buf, cps);
10387     }
10388     SendEgtPath(cps); /* [HGM] EGT */
10389     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10390       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10391         SendToProgram(buf, cps);
10392     }
10393
10394     SendToProgram(cps->initString, cps);
10395     if (gameInfo.variant != VariantNormal &&
10396         gameInfo.variant != VariantLoadable
10397         /* [HGM] also send variant if board size non-standard */
10398         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10399
10400       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10401                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10402       if (b == NULL) {
10403         DisplayFatalError(variantError, 0, 1);
10404         return;
10405       }
10406
10407       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10408       SendToProgram(buf, cps);
10409     }
10410     currentlyInitializedVariant = gameInfo.variant;
10411
10412     /* [HGM] send opening position in FRC to first engine */
10413     if(setup) {
10414           SendToProgram("force\n", cps);
10415           SendBoard(cps, 0);
10416           /* engine is now in force mode! Set flag to wake it up after first move. */
10417           setboardSpoiledMachineBlack = 1;
10418     }
10419
10420     if (cps->sendICS) {
10421       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10422       SendToProgram(buf, cps);
10423     }
10424     cps->maybeThinking = FALSE;
10425     cps->offeredDraw = 0;
10426     if (!appData.icsActive) {
10427         SendTimeControl(cps, movesPerSession, timeControl,
10428                         timeIncrement, appData.searchDepth,
10429                         searchTime);
10430     }
10431     if (appData.showThinking
10432         // [HGM] thinking: four options require thinking output to be sent
10433         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10434                                 ) {
10435         SendToProgram("post\n", cps);
10436     }
10437     SendToProgram("hard\n", cps);
10438     if (!appData.ponderNextMove) {
10439         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10440            it without being sure what state we are in first.  "hard"
10441            is not a toggle, so that one is OK.
10442          */
10443         SendToProgram("easy\n", cps);
10444     }
10445     if (cps->usePing) {
10446       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10447       SendToProgram(buf, cps);
10448     }
10449     cps->initDone = TRUE;
10450     ClearEngineOutputPane(cps == &second);
10451 }
10452
10453
10454 void
10455 ResendOptions (ChessProgramState *cps)
10456 { // send the stored value of the options
10457   int i;
10458   char buf[MSG_SIZ];
10459   Option *opt = cps->option;
10460   for(i=0; i<cps->nrOptions; i++, opt++) {
10461       switch(opt->type) {
10462         case Spin:
10463         case Slider:
10464         case CheckBox:
10465             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10466           break;
10467         case ComboBox:
10468           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10469           break;
10470         default:
10471             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10472           break;
10473         case Button:
10474         case SaveButton:
10475           continue;
10476       }
10477       SendToProgram(buf, cps);
10478   }
10479 }
10480
10481 void
10482 StartChessProgram (ChessProgramState *cps)
10483 {
10484     char buf[MSG_SIZ];
10485     int err;
10486
10487     if (appData.noChessProgram) return;
10488     cps->initDone = FALSE;
10489
10490     if (strcmp(cps->host, "localhost") == 0) {
10491         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10492     } else if (*appData.remoteShell == NULLCHAR) {
10493         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10494     } else {
10495         if (*appData.remoteUser == NULLCHAR) {
10496           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10497                     cps->program);
10498         } else {
10499           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10500                     cps->host, appData.remoteUser, cps->program);
10501         }
10502         err = StartChildProcess(buf, "", &cps->pr);
10503     }
10504
10505     if (err != 0) {
10506       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10507         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10508         if(cps != &first) return;
10509         appData.noChessProgram = TRUE;
10510         ThawUI();
10511         SetNCPMode();
10512 //      DisplayFatalError(buf, err, 1);
10513 //      cps->pr = NoProc;
10514 //      cps->isr = NULL;
10515         return;
10516     }
10517
10518     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10519     if (cps->protocolVersion > 1) {
10520       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10521       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10522         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10523         cps->comboCnt = 0;  //                and values of combo boxes
10524       }
10525       SendToProgram(buf, cps);
10526       if(cps->reload) ResendOptions(cps);
10527     } else {
10528       SendToProgram("xboard\n", cps);
10529     }
10530 }
10531
10532 void
10533 TwoMachinesEventIfReady P((void))
10534 {
10535   static int curMess = 0;
10536   if (first.lastPing != first.lastPong) {
10537     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10538     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10539     return;
10540   }
10541   if (second.lastPing != second.lastPong) {
10542     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10543     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10544     return;
10545   }
10546   DisplayMessage("", ""); curMess = 0;
10547   TwoMachinesEvent();
10548 }
10549
10550 char *
10551 MakeName (char *template)
10552 {
10553     time_t clock;
10554     struct tm *tm;
10555     static char buf[MSG_SIZ];
10556     char *p = buf;
10557     int i;
10558
10559     clock = time((time_t *)NULL);
10560     tm = localtime(&clock);
10561
10562     while(*p++ = *template++) if(p[-1] == '%') {
10563         switch(*template++) {
10564           case 0:   *p = 0; return buf;
10565           case 'Y': i = tm->tm_year+1900; break;
10566           case 'y': i = tm->tm_year-100; break;
10567           case 'M': i = tm->tm_mon+1; break;
10568           case 'd': i = tm->tm_mday; break;
10569           case 'h': i = tm->tm_hour; break;
10570           case 'm': i = tm->tm_min; break;
10571           case 's': i = tm->tm_sec; break;
10572           default:  i = 0;
10573         }
10574         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10575     }
10576     return buf;
10577 }
10578
10579 int
10580 CountPlayers (char *p)
10581 {
10582     int n = 0;
10583     while(p = strchr(p, '\n')) p++, n++; // count participants
10584     return n;
10585 }
10586
10587 FILE *
10588 WriteTourneyFile (char *results, FILE *f)
10589 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10590     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10591     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10592         // create a file with tournament description
10593         fprintf(f, "-participants {%s}\n", appData.participants);
10594         fprintf(f, "-seedBase %d\n", appData.seedBase);
10595         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10596         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10597         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10598         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10599         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10600         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10601         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10602         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10603         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10604         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10605         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10606         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10607         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10608         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10609         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10610         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10611         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10612         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10613         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10614         fprintf(f, "-smpCores %d\n", appData.smpCores);
10615         if(searchTime > 0)
10616                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10617         else {
10618                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10619                 fprintf(f, "-tc %s\n", appData.timeControl);
10620                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10621         }
10622         fprintf(f, "-results \"%s\"\n", results);
10623     }
10624     return f;
10625 }
10626
10627 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10628
10629 void
10630 Substitute (char *participants, int expunge)
10631 {
10632     int i, changed, changes=0, nPlayers=0;
10633     char *p, *q, *r, buf[MSG_SIZ];
10634     if(participants == NULL) return;
10635     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10636     r = p = participants; q = appData.participants;
10637     while(*p && *p == *q) {
10638         if(*p == '\n') r = p+1, nPlayers++;
10639         p++; q++;
10640     }
10641     if(*p) { // difference
10642         while(*p && *p++ != '\n');
10643         while(*q && *q++ != '\n');
10644       changed = nPlayers;
10645         changes = 1 + (strcmp(p, q) != 0);
10646     }
10647     if(changes == 1) { // a single engine mnemonic was changed
10648         q = r; while(*q) nPlayers += (*q++ == '\n');
10649         p = buf; while(*r && (*p = *r++) != '\n') p++;
10650         *p = NULLCHAR;
10651         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10652         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10653         if(mnemonic[i]) { // The substitute is valid
10654             FILE *f;
10655             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10656                 flock(fileno(f), LOCK_EX);
10657                 ParseArgsFromFile(f);
10658                 fseek(f, 0, SEEK_SET);
10659                 FREE(appData.participants); appData.participants = participants;
10660                 if(expunge) { // erase results of replaced engine
10661                     int len = strlen(appData.results), w, b, dummy;
10662                     for(i=0; i<len; i++) {
10663                         Pairing(i, nPlayers, &w, &b, &dummy);
10664                         if((w == changed || b == changed) && appData.results[i] == '*') {
10665                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10666                             fclose(f);
10667                             return;
10668                         }
10669                     }
10670                     for(i=0; i<len; i++) {
10671                         Pairing(i, nPlayers, &w, &b, &dummy);
10672                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10673                     }
10674                 }
10675                 WriteTourneyFile(appData.results, f);
10676                 fclose(f); // release lock
10677                 return;
10678             }
10679         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10680     }
10681     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10682     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10683     free(participants);
10684     return;
10685 }
10686
10687 int
10688 CheckPlayers (char *participants)
10689 {
10690         int i;
10691         char buf[MSG_SIZ], *p;
10692         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10693         while(p = strchr(participants, '\n')) {
10694             *p = NULLCHAR;
10695             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10696             if(!mnemonic[i]) {
10697                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10698                 *p = '\n';
10699                 DisplayError(buf, 0);
10700                 return 1;
10701             }
10702             *p = '\n';
10703             participants = p + 1;
10704         }
10705         return 0;
10706 }
10707
10708 int
10709 CreateTourney (char *name)
10710 {
10711         FILE *f;
10712         if(matchMode && strcmp(name, appData.tourneyFile)) {
10713              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10714         }
10715         if(name[0] == NULLCHAR) {
10716             if(appData.participants[0])
10717                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10718             return 0;
10719         }
10720         f = fopen(name, "r");
10721         if(f) { // file exists
10722             ASSIGN(appData.tourneyFile, name);
10723             ParseArgsFromFile(f); // parse it
10724         } else {
10725             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10726             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10727                 DisplayError(_("Not enough participants"), 0);
10728                 return 0;
10729             }
10730             if(CheckPlayers(appData.participants)) return 0;
10731             ASSIGN(appData.tourneyFile, name);
10732             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10733             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10734         }
10735         fclose(f);
10736         appData.noChessProgram = FALSE;
10737         appData.clockMode = TRUE;
10738         SetGNUMode();
10739         return 1;
10740 }
10741
10742 int
10743 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10744 {
10745     char buf[MSG_SIZ], *p, *q;
10746     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10747     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10748     skip = !all && group[0]; // if group requested, we start in skip mode
10749     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10750         p = names; q = buf; header = 0;
10751         while(*p && *p != '\n') *q++ = *p++;
10752         *q = 0;
10753         if(*p == '\n') p++;
10754         if(buf[0] == '#') {
10755             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10756             depth++; // we must be entering a new group
10757             if(all) continue; // suppress printing group headers when complete list requested
10758             header = 1;
10759             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10760         }
10761         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10762         if(engineList[i]) free(engineList[i]);
10763         engineList[i] = strdup(buf);
10764         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10765         if(engineMnemonic[i]) free(engineMnemonic[i]);
10766         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10767             strcat(buf, " (");
10768             sscanf(q + 8, "%s", buf + strlen(buf));
10769             strcat(buf, ")");
10770         }
10771         engineMnemonic[i] = strdup(buf);
10772         i++;
10773     }
10774     engineList[i] = engineMnemonic[i] = NULL;
10775     return i;
10776 }
10777
10778 // following implemented as macro to avoid type limitations
10779 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10780
10781 void
10782 SwapEngines (int n)
10783 {   // swap settings for first engine and other engine (so far only some selected options)
10784     int h;
10785     char *p;
10786     if(n == 0) return;
10787     SWAP(directory, p)
10788     SWAP(chessProgram, p)
10789     SWAP(isUCI, h)
10790     SWAP(hasOwnBookUCI, h)
10791     SWAP(protocolVersion, h)
10792     SWAP(reuse, h)
10793     SWAP(scoreIsAbsolute, h)
10794     SWAP(timeOdds, h)
10795     SWAP(logo, p)
10796     SWAP(pgnName, p)
10797     SWAP(pvSAN, h)
10798     SWAP(engOptions, p)
10799     SWAP(engInitString, p)
10800     SWAP(computerString, p)
10801     SWAP(features, p)
10802     SWAP(fenOverride, p)
10803     SWAP(NPS, h)
10804     SWAP(accumulateTC, h)
10805     SWAP(drawDepth, h)
10806     SWAP(host, p)
10807 }
10808
10809 int
10810 GetEngineLine (char *s, int n)
10811 {
10812     int i;
10813     char buf[MSG_SIZ];
10814     extern char *icsNames;
10815     if(!s || !*s) return 0;
10816     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10817     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10818     if(!mnemonic[i]) return 0;
10819     if(n == 11) return 1; // just testing if there was a match
10820     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10821     if(n == 1) SwapEngines(n);
10822     ParseArgsFromString(buf);
10823     if(n == 1) SwapEngines(n);
10824     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10825         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10826         ParseArgsFromString(buf);
10827     }
10828     return 1;
10829 }
10830
10831 int
10832 SetPlayer (int player, char *p)
10833 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10834     int i;
10835     char buf[MSG_SIZ], *engineName;
10836     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10837     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10838     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10839     if(mnemonic[i]) {
10840         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10841         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10842         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10843         ParseArgsFromString(buf);
10844     } else { // no engine with this nickname is installed!
10845         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10846         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10847         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10848         ModeHighlight();
10849         DisplayError(buf, 0);
10850         return 0;
10851     }
10852     free(engineName);
10853     return i;
10854 }
10855
10856 char *recentEngines;
10857
10858 void
10859 RecentEngineEvent (int nr)
10860 {
10861     int n;
10862 //    SwapEngines(1); // bump first to second
10863 //    ReplaceEngine(&second, 1); // and load it there
10864     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10865     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10866     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10867         ReplaceEngine(&first, 0);
10868         FloatToFront(&appData.recentEngineList, command[n]);
10869     }
10870 }
10871
10872 int
10873 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10874 {   // determine players from game number
10875     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10876
10877     if(appData.tourneyType == 0) {
10878         roundsPerCycle = (nPlayers - 1) | 1;
10879         pairingsPerRound = nPlayers / 2;
10880     } else if(appData.tourneyType > 0) {
10881         roundsPerCycle = nPlayers - appData.tourneyType;
10882         pairingsPerRound = appData.tourneyType;
10883     }
10884     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10885     gamesPerCycle = gamesPerRound * roundsPerCycle;
10886     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10887     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10888     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10889     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10890     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10891     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10892
10893     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10894     if(appData.roundSync) *syncInterval = gamesPerRound;
10895
10896     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10897
10898     if(appData.tourneyType == 0) {
10899         if(curPairing == (nPlayers-1)/2 ) {
10900             *whitePlayer = curRound;
10901             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10902         } else {
10903             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10904             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10905             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10906             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10907         }
10908     } else if(appData.tourneyType > 1) {
10909         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10910         *whitePlayer = curRound + appData.tourneyType;
10911     } else if(appData.tourneyType > 0) {
10912         *whitePlayer = curPairing;
10913         *blackPlayer = curRound + appData.tourneyType;
10914     }
10915
10916     // take care of white/black alternation per round.
10917     // For cycles and games this is already taken care of by default, derived from matchGame!
10918     return curRound & 1;
10919 }
10920
10921 int
10922 NextTourneyGame (int nr, int *swapColors)
10923 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10924     char *p, *q;
10925     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10926     FILE *tf;
10927     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10928     tf = fopen(appData.tourneyFile, "r");
10929     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10930     ParseArgsFromFile(tf); fclose(tf);
10931     InitTimeControls(); // TC might be altered from tourney file
10932
10933     nPlayers = CountPlayers(appData.participants); // count participants
10934     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10935     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10936
10937     if(syncInterval) {
10938         p = q = appData.results;
10939         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10940         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10941             DisplayMessage(_("Waiting for other game(s)"),"");
10942             waitingForGame = TRUE;
10943             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10944             return 0;
10945         }
10946         waitingForGame = FALSE;
10947     }
10948
10949     if(appData.tourneyType < 0) {
10950         if(nr>=0 && !pairingReceived) {
10951             char buf[1<<16];
10952             if(pairing.pr == NoProc) {
10953                 if(!appData.pairingEngine[0]) {
10954                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10955                     return 0;
10956                 }
10957                 StartChessProgram(&pairing); // starts the pairing engine
10958             }
10959             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10960             SendToProgram(buf, &pairing);
10961             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10962             SendToProgram(buf, &pairing);
10963             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10964         }
10965         pairingReceived = 0;                              // ... so we continue here
10966         *swapColors = 0;
10967         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10968         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10969         matchGame = 1; roundNr = nr / syncInterval + 1;
10970     }
10971
10972     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10973
10974     // redefine engines, engine dir, etc.
10975     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10976     if(first.pr == NoProc) {
10977       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10978       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10979     }
10980     if(second.pr == NoProc) {
10981       SwapEngines(1);
10982       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10983       SwapEngines(1);         // and make that valid for second engine by swapping
10984       InitEngine(&second, 1);
10985     }
10986     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10987     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10988     return OK;
10989 }
10990
10991 void
10992 NextMatchGame ()
10993 {   // performs game initialization that does not invoke engines, and then tries to start the game
10994     int res, firstWhite, swapColors = 0;
10995     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10996     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
10997         char buf[MSG_SIZ];
10998         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10999         if(strcmp(buf, currentDebugFile)) { // name has changed
11000             FILE *f = fopen(buf, "w");
11001             if(f) { // if opening the new file failed, just keep using the old one
11002                 ASSIGN(currentDebugFile, buf);
11003                 fclose(debugFP);
11004                 debugFP = f;
11005             }
11006             if(appData.serverFileName) {
11007                 if(serverFP) fclose(serverFP);
11008                 serverFP = fopen(appData.serverFileName, "w");
11009                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11010                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11011             }
11012         }
11013     }
11014     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11015     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11016     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11017     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11018     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11019     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11020     Reset(FALSE, first.pr != NoProc);
11021     res = LoadGameOrPosition(matchGame); // setup game
11022     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11023     if(!res) return; // abort when bad game/pos file
11024     TwoMachinesEvent();
11025 }
11026
11027 void
11028 UserAdjudicationEvent (int result)
11029 {
11030     ChessMove gameResult = GameIsDrawn;
11031
11032     if( result > 0 ) {
11033         gameResult = WhiteWins;
11034     }
11035     else if( result < 0 ) {
11036         gameResult = BlackWins;
11037     }
11038
11039     if( gameMode == TwoMachinesPlay ) {
11040         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11041     }
11042 }
11043
11044
11045 // [HGM] save: calculate checksum of game to make games easily identifiable
11046 int
11047 StringCheckSum (char *s)
11048 {
11049         int i = 0;
11050         if(s==NULL) return 0;
11051         while(*s) i = i*259 + *s++;
11052         return i;
11053 }
11054
11055 int
11056 GameCheckSum ()
11057 {
11058         int i, sum=0;
11059         for(i=backwardMostMove; i<forwardMostMove; i++) {
11060                 sum += pvInfoList[i].depth;
11061                 sum += StringCheckSum(parseList[i]);
11062                 sum += StringCheckSum(commentList[i]);
11063                 sum *= 261;
11064         }
11065         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11066         return sum + StringCheckSum(commentList[i]);
11067 } // end of save patch
11068
11069 void
11070 GameEnds (ChessMove result, char *resultDetails, int whosays)
11071 {
11072     GameMode nextGameMode;
11073     int isIcsGame;
11074     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11075
11076     if(endingGame) return; /* [HGM] crash: forbid recursion */
11077     endingGame = 1;
11078     if(twoBoards) { // [HGM] dual: switch back to one board
11079         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11080         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11081     }
11082     if (appData.debugMode) {
11083       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11084               result, resultDetails ? resultDetails : "(null)", whosays);
11085     }
11086
11087     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11088
11089     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11090
11091     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11092         /* If we are playing on ICS, the server decides when the
11093            game is over, but the engine can offer to draw, claim
11094            a draw, or resign.
11095          */
11096 #if ZIPPY
11097         if (appData.zippyPlay && first.initDone) {
11098             if (result == GameIsDrawn) {
11099                 /* In case draw still needs to be claimed */
11100                 SendToICS(ics_prefix);
11101                 SendToICS("draw\n");
11102             } else if (StrCaseStr(resultDetails, "resign")) {
11103                 SendToICS(ics_prefix);
11104                 SendToICS("resign\n");
11105             }
11106         }
11107 #endif
11108         endingGame = 0; /* [HGM] crash */
11109         return;
11110     }
11111
11112     /* If we're loading the game from a file, stop */
11113     if (whosays == GE_FILE) {
11114       (void) StopLoadGameTimer();
11115       gameFileFP = NULL;
11116     }
11117
11118     /* Cancel draw offers */
11119     first.offeredDraw = second.offeredDraw = 0;
11120
11121     /* If this is an ICS game, only ICS can really say it's done;
11122        if not, anyone can. */
11123     isIcsGame = (gameMode == IcsPlayingWhite ||
11124                  gameMode == IcsPlayingBlack ||
11125                  gameMode == IcsObserving    ||
11126                  gameMode == IcsExamining);
11127
11128     if (!isIcsGame || whosays == GE_ICS) {
11129         /* OK -- not an ICS game, or ICS said it was done */
11130         StopClocks();
11131         if (!isIcsGame && !appData.noChessProgram)
11132           SetUserThinkingEnables();
11133
11134         /* [HGM] if a machine claims the game end we verify this claim */
11135         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11136             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11137                 char claimer;
11138                 ChessMove trueResult = (ChessMove) -1;
11139
11140                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11141                                             first.twoMachinesColor[0] :
11142                                             second.twoMachinesColor[0] ;
11143
11144                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11145                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11146                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11147                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11148                 } else
11149                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11150                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11151                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11152                 } else
11153                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11154                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11155                 }
11156
11157                 // now verify win claims, but not in drop games, as we don't understand those yet
11158                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11159                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11160                     (result == WhiteWins && claimer == 'w' ||
11161                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11162                       if (appData.debugMode) {
11163                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11164                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11165                       }
11166                       if(result != trueResult) {
11167                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11168                               result = claimer == 'w' ? BlackWins : WhiteWins;
11169                               resultDetails = buf;
11170                       }
11171                 } else
11172                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11173                     && (forwardMostMove <= backwardMostMove ||
11174                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11175                         (claimer=='b')==(forwardMostMove&1))
11176                                                                                   ) {
11177                       /* [HGM] verify: draws that were not flagged are false claims */
11178                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11179                       result = claimer == 'w' ? BlackWins : WhiteWins;
11180                       resultDetails = buf;
11181                 }
11182                 /* (Claiming a loss is accepted no questions asked!) */
11183             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11184                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11185                 result = GameUnfinished;
11186                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11187             }
11188             /* [HGM] bare: don't allow bare King to win */
11189             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11190                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11191                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11192                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11193                && result != GameIsDrawn)
11194             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11195                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11196                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11197                         if(p >= 0 && p <= (int)WhiteKing) k++;
11198                 }
11199                 if (appData.debugMode) {
11200                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11201                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11202                 }
11203                 if(k <= 1) {
11204                         result = GameIsDrawn;
11205                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11206                         resultDetails = buf;
11207                 }
11208             }
11209         }
11210
11211
11212         if(serverMoves != NULL && !loadFlag) { char c = '=';
11213             if(result==WhiteWins) c = '+';
11214             if(result==BlackWins) c = '-';
11215             if(resultDetails != NULL)
11216                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11217         }
11218         if (resultDetails != NULL) {
11219             gameInfo.result = result;
11220             gameInfo.resultDetails = StrSave(resultDetails);
11221
11222             /* display last move only if game was not loaded from file */
11223             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11224                 DisplayMove(currentMove - 1);
11225
11226             if (forwardMostMove != 0) {
11227                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11228                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11229                                                                 ) {
11230                     if (*appData.saveGameFile != NULLCHAR) {
11231                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11232                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11233                         else
11234                         SaveGameToFile(appData.saveGameFile, TRUE);
11235                     } else if (appData.autoSaveGames) {
11236                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11237                     }
11238                     if (*appData.savePositionFile != NULLCHAR) {
11239                         SavePositionToFile(appData.savePositionFile);
11240                     }
11241                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11242                 }
11243             }
11244
11245             /* Tell program how game ended in case it is learning */
11246             /* [HGM] Moved this to after saving the PGN, just in case */
11247             /* engine died and we got here through time loss. In that */
11248             /* case we will get a fatal error writing the pipe, which */
11249             /* would otherwise lose us the PGN.                       */
11250             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11251             /* output during GameEnds should never be fatal anymore   */
11252             if (gameMode == MachinePlaysWhite ||
11253                 gameMode == MachinePlaysBlack ||
11254                 gameMode == TwoMachinesPlay ||
11255                 gameMode == IcsPlayingWhite ||
11256                 gameMode == IcsPlayingBlack ||
11257                 gameMode == BeginningOfGame) {
11258                 char buf[MSG_SIZ];
11259                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11260                         resultDetails);
11261                 if (first.pr != NoProc) {
11262                     SendToProgram(buf, &first);
11263                 }
11264                 if (second.pr != NoProc &&
11265                     gameMode == TwoMachinesPlay) {
11266                     SendToProgram(buf, &second);
11267                 }
11268             }
11269         }
11270
11271         if (appData.icsActive) {
11272             if (appData.quietPlay &&
11273                 (gameMode == IcsPlayingWhite ||
11274                  gameMode == IcsPlayingBlack)) {
11275                 SendToICS(ics_prefix);
11276                 SendToICS("set shout 1\n");
11277             }
11278             nextGameMode = IcsIdle;
11279             ics_user_moved = FALSE;
11280             /* clean up premove.  It's ugly when the game has ended and the
11281              * premove highlights are still on the board.
11282              */
11283             if (gotPremove) {
11284               gotPremove = FALSE;
11285               ClearPremoveHighlights();
11286               DrawPosition(FALSE, boards[currentMove]);
11287             }
11288             if (whosays == GE_ICS) {
11289                 switch (result) {
11290                 case WhiteWins:
11291                     if (gameMode == IcsPlayingWhite)
11292                         PlayIcsWinSound();
11293                     else if(gameMode == IcsPlayingBlack)
11294                         PlayIcsLossSound();
11295                     break;
11296                 case BlackWins:
11297                     if (gameMode == IcsPlayingBlack)
11298                         PlayIcsWinSound();
11299                     else if(gameMode == IcsPlayingWhite)
11300                         PlayIcsLossSound();
11301                     break;
11302                 case GameIsDrawn:
11303                     PlayIcsDrawSound();
11304                     break;
11305                 default:
11306                     PlayIcsUnfinishedSound();
11307                 }
11308             }
11309             if(appData.quitNext) { ExitEvent(0); return; }
11310         } else if (gameMode == EditGame ||
11311                    gameMode == PlayFromGameFile ||
11312                    gameMode == AnalyzeMode ||
11313                    gameMode == AnalyzeFile) {
11314             nextGameMode = gameMode;
11315         } else {
11316             nextGameMode = EndOfGame;
11317         }
11318         pausing = FALSE;
11319         ModeHighlight();
11320     } else {
11321         nextGameMode = gameMode;
11322     }
11323
11324     if (appData.noChessProgram) {
11325         gameMode = nextGameMode;
11326         ModeHighlight();
11327         endingGame = 0; /* [HGM] crash */
11328         return;
11329     }
11330
11331     if (first.reuse) {
11332         /* Put first chess program into idle state */
11333         if (first.pr != NoProc &&
11334             (gameMode == MachinePlaysWhite ||
11335              gameMode == MachinePlaysBlack ||
11336              gameMode == TwoMachinesPlay ||
11337              gameMode == IcsPlayingWhite ||
11338              gameMode == IcsPlayingBlack ||
11339              gameMode == BeginningOfGame)) {
11340             SendToProgram("force\n", &first);
11341             if (first.usePing) {
11342               char buf[MSG_SIZ];
11343               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11344               SendToProgram(buf, &first);
11345             }
11346         }
11347     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11348         /* Kill off first chess program */
11349         if (first.isr != NULL)
11350           RemoveInputSource(first.isr);
11351         first.isr = NULL;
11352
11353         if (first.pr != NoProc) {
11354             ExitAnalyzeMode();
11355             DoSleep( appData.delayBeforeQuit );
11356             SendToProgram("quit\n", &first);
11357             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11358             first.reload = TRUE;
11359         }
11360         first.pr = NoProc;
11361     }
11362     if (second.reuse) {
11363         /* Put second chess program into idle state */
11364         if (second.pr != NoProc &&
11365             gameMode == TwoMachinesPlay) {
11366             SendToProgram("force\n", &second);
11367             if (second.usePing) {
11368               char buf[MSG_SIZ];
11369               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11370               SendToProgram(buf, &second);
11371             }
11372         }
11373     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11374         /* Kill off second chess program */
11375         if (second.isr != NULL)
11376           RemoveInputSource(second.isr);
11377         second.isr = NULL;
11378
11379         if (second.pr != NoProc) {
11380             DoSleep( appData.delayBeforeQuit );
11381             SendToProgram("quit\n", &second);
11382             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11383             second.reload = TRUE;
11384         }
11385         second.pr = NoProc;
11386     }
11387
11388     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11389         char resChar = '=';
11390         switch (result) {
11391         case WhiteWins:
11392           resChar = '+';
11393           if (first.twoMachinesColor[0] == 'w') {
11394             first.matchWins++;
11395           } else {
11396             second.matchWins++;
11397           }
11398           break;
11399         case BlackWins:
11400           resChar = '-';
11401           if (first.twoMachinesColor[0] == 'b') {
11402             first.matchWins++;
11403           } else {
11404             second.matchWins++;
11405           }
11406           break;
11407         case GameUnfinished:
11408           resChar = ' ';
11409         default:
11410           break;
11411         }
11412
11413         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11414         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11415             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11416             ReserveGame(nextGame, resChar); // sets nextGame
11417             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11418             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11419         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11420
11421         if (nextGame <= appData.matchGames && !abortMatch) {
11422             gameMode = nextGameMode;
11423             matchGame = nextGame; // this will be overruled in tourney mode!
11424             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11425             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11426             endingGame = 0; /* [HGM] crash */
11427             return;
11428         } else {
11429             gameMode = nextGameMode;
11430             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11431                      first.tidy, second.tidy,
11432                      first.matchWins, second.matchWins,
11433                      appData.matchGames - (first.matchWins + second.matchWins));
11434             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11435             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11436             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11437             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11438                 first.twoMachinesColor = "black\n";
11439                 second.twoMachinesColor = "white\n";
11440             } else {
11441                 first.twoMachinesColor = "white\n";
11442                 second.twoMachinesColor = "black\n";
11443             }
11444         }
11445     }
11446     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11447         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11448       ExitAnalyzeMode();
11449     gameMode = nextGameMode;
11450     ModeHighlight();
11451     endingGame = 0;  /* [HGM] crash */
11452     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11453         if(matchMode == TRUE) { // match through command line: exit with or without popup
11454             if(ranking) {
11455                 ToNrEvent(forwardMostMove);
11456                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11457                 else ExitEvent(0);
11458             } else DisplayFatalError(buf, 0, 0);
11459         } else { // match through menu; just stop, with or without popup
11460             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11461             ModeHighlight();
11462             if(ranking){
11463                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11464             } else DisplayNote(buf);
11465       }
11466       if(ranking) free(ranking);
11467     }
11468 }
11469
11470 /* Assumes program was just initialized (initString sent).
11471    Leaves program in force mode. */
11472 void
11473 FeedMovesToProgram (ChessProgramState *cps, int upto)
11474 {
11475     int i;
11476
11477     if (appData.debugMode)
11478       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11479               startedFromSetupPosition ? "position and " : "",
11480               backwardMostMove, upto, cps->which);
11481     if(currentlyInitializedVariant != gameInfo.variant) {
11482       char buf[MSG_SIZ];
11483         // [HGM] variantswitch: make engine aware of new variant
11484         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11485                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11486                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11487         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11488         SendToProgram(buf, cps);
11489         currentlyInitializedVariant = gameInfo.variant;
11490     }
11491     SendToProgram("force\n", cps);
11492     if (startedFromSetupPosition) {
11493         SendBoard(cps, backwardMostMove);
11494     if (appData.debugMode) {
11495         fprintf(debugFP, "feedMoves\n");
11496     }
11497     }
11498     for (i = backwardMostMove; i < upto; i++) {
11499         SendMoveToProgram(i, cps);
11500     }
11501 }
11502
11503
11504 int
11505 ResurrectChessProgram ()
11506 {
11507      /* The chess program may have exited.
11508         If so, restart it and feed it all the moves made so far. */
11509     static int doInit = 0;
11510
11511     if (appData.noChessProgram) return 1;
11512
11513     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11514         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11515         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11516         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11517     } else {
11518         if (first.pr != NoProc) return 1;
11519         StartChessProgram(&first);
11520     }
11521     InitChessProgram(&first, FALSE);
11522     FeedMovesToProgram(&first, currentMove);
11523
11524     if (!first.sendTime) {
11525         /* can't tell gnuchess what its clock should read,
11526            so we bow to its notion. */
11527         ResetClocks();
11528         timeRemaining[0][currentMove] = whiteTimeRemaining;
11529         timeRemaining[1][currentMove] = blackTimeRemaining;
11530     }
11531
11532     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11533                 appData.icsEngineAnalyze) && first.analysisSupport) {
11534       SendToProgram("analyze\n", &first);
11535       first.analyzing = TRUE;
11536     }
11537     return 1;
11538 }
11539
11540 /*
11541  * Button procedures
11542  */
11543 void
11544 Reset (int redraw, int init)
11545 {
11546     int i;
11547
11548     if (appData.debugMode) {
11549         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11550                 redraw, init, gameMode);
11551     }
11552     CleanupTail(); // [HGM] vari: delete any stored variations
11553     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11554     pausing = pauseExamInvalid = FALSE;
11555     startedFromSetupPosition = blackPlaysFirst = FALSE;
11556     firstMove = TRUE;
11557     whiteFlag = blackFlag = FALSE;
11558     userOfferedDraw = FALSE;
11559     hintRequested = bookRequested = FALSE;
11560     first.maybeThinking = FALSE;
11561     second.maybeThinking = FALSE;
11562     first.bookSuspend = FALSE; // [HGM] book
11563     second.bookSuspend = FALSE;
11564     thinkOutput[0] = NULLCHAR;
11565     lastHint[0] = NULLCHAR;
11566     ClearGameInfo(&gameInfo);
11567     gameInfo.variant = StringToVariant(appData.variant);
11568     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11569     ics_user_moved = ics_clock_paused = FALSE;
11570     ics_getting_history = H_FALSE;
11571     ics_gamenum = -1;
11572     white_holding[0] = black_holding[0] = NULLCHAR;
11573     ClearProgramStats();
11574     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11575
11576     ResetFrontEnd();
11577     ClearHighlights();
11578     flipView = appData.flipView;
11579     ClearPremoveHighlights();
11580     gotPremove = FALSE;
11581     alarmSounded = FALSE;
11582     killX = killY = -1; // [HGM] lion
11583
11584     GameEnds(EndOfFile, NULL, GE_PLAYER);
11585     if(appData.serverMovesName != NULL) {
11586         /* [HGM] prepare to make moves file for broadcasting */
11587         clock_t t = clock();
11588         if(serverMoves != NULL) fclose(serverMoves);
11589         serverMoves = fopen(appData.serverMovesName, "r");
11590         if(serverMoves != NULL) {
11591             fclose(serverMoves);
11592             /* delay 15 sec before overwriting, so all clients can see end */
11593             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11594         }
11595         serverMoves = fopen(appData.serverMovesName, "w");
11596     }
11597
11598     ExitAnalyzeMode();
11599     gameMode = BeginningOfGame;
11600     ModeHighlight();
11601     if(appData.icsActive) gameInfo.variant = VariantNormal;
11602     currentMove = forwardMostMove = backwardMostMove = 0;
11603     MarkTargetSquares(1);
11604     InitPosition(redraw);
11605     for (i = 0; i < MAX_MOVES; i++) {
11606         if (commentList[i] != NULL) {
11607             free(commentList[i]);
11608             commentList[i] = NULL;
11609         }
11610     }
11611     ResetClocks();
11612     timeRemaining[0][0] = whiteTimeRemaining;
11613     timeRemaining[1][0] = blackTimeRemaining;
11614
11615     if (first.pr == NoProc) {
11616         StartChessProgram(&first);
11617     }
11618     if (init) {
11619             InitChessProgram(&first, startedFromSetupPosition);
11620     }
11621     DisplayTitle("");
11622     DisplayMessage("", "");
11623     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11624     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11625     ClearMap();        // [HGM] exclude: invalidate map
11626 }
11627
11628 void
11629 AutoPlayGameLoop ()
11630 {
11631     for (;;) {
11632         if (!AutoPlayOneMove())
11633           return;
11634         if (matchMode || appData.timeDelay == 0)
11635           continue;
11636         if (appData.timeDelay < 0)
11637           return;
11638         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11639         break;
11640     }
11641 }
11642
11643 void
11644 AnalyzeNextGame()
11645 {
11646     ReloadGame(1); // next game
11647 }
11648
11649 int
11650 AutoPlayOneMove ()
11651 {
11652     int fromX, fromY, toX, toY;
11653
11654     if (appData.debugMode) {
11655       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11656     }
11657
11658     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11659       return FALSE;
11660
11661     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11662       pvInfoList[currentMove].depth = programStats.depth;
11663       pvInfoList[currentMove].score = programStats.score;
11664       pvInfoList[currentMove].time  = 0;
11665       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11666       else { // append analysis of final position as comment
11667         char buf[MSG_SIZ];
11668         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11669         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11670       }
11671       programStats.depth = 0;
11672     }
11673
11674     if (currentMove >= forwardMostMove) {
11675       if(gameMode == AnalyzeFile) {
11676           if(appData.loadGameIndex == -1) {
11677             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11678           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11679           } else {
11680           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11681         }
11682       }
11683 //      gameMode = EndOfGame;
11684 //      ModeHighlight();
11685
11686       /* [AS] Clear current move marker at the end of a game */
11687       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11688
11689       return FALSE;
11690     }
11691
11692     toX = moveList[currentMove][2] - AAA;
11693     toY = moveList[currentMove][3] - ONE;
11694
11695     if (moveList[currentMove][1] == '@') {
11696         if (appData.highlightLastMove) {
11697             SetHighlights(-1, -1, toX, toY);
11698         }
11699     } else {
11700         fromX = moveList[currentMove][0] - AAA;
11701         fromY = moveList[currentMove][1] - ONE;
11702
11703         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11704
11705         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11706
11707         if (appData.highlightLastMove) {
11708             SetHighlights(fromX, fromY, toX, toY);
11709         }
11710     }
11711     DisplayMove(currentMove);
11712     SendMoveToProgram(currentMove++, &first);
11713     DisplayBothClocks();
11714     DrawPosition(FALSE, boards[currentMove]);
11715     // [HGM] PV info: always display, routine tests if empty
11716     DisplayComment(currentMove - 1, commentList[currentMove]);
11717     return TRUE;
11718 }
11719
11720
11721 int
11722 LoadGameOneMove (ChessMove readAhead)
11723 {
11724     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11725     char promoChar = NULLCHAR;
11726     ChessMove moveType;
11727     char move[MSG_SIZ];
11728     char *p, *q;
11729
11730     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11731         gameMode != AnalyzeMode && gameMode != Training) {
11732         gameFileFP = NULL;
11733         return FALSE;
11734     }
11735
11736     yyboardindex = forwardMostMove;
11737     if (readAhead != EndOfFile) {
11738       moveType = readAhead;
11739     } else {
11740       if (gameFileFP == NULL)
11741           return FALSE;
11742       moveType = (ChessMove) Myylex();
11743     }
11744
11745     done = FALSE;
11746     switch (moveType) {
11747       case Comment:
11748         if (appData.debugMode)
11749           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11750         p = yy_text;
11751
11752         /* append the comment but don't display it */
11753         AppendComment(currentMove, p, FALSE);
11754         return TRUE;
11755
11756       case WhiteCapturesEnPassant:
11757       case BlackCapturesEnPassant:
11758       case WhitePromotion:
11759       case BlackPromotion:
11760       case WhiteNonPromotion:
11761       case BlackNonPromotion:
11762       case NormalMove:
11763       case FirstLeg:
11764       case WhiteKingSideCastle:
11765       case WhiteQueenSideCastle:
11766       case BlackKingSideCastle:
11767       case BlackQueenSideCastle:
11768       case WhiteKingSideCastleWild:
11769       case WhiteQueenSideCastleWild:
11770       case BlackKingSideCastleWild:
11771       case BlackQueenSideCastleWild:
11772       /* PUSH Fabien */
11773       case WhiteHSideCastleFR:
11774       case WhiteASideCastleFR:
11775       case BlackHSideCastleFR:
11776       case BlackASideCastleFR:
11777       /* POP Fabien */
11778         if (appData.debugMode)
11779           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11780         fromX = currentMoveString[0] - AAA;
11781         fromY = currentMoveString[1] - ONE;
11782         toX = currentMoveString[2] - AAA;
11783         toY = currentMoveString[3] - ONE;
11784         promoChar = currentMoveString[4];
11785         if(promoChar == ';') promoChar = NULLCHAR;
11786         break;
11787
11788       case WhiteDrop:
11789       case BlackDrop:
11790         if (appData.debugMode)
11791           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11792         fromX = moveType == WhiteDrop ?
11793           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11794         (int) CharToPiece(ToLower(currentMoveString[0]));
11795         fromY = DROP_RANK;
11796         toX = currentMoveString[2] - AAA;
11797         toY = currentMoveString[3] - ONE;
11798         break;
11799
11800       case WhiteWins:
11801       case BlackWins:
11802       case GameIsDrawn:
11803       case GameUnfinished:
11804         if (appData.debugMode)
11805           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11806         p = strchr(yy_text, '{');
11807         if (p == NULL) p = strchr(yy_text, '(');
11808         if (p == NULL) {
11809             p = yy_text;
11810             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11811         } else {
11812             q = strchr(p, *p == '{' ? '}' : ')');
11813             if (q != NULL) *q = NULLCHAR;
11814             p++;
11815         }
11816         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11817         GameEnds(moveType, p, GE_FILE);
11818         done = TRUE;
11819         if (cmailMsgLoaded) {
11820             ClearHighlights();
11821             flipView = WhiteOnMove(currentMove);
11822             if (moveType == GameUnfinished) flipView = !flipView;
11823             if (appData.debugMode)
11824               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11825         }
11826         break;
11827
11828       case EndOfFile:
11829         if (appData.debugMode)
11830           fprintf(debugFP, "Parser hit end of file\n");
11831         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11832           case MT_NONE:
11833           case MT_CHECK:
11834             break;
11835           case MT_CHECKMATE:
11836           case MT_STAINMATE:
11837             if (WhiteOnMove(currentMove)) {
11838                 GameEnds(BlackWins, "Black mates", GE_FILE);
11839             } else {
11840                 GameEnds(WhiteWins, "White mates", GE_FILE);
11841             }
11842             break;
11843           case MT_STALEMATE:
11844             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11845             break;
11846         }
11847         done = TRUE;
11848         break;
11849
11850       case MoveNumberOne:
11851         if (lastLoadGameStart == GNUChessGame) {
11852             /* GNUChessGames have numbers, but they aren't move numbers */
11853             if (appData.debugMode)
11854               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11855                       yy_text, (int) moveType);
11856             return LoadGameOneMove(EndOfFile); /* tail recursion */
11857         }
11858         /* else fall thru */
11859
11860       case XBoardGame:
11861       case GNUChessGame:
11862       case PGNTag:
11863         /* Reached start of next game in file */
11864         if (appData.debugMode)
11865           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11866         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11867           case MT_NONE:
11868           case MT_CHECK:
11869             break;
11870           case MT_CHECKMATE:
11871           case MT_STAINMATE:
11872             if (WhiteOnMove(currentMove)) {
11873                 GameEnds(BlackWins, "Black mates", GE_FILE);
11874             } else {
11875                 GameEnds(WhiteWins, "White mates", GE_FILE);
11876             }
11877             break;
11878           case MT_STALEMATE:
11879             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11880             break;
11881         }
11882         done = TRUE;
11883         break;
11884
11885       case PositionDiagram:     /* should not happen; ignore */
11886       case ElapsedTime:         /* ignore */
11887       case NAG:                 /* ignore */
11888         if (appData.debugMode)
11889           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11890                   yy_text, (int) moveType);
11891         return LoadGameOneMove(EndOfFile); /* tail recursion */
11892
11893       case IllegalMove:
11894         if (appData.testLegality) {
11895             if (appData.debugMode)
11896               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11897             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11898                     (forwardMostMove / 2) + 1,
11899                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11900             DisplayError(move, 0);
11901             done = TRUE;
11902         } else {
11903             if (appData.debugMode)
11904               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11905                       yy_text, currentMoveString);
11906             fromX = currentMoveString[0] - AAA;
11907             fromY = currentMoveString[1] - ONE;
11908             toX = currentMoveString[2] - AAA;
11909             toY = currentMoveString[3] - ONE;
11910             promoChar = currentMoveString[4];
11911         }
11912         break;
11913
11914       case AmbiguousMove:
11915         if (appData.debugMode)
11916           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11917         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11918                 (forwardMostMove / 2) + 1,
11919                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11920         DisplayError(move, 0);
11921         done = TRUE;
11922         break;
11923
11924       default:
11925       case ImpossibleMove:
11926         if (appData.debugMode)
11927           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11928         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11929                 (forwardMostMove / 2) + 1,
11930                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11931         DisplayError(move, 0);
11932         done = TRUE;
11933         break;
11934     }
11935
11936     if (done) {
11937         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11938             DrawPosition(FALSE, boards[currentMove]);
11939             DisplayBothClocks();
11940             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11941               DisplayComment(currentMove - 1, commentList[currentMove]);
11942         }
11943         (void) StopLoadGameTimer();
11944         gameFileFP = NULL;
11945         cmailOldMove = forwardMostMove;
11946         return FALSE;
11947     } else {
11948         /* currentMoveString is set as a side-effect of yylex */
11949
11950         thinkOutput[0] = NULLCHAR;
11951         MakeMove(fromX, fromY, toX, toY, promoChar);
11952         killX = killY = -1; // [HGM] lion: used up
11953         currentMove = forwardMostMove;
11954         return TRUE;
11955     }
11956 }
11957
11958 /* Load the nth game from the given file */
11959 int
11960 LoadGameFromFile (char *filename, int n, char *title, int useList)
11961 {
11962     FILE *f;
11963     char buf[MSG_SIZ];
11964
11965     if (strcmp(filename, "-") == 0) {
11966         f = stdin;
11967         title = "stdin";
11968     } else {
11969         f = fopen(filename, "rb");
11970         if (f == NULL) {
11971           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11972             DisplayError(buf, errno);
11973             return FALSE;
11974         }
11975     }
11976     if (fseek(f, 0, 0) == -1) {
11977         /* f is not seekable; probably a pipe */
11978         useList = FALSE;
11979     }
11980     if (useList && n == 0) {
11981         int error = GameListBuild(f);
11982         if (error) {
11983             DisplayError(_("Cannot build game list"), error);
11984         } else if (!ListEmpty(&gameList) &&
11985                    ((ListGame *) gameList.tailPred)->number > 1) {
11986             GameListPopUp(f, title);
11987             return TRUE;
11988         }
11989         GameListDestroy();
11990         n = 1;
11991     }
11992     if (n == 0) n = 1;
11993     return LoadGame(f, n, title, FALSE);
11994 }
11995
11996
11997 void
11998 MakeRegisteredMove ()
11999 {
12000     int fromX, fromY, toX, toY;
12001     char promoChar;
12002     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12003         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12004           case CMAIL_MOVE:
12005           case CMAIL_DRAW:
12006             if (appData.debugMode)
12007               fprintf(debugFP, "Restoring %s for game %d\n",
12008                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12009
12010             thinkOutput[0] = NULLCHAR;
12011             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12012             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12013             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12014             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12015             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12016             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12017             MakeMove(fromX, fromY, toX, toY, promoChar);
12018             ShowMove(fromX, fromY, toX, toY);
12019
12020             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12021               case MT_NONE:
12022               case MT_CHECK:
12023                 break;
12024
12025               case MT_CHECKMATE:
12026               case MT_STAINMATE:
12027                 if (WhiteOnMove(currentMove)) {
12028                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12029                 } else {
12030                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12031                 }
12032                 break;
12033
12034               case MT_STALEMATE:
12035                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12036                 break;
12037             }
12038
12039             break;
12040
12041           case CMAIL_RESIGN:
12042             if (WhiteOnMove(currentMove)) {
12043                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12044             } else {
12045                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12046             }
12047             break;
12048
12049           case CMAIL_ACCEPT:
12050             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12051             break;
12052
12053           default:
12054             break;
12055         }
12056     }
12057
12058     return;
12059 }
12060
12061 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12062 int
12063 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12064 {
12065     int retVal;
12066
12067     if (gameNumber > nCmailGames) {
12068         DisplayError(_("No more games in this message"), 0);
12069         return FALSE;
12070     }
12071     if (f == lastLoadGameFP) {
12072         int offset = gameNumber - lastLoadGameNumber;
12073         if (offset == 0) {
12074             cmailMsg[0] = NULLCHAR;
12075             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12076                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12077                 nCmailMovesRegistered--;
12078             }
12079             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12080             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12081                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12082             }
12083         } else {
12084             if (! RegisterMove()) return FALSE;
12085         }
12086     }
12087
12088     retVal = LoadGame(f, gameNumber, title, useList);
12089
12090     /* Make move registered during previous look at this game, if any */
12091     MakeRegisteredMove();
12092
12093     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12094         commentList[currentMove]
12095           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12096         DisplayComment(currentMove - 1, commentList[currentMove]);
12097     }
12098
12099     return retVal;
12100 }
12101
12102 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12103 int
12104 ReloadGame (int offset)
12105 {
12106     int gameNumber = lastLoadGameNumber + offset;
12107     if (lastLoadGameFP == NULL) {
12108         DisplayError(_("No game has been loaded yet"), 0);
12109         return FALSE;
12110     }
12111     if (gameNumber <= 0) {
12112         DisplayError(_("Can't back up any further"), 0);
12113         return FALSE;
12114     }
12115     if (cmailMsgLoaded) {
12116         return CmailLoadGame(lastLoadGameFP, gameNumber,
12117                              lastLoadGameTitle, lastLoadGameUseList);
12118     } else {
12119         return LoadGame(lastLoadGameFP, gameNumber,
12120                         lastLoadGameTitle, lastLoadGameUseList);
12121     }
12122 }
12123
12124 int keys[EmptySquare+1];
12125
12126 int
12127 PositionMatches (Board b1, Board b2)
12128 {
12129     int r, f, sum=0;
12130     switch(appData.searchMode) {
12131         case 1: return CompareWithRights(b1, b2);
12132         case 2:
12133             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12134                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12135             }
12136             return TRUE;
12137         case 3:
12138             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12139               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12140                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12141             }
12142             return sum==0;
12143         case 4:
12144             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12145                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12146             }
12147             return sum==0;
12148     }
12149     return TRUE;
12150 }
12151
12152 #define Q_PROMO  4
12153 #define Q_EP     3
12154 #define Q_BCASTL 2
12155 #define Q_WCASTL 1
12156
12157 int pieceList[256], quickBoard[256];
12158 ChessSquare pieceType[256] = { EmptySquare };
12159 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12160 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12161 int soughtTotal, turn;
12162 Boolean epOK, flipSearch;
12163
12164 typedef struct {
12165     unsigned char piece, to;
12166 } Move;
12167
12168 #define DSIZE (250000)
12169
12170 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12171 Move *moveDatabase = initialSpace;
12172 unsigned int movePtr, dataSize = DSIZE;
12173
12174 int
12175 MakePieceList (Board board, int *counts)
12176 {
12177     int r, f, n=Q_PROMO, total=0;
12178     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12179     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12180         int sq = f + (r<<4);
12181         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12182             quickBoard[sq] = ++n;
12183             pieceList[n] = sq;
12184             pieceType[n] = board[r][f];
12185             counts[board[r][f]]++;
12186             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12187             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12188             total++;
12189         }
12190     }
12191     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12192     return total;
12193 }
12194
12195 void
12196 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12197 {
12198     int sq = fromX + (fromY<<4);
12199     int piece = quickBoard[sq], rook;
12200     quickBoard[sq] = 0;
12201     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12202     if(piece == pieceList[1] && fromY == toY) {
12203       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12204         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12205         moveDatabase[movePtr++].piece = Q_WCASTL;
12206         quickBoard[sq] = piece;
12207         piece = quickBoard[from]; quickBoard[from] = 0;
12208         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12209       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12210         quickBoard[sq] = 0; // remove Rook
12211         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12212         moveDatabase[movePtr++].piece = Q_WCASTL;
12213         quickBoard[sq] = pieceList[1]; // put King
12214         piece = rook;
12215         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12216       }
12217     } else
12218     if(piece == pieceList[2] && fromY == toY) {
12219       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12220         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12221         moveDatabase[movePtr++].piece = Q_BCASTL;
12222         quickBoard[sq] = piece;
12223         piece = quickBoard[from]; quickBoard[from] = 0;
12224         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12225       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12226         quickBoard[sq] = 0; // remove Rook
12227         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12228         moveDatabase[movePtr++].piece = Q_BCASTL;
12229         quickBoard[sq] = pieceList[2]; // put King
12230         piece = rook;
12231         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12232       }
12233     } else
12234     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12235         quickBoard[(fromY<<4)+toX] = 0;
12236         moveDatabase[movePtr].piece = Q_EP;
12237         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12238         moveDatabase[movePtr].to = sq;
12239     } else
12240     if(promoPiece != pieceType[piece]) {
12241         moveDatabase[movePtr++].piece = Q_PROMO;
12242         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12243     }
12244     moveDatabase[movePtr].piece = piece;
12245     quickBoard[sq] = piece;
12246     movePtr++;
12247 }
12248
12249 int
12250 PackGame (Board board)
12251 {
12252     Move *newSpace = NULL;
12253     moveDatabase[movePtr].piece = 0; // terminate previous game
12254     if(movePtr > dataSize) {
12255         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12256         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12257         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12258         if(newSpace) {
12259             int i;
12260             Move *p = moveDatabase, *q = newSpace;
12261             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12262             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12263             moveDatabase = newSpace;
12264         } else { // calloc failed, we must be out of memory. Too bad...
12265             dataSize = 0; // prevent calloc events for all subsequent games
12266             return 0;     // and signal this one isn't cached
12267         }
12268     }
12269     movePtr++;
12270     MakePieceList(board, counts);
12271     return movePtr;
12272 }
12273
12274 int
12275 QuickCompare (Board board, int *minCounts, int *maxCounts)
12276 {   // compare according to search mode
12277     int r, f;
12278     switch(appData.searchMode)
12279     {
12280       case 1: // exact position match
12281         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12282         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12283             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12284         }
12285         break;
12286       case 2: // can have extra material on empty squares
12287         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12288             if(board[r][f] == EmptySquare) continue;
12289             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12290         }
12291         break;
12292       case 3: // material with exact Pawn structure
12293         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12294             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12295             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12296         } // fall through to material comparison
12297       case 4: // exact material
12298         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12299         break;
12300       case 6: // material range with given imbalance
12301         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12302         // fall through to range comparison
12303       case 5: // material range
12304         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12305     }
12306     return TRUE;
12307 }
12308
12309 int
12310 QuickScan (Board board, Move *move)
12311 {   // reconstruct game,and compare all positions in it
12312     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12313     do {
12314         int piece = move->piece;
12315         int to = move->to, from = pieceList[piece];
12316         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12317           if(!piece) return -1;
12318           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12319             piece = (++move)->piece;
12320             from = pieceList[piece];
12321             counts[pieceType[piece]]--;
12322             pieceType[piece] = (ChessSquare) move->to;
12323             counts[move->to]++;
12324           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12325             counts[pieceType[quickBoard[to]]]--;
12326             quickBoard[to] = 0; total--;
12327             move++;
12328             continue;
12329           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12330             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12331             from  = pieceList[piece]; // so this must be King
12332             quickBoard[from] = 0;
12333             pieceList[piece] = to;
12334             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12335             quickBoard[from] = 0; // rook
12336             quickBoard[to] = piece;
12337             to = move->to; piece = move->piece;
12338             goto aftercastle;
12339           }
12340         }
12341         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12342         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12343         quickBoard[from] = 0;
12344       aftercastle:
12345         quickBoard[to] = piece;
12346         pieceList[piece] = to;
12347         cnt++; turn ^= 3;
12348         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12349            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12350            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12351                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12352           ) {
12353             static int lastCounts[EmptySquare+1];
12354             int i;
12355             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12356             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12357         } else stretch = 0;
12358         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12359         move++;
12360     } while(1);
12361 }
12362
12363 void
12364 InitSearch ()
12365 {
12366     int r, f;
12367     flipSearch = FALSE;
12368     CopyBoard(soughtBoard, boards[currentMove]);
12369     soughtTotal = MakePieceList(soughtBoard, maxSought);
12370     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12371     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12372     CopyBoard(reverseBoard, boards[currentMove]);
12373     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12374         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12375         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12376         reverseBoard[r][f] = piece;
12377     }
12378     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12379     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12380     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12381                  || (boards[currentMove][CASTLING][2] == NoRights ||
12382                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12383                  && (boards[currentMove][CASTLING][5] == NoRights ||
12384                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12385       ) {
12386         flipSearch = TRUE;
12387         CopyBoard(flipBoard, soughtBoard);
12388         CopyBoard(rotateBoard, reverseBoard);
12389         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12390             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12391             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12392         }
12393     }
12394     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12395     if(appData.searchMode >= 5) {
12396         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12397         MakePieceList(soughtBoard, minSought);
12398         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12399     }
12400     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12401         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12402 }
12403
12404 GameInfo dummyInfo;
12405 static int creatingBook;
12406
12407 int
12408 GameContainsPosition (FILE *f, ListGame *lg)
12409 {
12410     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12411     int fromX, fromY, toX, toY;
12412     char promoChar;
12413     static int initDone=FALSE;
12414
12415     // weed out games based on numerical tag comparison
12416     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12417     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12418     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12419     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12420     if(!initDone) {
12421         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12422         initDone = TRUE;
12423     }
12424     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12425     else CopyBoard(boards[scratch], initialPosition); // default start position
12426     if(lg->moves) {
12427         turn = btm + 1;
12428         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12429         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12430     }
12431     if(btm) plyNr++;
12432     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12433     fseek(f, lg->offset, 0);
12434     yynewfile(f);
12435     while(1) {
12436         yyboardindex = scratch;
12437         quickFlag = plyNr+1;
12438         next = Myylex();
12439         quickFlag = 0;
12440         switch(next) {
12441             case PGNTag:
12442                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12443             default:
12444                 continue;
12445
12446             case XBoardGame:
12447             case GNUChessGame:
12448                 if(plyNr) return -1; // after we have seen moves, this is for new game
12449               continue;
12450
12451             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12452             case ImpossibleMove:
12453             case WhiteWins: // game ends here with these four
12454             case BlackWins:
12455             case GameIsDrawn:
12456             case GameUnfinished:
12457                 return -1;
12458
12459             case IllegalMove:
12460                 if(appData.testLegality) return -1;
12461             case WhiteCapturesEnPassant:
12462             case BlackCapturesEnPassant:
12463             case WhitePromotion:
12464             case BlackPromotion:
12465             case WhiteNonPromotion:
12466             case BlackNonPromotion:
12467             case NormalMove:
12468             case FirstLeg:
12469             case WhiteKingSideCastle:
12470             case WhiteQueenSideCastle:
12471             case BlackKingSideCastle:
12472             case BlackQueenSideCastle:
12473             case WhiteKingSideCastleWild:
12474             case WhiteQueenSideCastleWild:
12475             case BlackKingSideCastleWild:
12476             case BlackQueenSideCastleWild:
12477             case WhiteHSideCastleFR:
12478             case WhiteASideCastleFR:
12479             case BlackHSideCastleFR:
12480             case BlackASideCastleFR:
12481                 fromX = currentMoveString[0] - AAA;
12482                 fromY = currentMoveString[1] - ONE;
12483                 toX = currentMoveString[2] - AAA;
12484                 toY = currentMoveString[3] - ONE;
12485                 promoChar = currentMoveString[4];
12486                 break;
12487             case WhiteDrop:
12488             case BlackDrop:
12489                 fromX = next == WhiteDrop ?
12490                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12491                   (int) CharToPiece(ToLower(currentMoveString[0]));
12492                 fromY = DROP_RANK;
12493                 toX = currentMoveString[2] - AAA;
12494                 toY = currentMoveString[3] - ONE;
12495                 promoChar = 0;
12496                 break;
12497         }
12498         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12499         plyNr++;
12500         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12501         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12502         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12503         if(appData.findMirror) {
12504             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12505             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12506         }
12507     }
12508 }
12509
12510 /* Load the nth game from open file f */
12511 int
12512 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12513 {
12514     ChessMove cm;
12515     char buf[MSG_SIZ];
12516     int gn = gameNumber;
12517     ListGame *lg = NULL;
12518     int numPGNTags = 0;
12519     int err, pos = -1;
12520     GameMode oldGameMode;
12521     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12522
12523     if (appData.debugMode)
12524         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12525
12526     if (gameMode == Training )
12527         SetTrainingModeOff();
12528
12529     oldGameMode = gameMode;
12530     if (gameMode != BeginningOfGame) {
12531       Reset(FALSE, TRUE);
12532     }
12533     killX = killY = -1; // [HGM] lion: in case we did not Reset
12534
12535     gameFileFP = f;
12536     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12537         fclose(lastLoadGameFP);
12538     }
12539
12540     if (useList) {
12541         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12542
12543         if (lg) {
12544             fseek(f, lg->offset, 0);
12545             GameListHighlight(gameNumber);
12546             pos = lg->position;
12547             gn = 1;
12548         }
12549         else {
12550             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12551               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12552             else
12553             DisplayError(_("Game number out of range"), 0);
12554             return FALSE;
12555         }
12556     } else {
12557         GameListDestroy();
12558         if (fseek(f, 0, 0) == -1) {
12559             if (f == lastLoadGameFP ?
12560                 gameNumber == lastLoadGameNumber + 1 :
12561                 gameNumber == 1) {
12562                 gn = 1;
12563             } else {
12564                 DisplayError(_("Can't seek on game file"), 0);
12565                 return FALSE;
12566             }
12567         }
12568     }
12569     lastLoadGameFP = f;
12570     lastLoadGameNumber = gameNumber;
12571     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12572     lastLoadGameUseList = useList;
12573
12574     yynewfile(f);
12575
12576     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12577       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12578                 lg->gameInfo.black);
12579             DisplayTitle(buf);
12580     } else if (*title != NULLCHAR) {
12581         if (gameNumber > 1) {
12582           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12583             DisplayTitle(buf);
12584         } else {
12585             DisplayTitle(title);
12586         }
12587     }
12588
12589     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12590         gameMode = PlayFromGameFile;
12591         ModeHighlight();
12592     }
12593
12594     currentMove = forwardMostMove = backwardMostMove = 0;
12595     CopyBoard(boards[0], initialPosition);
12596     StopClocks();
12597
12598     /*
12599      * Skip the first gn-1 games in the file.
12600      * Also skip over anything that precedes an identifiable
12601      * start of game marker, to avoid being confused by
12602      * garbage at the start of the file.  Currently
12603      * recognized start of game markers are the move number "1",
12604      * the pattern "gnuchess .* game", the pattern
12605      * "^[#;%] [^ ]* game file", and a PGN tag block.
12606      * A game that starts with one of the latter two patterns
12607      * will also have a move number 1, possibly
12608      * following a position diagram.
12609      * 5-4-02: Let's try being more lenient and allowing a game to
12610      * start with an unnumbered move.  Does that break anything?
12611      */
12612     cm = lastLoadGameStart = EndOfFile;
12613     while (gn > 0) {
12614         yyboardindex = forwardMostMove;
12615         cm = (ChessMove) Myylex();
12616         switch (cm) {
12617           case EndOfFile:
12618             if (cmailMsgLoaded) {
12619                 nCmailGames = CMAIL_MAX_GAMES - gn;
12620             } else {
12621                 Reset(TRUE, TRUE);
12622                 DisplayError(_("Game not found in file"), 0);
12623             }
12624             return FALSE;
12625
12626           case GNUChessGame:
12627           case XBoardGame:
12628             gn--;
12629             lastLoadGameStart = cm;
12630             break;
12631
12632           case MoveNumberOne:
12633             switch (lastLoadGameStart) {
12634               case GNUChessGame:
12635               case XBoardGame:
12636               case PGNTag:
12637                 break;
12638               case MoveNumberOne:
12639               case EndOfFile:
12640                 gn--;           /* count this game */
12641                 lastLoadGameStart = cm;
12642                 break;
12643               default:
12644                 /* impossible */
12645                 break;
12646             }
12647             break;
12648
12649           case PGNTag:
12650             switch (lastLoadGameStart) {
12651               case GNUChessGame:
12652               case PGNTag:
12653               case MoveNumberOne:
12654               case EndOfFile:
12655                 gn--;           /* count this game */
12656                 lastLoadGameStart = cm;
12657                 break;
12658               case XBoardGame:
12659                 lastLoadGameStart = cm; /* game counted already */
12660                 break;
12661               default:
12662                 /* impossible */
12663                 break;
12664             }
12665             if (gn > 0) {
12666                 do {
12667                     yyboardindex = forwardMostMove;
12668                     cm = (ChessMove) Myylex();
12669                 } while (cm == PGNTag || cm == Comment);
12670             }
12671             break;
12672
12673           case WhiteWins:
12674           case BlackWins:
12675           case GameIsDrawn:
12676             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12677                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12678                     != CMAIL_OLD_RESULT) {
12679                     nCmailResults ++ ;
12680                     cmailResult[  CMAIL_MAX_GAMES
12681                                 - gn - 1] = CMAIL_OLD_RESULT;
12682                 }
12683             }
12684             break;
12685
12686           case NormalMove:
12687           case FirstLeg:
12688             /* Only a NormalMove can be at the start of a game
12689              * without a position diagram. */
12690             if (lastLoadGameStart == EndOfFile ) {
12691               gn--;
12692               lastLoadGameStart = MoveNumberOne;
12693             }
12694             break;
12695
12696           default:
12697             break;
12698         }
12699     }
12700
12701     if (appData.debugMode)
12702       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12703
12704     if (cm == XBoardGame) {
12705         /* Skip any header junk before position diagram and/or move 1 */
12706         for (;;) {
12707             yyboardindex = forwardMostMove;
12708             cm = (ChessMove) Myylex();
12709
12710             if (cm == EndOfFile ||
12711                 cm == GNUChessGame || cm == XBoardGame) {
12712                 /* Empty game; pretend end-of-file and handle later */
12713                 cm = EndOfFile;
12714                 break;
12715             }
12716
12717             if (cm == MoveNumberOne || cm == PositionDiagram ||
12718                 cm == PGNTag || cm == Comment)
12719               break;
12720         }
12721     } else if (cm == GNUChessGame) {
12722         if (gameInfo.event != NULL) {
12723             free(gameInfo.event);
12724         }
12725         gameInfo.event = StrSave(yy_text);
12726     }
12727
12728     startedFromSetupPosition = FALSE;
12729     while (cm == PGNTag) {
12730         if (appData.debugMode)
12731           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12732         err = ParsePGNTag(yy_text, &gameInfo);
12733         if (!err) numPGNTags++;
12734
12735         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12736         if(gameInfo.variant != oldVariant) {
12737             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12738             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12739             InitPosition(TRUE);
12740             oldVariant = gameInfo.variant;
12741             if (appData.debugMode)
12742               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12743         }
12744
12745
12746         if (gameInfo.fen != NULL) {
12747           Board initial_position;
12748           startedFromSetupPosition = TRUE;
12749           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12750             Reset(TRUE, TRUE);
12751             DisplayError(_("Bad FEN position in file"), 0);
12752             return FALSE;
12753           }
12754           CopyBoard(boards[0], initial_position);
12755           if (blackPlaysFirst) {
12756             currentMove = forwardMostMove = backwardMostMove = 1;
12757             CopyBoard(boards[1], initial_position);
12758             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12759             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12760             timeRemaining[0][1] = whiteTimeRemaining;
12761             timeRemaining[1][1] = blackTimeRemaining;
12762             if (commentList[0] != NULL) {
12763               commentList[1] = commentList[0];
12764               commentList[0] = NULL;
12765             }
12766           } else {
12767             currentMove = forwardMostMove = backwardMostMove = 0;
12768           }
12769           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12770           {   int i;
12771               initialRulePlies = FENrulePlies;
12772               for( i=0; i< nrCastlingRights; i++ )
12773                   initialRights[i] = initial_position[CASTLING][i];
12774           }
12775           yyboardindex = forwardMostMove;
12776           free(gameInfo.fen);
12777           gameInfo.fen = NULL;
12778         }
12779
12780         yyboardindex = forwardMostMove;
12781         cm = (ChessMove) Myylex();
12782
12783         /* Handle comments interspersed among the tags */
12784         while (cm == Comment) {
12785             char *p;
12786             if (appData.debugMode)
12787               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12788             p = yy_text;
12789             AppendComment(currentMove, p, FALSE);
12790             yyboardindex = forwardMostMove;
12791             cm = (ChessMove) Myylex();
12792         }
12793     }
12794
12795     /* don't rely on existence of Event tag since if game was
12796      * pasted from clipboard the Event tag may not exist
12797      */
12798     if (numPGNTags > 0){
12799         char *tags;
12800         if (gameInfo.variant == VariantNormal) {
12801           VariantClass v = StringToVariant(gameInfo.event);
12802           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12803           if(v < VariantShogi) gameInfo.variant = v;
12804         }
12805         if (!matchMode) {
12806           if( appData.autoDisplayTags ) {
12807             tags = PGNTags(&gameInfo);
12808             TagsPopUp(tags, CmailMsg());
12809             free(tags);
12810           }
12811         }
12812     } else {
12813         /* Make something up, but don't display it now */
12814         SetGameInfo();
12815         TagsPopDown();
12816     }
12817
12818     if (cm == PositionDiagram) {
12819         int i, j;
12820         char *p;
12821         Board initial_position;
12822
12823         if (appData.debugMode)
12824           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12825
12826         if (!startedFromSetupPosition) {
12827             p = yy_text;
12828             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12829               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12830                 switch (*p) {
12831                   case '{':
12832                   case '[':
12833                   case '-':
12834                   case ' ':
12835                   case '\t':
12836                   case '\n':
12837                   case '\r':
12838                     break;
12839                   default:
12840                     initial_position[i][j++] = CharToPiece(*p);
12841                     break;
12842                 }
12843             while (*p == ' ' || *p == '\t' ||
12844                    *p == '\n' || *p == '\r') p++;
12845
12846             if (strncmp(p, "black", strlen("black"))==0)
12847               blackPlaysFirst = TRUE;
12848             else
12849               blackPlaysFirst = FALSE;
12850             startedFromSetupPosition = TRUE;
12851
12852             CopyBoard(boards[0], initial_position);
12853             if (blackPlaysFirst) {
12854                 currentMove = forwardMostMove = backwardMostMove = 1;
12855                 CopyBoard(boards[1], initial_position);
12856                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12857                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12858                 timeRemaining[0][1] = whiteTimeRemaining;
12859                 timeRemaining[1][1] = blackTimeRemaining;
12860                 if (commentList[0] != NULL) {
12861                     commentList[1] = commentList[0];
12862                     commentList[0] = NULL;
12863                 }
12864             } else {
12865                 currentMove = forwardMostMove = backwardMostMove = 0;
12866             }
12867         }
12868         yyboardindex = forwardMostMove;
12869         cm = (ChessMove) Myylex();
12870     }
12871
12872   if(!creatingBook) {
12873     if (first.pr == NoProc) {
12874         StartChessProgram(&first);
12875     }
12876     InitChessProgram(&first, FALSE);
12877     SendToProgram("force\n", &first);
12878     if (startedFromSetupPosition) {
12879         SendBoard(&first, forwardMostMove);
12880     if (appData.debugMode) {
12881         fprintf(debugFP, "Load Game\n");
12882     }
12883         DisplayBothClocks();
12884     }
12885   }
12886
12887     /* [HGM] server: flag to write setup moves in broadcast file as one */
12888     loadFlag = appData.suppressLoadMoves;
12889
12890     while (cm == Comment) {
12891         char *p;
12892         if (appData.debugMode)
12893           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12894         p = yy_text;
12895         AppendComment(currentMove, p, FALSE);
12896         yyboardindex = forwardMostMove;
12897         cm = (ChessMove) Myylex();
12898     }
12899
12900     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12901         cm == WhiteWins || cm == BlackWins ||
12902         cm == GameIsDrawn || cm == GameUnfinished) {
12903         DisplayMessage("", _("No moves in game"));
12904         if (cmailMsgLoaded) {
12905             if (appData.debugMode)
12906               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12907             ClearHighlights();
12908             flipView = FALSE;
12909         }
12910         DrawPosition(FALSE, boards[currentMove]);
12911         DisplayBothClocks();
12912         gameMode = EditGame;
12913         ModeHighlight();
12914         gameFileFP = NULL;
12915         cmailOldMove = 0;
12916         return TRUE;
12917     }
12918
12919     // [HGM] PV info: routine tests if comment empty
12920     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12921         DisplayComment(currentMove - 1, commentList[currentMove]);
12922     }
12923     if (!matchMode && appData.timeDelay != 0)
12924       DrawPosition(FALSE, boards[currentMove]);
12925
12926     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12927       programStats.ok_to_send = 1;
12928     }
12929
12930     /* if the first token after the PGN tags is a move
12931      * and not move number 1, retrieve it from the parser
12932      */
12933     if (cm != MoveNumberOne)
12934         LoadGameOneMove(cm);
12935
12936     /* load the remaining moves from the file */
12937     while (LoadGameOneMove(EndOfFile)) {
12938       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12939       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12940     }
12941
12942     /* rewind to the start of the game */
12943     currentMove = backwardMostMove;
12944
12945     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12946
12947     if (oldGameMode == AnalyzeFile) {
12948       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12949       AnalyzeFileEvent();
12950     } else
12951     if (oldGameMode == AnalyzeMode) {
12952       AnalyzeFileEvent();
12953     }
12954
12955     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12956         long int w, b; // [HGM] adjourn: restore saved clock times
12957         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12958         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12959             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12960             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12961         }
12962     }
12963
12964     if(creatingBook) return TRUE;
12965     if (!matchMode && pos > 0) {
12966         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12967     } else
12968     if (matchMode || appData.timeDelay == 0) {
12969       ToEndEvent();
12970     } else if (appData.timeDelay > 0) {
12971       AutoPlayGameLoop();
12972     }
12973
12974     if (appData.debugMode)
12975         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12976
12977     loadFlag = 0; /* [HGM] true game starts */
12978     return TRUE;
12979 }
12980
12981 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12982 int
12983 ReloadPosition (int offset)
12984 {
12985     int positionNumber = lastLoadPositionNumber + offset;
12986     if (lastLoadPositionFP == NULL) {
12987         DisplayError(_("No position has been loaded yet"), 0);
12988         return FALSE;
12989     }
12990     if (positionNumber <= 0) {
12991         DisplayError(_("Can't back up any further"), 0);
12992         return FALSE;
12993     }
12994     return LoadPosition(lastLoadPositionFP, positionNumber,
12995                         lastLoadPositionTitle);
12996 }
12997
12998 /* Load the nth position from the given file */
12999 int
13000 LoadPositionFromFile (char *filename, int n, char *title)
13001 {
13002     FILE *f;
13003     char buf[MSG_SIZ];
13004
13005     if (strcmp(filename, "-") == 0) {
13006         return LoadPosition(stdin, n, "stdin");
13007     } else {
13008         f = fopen(filename, "rb");
13009         if (f == NULL) {
13010             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13011             DisplayError(buf, errno);
13012             return FALSE;
13013         } else {
13014             return LoadPosition(f, n, title);
13015         }
13016     }
13017 }
13018
13019 /* Load the nth position from the given open file, and close it */
13020 int
13021 LoadPosition (FILE *f, int positionNumber, char *title)
13022 {
13023     char *p, line[MSG_SIZ];
13024     Board initial_position;
13025     int i, j, fenMode, pn;
13026
13027     if (gameMode == Training )
13028         SetTrainingModeOff();
13029
13030     if (gameMode != BeginningOfGame) {
13031         Reset(FALSE, TRUE);
13032     }
13033     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13034         fclose(lastLoadPositionFP);
13035     }
13036     if (positionNumber == 0) positionNumber = 1;
13037     lastLoadPositionFP = f;
13038     lastLoadPositionNumber = positionNumber;
13039     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13040     if (first.pr == NoProc && !appData.noChessProgram) {
13041       StartChessProgram(&first);
13042       InitChessProgram(&first, FALSE);
13043     }
13044     pn = positionNumber;
13045     if (positionNumber < 0) {
13046         /* Negative position number means to seek to that byte offset */
13047         if (fseek(f, -positionNumber, 0) == -1) {
13048             DisplayError(_("Can't seek on position file"), 0);
13049             return FALSE;
13050         };
13051         pn = 1;
13052     } else {
13053         if (fseek(f, 0, 0) == -1) {
13054             if (f == lastLoadPositionFP ?
13055                 positionNumber == lastLoadPositionNumber + 1 :
13056                 positionNumber == 1) {
13057                 pn = 1;
13058             } else {
13059                 DisplayError(_("Can't seek on position file"), 0);
13060                 return FALSE;
13061             }
13062         }
13063     }
13064     /* See if this file is FEN or old-style xboard */
13065     if (fgets(line, MSG_SIZ, f) == NULL) {
13066         DisplayError(_("Position not found in file"), 0);
13067         return FALSE;
13068     }
13069     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13070     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13071
13072     if (pn >= 2) {
13073         if (fenMode || line[0] == '#') pn--;
13074         while (pn > 0) {
13075             /* skip positions before number pn */
13076             if (fgets(line, MSG_SIZ, f) == NULL) {
13077                 Reset(TRUE, TRUE);
13078                 DisplayError(_("Position not found in file"), 0);
13079                 return FALSE;
13080             }
13081             if (fenMode || line[0] == '#') pn--;
13082         }
13083     }
13084
13085     if (fenMode) {
13086         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13087             DisplayError(_("Bad FEN position in file"), 0);
13088             return FALSE;
13089         }
13090     } else {
13091         (void) fgets(line, MSG_SIZ, f);
13092         (void) fgets(line, MSG_SIZ, f);
13093
13094         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13095             (void) fgets(line, MSG_SIZ, f);
13096             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13097                 if (*p == ' ')
13098                   continue;
13099                 initial_position[i][j++] = CharToPiece(*p);
13100             }
13101         }
13102
13103         blackPlaysFirst = FALSE;
13104         if (!feof(f)) {
13105             (void) fgets(line, MSG_SIZ, f);
13106             if (strncmp(line, "black", strlen("black"))==0)
13107               blackPlaysFirst = TRUE;
13108         }
13109     }
13110     startedFromSetupPosition = TRUE;
13111
13112     CopyBoard(boards[0], initial_position);
13113     if (blackPlaysFirst) {
13114         currentMove = forwardMostMove = backwardMostMove = 1;
13115         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13116         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13117         CopyBoard(boards[1], initial_position);
13118         DisplayMessage("", _("Black to play"));
13119     } else {
13120         currentMove = forwardMostMove = backwardMostMove = 0;
13121         DisplayMessage("", _("White to play"));
13122     }
13123     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13124     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13125         SendToProgram("force\n", &first);
13126         SendBoard(&first, forwardMostMove);
13127     }
13128     if (appData.debugMode) {
13129 int i, j;
13130   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13131   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13132         fprintf(debugFP, "Load Position\n");
13133     }
13134
13135     if (positionNumber > 1) {
13136       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13137         DisplayTitle(line);
13138     } else {
13139         DisplayTitle(title);
13140     }
13141     gameMode = EditGame;
13142     ModeHighlight();
13143     ResetClocks();
13144     timeRemaining[0][1] = whiteTimeRemaining;
13145     timeRemaining[1][1] = blackTimeRemaining;
13146     DrawPosition(FALSE, boards[currentMove]);
13147
13148     return TRUE;
13149 }
13150
13151
13152 void
13153 CopyPlayerNameIntoFileName (char **dest, char *src)
13154 {
13155     while (*src != NULLCHAR && *src != ',') {
13156         if (*src == ' ') {
13157             *(*dest)++ = '_';
13158             src++;
13159         } else {
13160             *(*dest)++ = *src++;
13161         }
13162     }
13163 }
13164
13165 char *
13166 DefaultFileName (char *ext)
13167 {
13168     static char def[MSG_SIZ];
13169     char *p;
13170
13171     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13172         p = def;
13173         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13174         *p++ = '-';
13175         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13176         *p++ = '.';
13177         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13178     } else {
13179         def[0] = NULLCHAR;
13180     }
13181     return def;
13182 }
13183
13184 /* Save the current game to the given file */
13185 int
13186 SaveGameToFile (char *filename, int append)
13187 {
13188     FILE *f;
13189     char buf[MSG_SIZ];
13190     int result, i, t,tot=0;
13191
13192     if (strcmp(filename, "-") == 0) {
13193         return SaveGame(stdout, 0, NULL);
13194     } else {
13195         for(i=0; i<10; i++) { // upto 10 tries
13196              f = fopen(filename, append ? "a" : "w");
13197              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13198              if(f || errno != 13) break;
13199              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13200              tot += t;
13201         }
13202         if (f == NULL) {
13203             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13204             DisplayError(buf, errno);
13205             return FALSE;
13206         } else {
13207             safeStrCpy(buf, lastMsg, MSG_SIZ);
13208             DisplayMessage(_("Waiting for access to save file"), "");
13209             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13210             DisplayMessage(_("Saving game"), "");
13211             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13212             result = SaveGame(f, 0, NULL);
13213             DisplayMessage(buf, "");
13214             return result;
13215         }
13216     }
13217 }
13218
13219 char *
13220 SavePart (char *str)
13221 {
13222     static char buf[MSG_SIZ];
13223     char *p;
13224
13225     p = strchr(str, ' ');
13226     if (p == NULL) return str;
13227     strncpy(buf, str, p - str);
13228     buf[p - str] = NULLCHAR;
13229     return buf;
13230 }
13231
13232 #define PGN_MAX_LINE 75
13233
13234 #define PGN_SIDE_WHITE  0
13235 #define PGN_SIDE_BLACK  1
13236
13237 static int
13238 FindFirstMoveOutOfBook (int side)
13239 {
13240     int result = -1;
13241
13242     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13243         int index = backwardMostMove;
13244         int has_book_hit = 0;
13245
13246         if( (index % 2) != side ) {
13247             index++;
13248         }
13249
13250         while( index < forwardMostMove ) {
13251             /* Check to see if engine is in book */
13252             int depth = pvInfoList[index].depth;
13253             int score = pvInfoList[index].score;
13254             int in_book = 0;
13255
13256             if( depth <= 2 ) {
13257                 in_book = 1;
13258             }
13259             else if( score == 0 && depth == 63 ) {
13260                 in_book = 1; /* Zappa */
13261             }
13262             else if( score == 2 && depth == 99 ) {
13263                 in_book = 1; /* Abrok */
13264             }
13265
13266             has_book_hit += in_book;
13267
13268             if( ! in_book ) {
13269                 result = index;
13270
13271                 break;
13272             }
13273
13274             index += 2;
13275         }
13276     }
13277
13278     return result;
13279 }
13280
13281 void
13282 GetOutOfBookInfo (char * buf)
13283 {
13284     int oob[2];
13285     int i;
13286     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13287
13288     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13289     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13290
13291     *buf = '\0';
13292
13293     if( oob[0] >= 0 || oob[1] >= 0 ) {
13294         for( i=0; i<2; i++ ) {
13295             int idx = oob[i];
13296
13297             if( idx >= 0 ) {
13298                 if( i > 0 && oob[0] >= 0 ) {
13299                     strcat( buf, "   " );
13300                 }
13301
13302                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13303                 sprintf( buf+strlen(buf), "%s%.2f",
13304                     pvInfoList[idx].score >= 0 ? "+" : "",
13305                     pvInfoList[idx].score / 100.0 );
13306             }
13307         }
13308     }
13309 }
13310
13311 /* Save game in PGN style and close the file */
13312 int
13313 SaveGamePGN (FILE *f)
13314 {
13315     int i, offset, linelen, newblock;
13316 //    char *movetext;
13317     char numtext[32];
13318     int movelen, numlen, blank;
13319     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13320
13321     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13322
13323     PrintPGNTags(f, &gameInfo);
13324
13325     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13326
13327     if (backwardMostMove > 0 || startedFromSetupPosition) {
13328         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13329         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13330         fprintf(f, "\n{--------------\n");
13331         PrintPosition(f, backwardMostMove);
13332         fprintf(f, "--------------}\n");
13333         free(fen);
13334     }
13335     else {
13336         /* [AS] Out of book annotation */
13337         if( appData.saveOutOfBookInfo ) {
13338             char buf[64];
13339
13340             GetOutOfBookInfo( buf );
13341
13342             if( buf[0] != '\0' ) {
13343                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13344             }
13345         }
13346
13347         fprintf(f, "\n");
13348     }
13349
13350     i = backwardMostMove;
13351     linelen = 0;
13352     newblock = TRUE;
13353
13354     while (i < forwardMostMove) {
13355         /* Print comments preceding this move */
13356         if (commentList[i] != NULL) {
13357             if (linelen > 0) fprintf(f, "\n");
13358             fprintf(f, "%s", commentList[i]);
13359             linelen = 0;
13360             newblock = TRUE;
13361         }
13362
13363         /* Format move number */
13364         if ((i % 2) == 0)
13365           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13366         else
13367           if (newblock)
13368             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13369           else
13370             numtext[0] = NULLCHAR;
13371
13372         numlen = strlen(numtext);
13373         newblock = FALSE;
13374
13375         /* Print move number */
13376         blank = linelen > 0 && numlen > 0;
13377         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13378             fprintf(f, "\n");
13379             linelen = 0;
13380             blank = 0;
13381         }
13382         if (blank) {
13383             fprintf(f, " ");
13384             linelen++;
13385         }
13386         fprintf(f, "%s", numtext);
13387         linelen += numlen;
13388
13389         /* Get move */
13390         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13391         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13392
13393         /* Print move */
13394         blank = linelen > 0 && movelen > 0;
13395         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13396             fprintf(f, "\n");
13397             linelen = 0;
13398             blank = 0;
13399         }
13400         if (blank) {
13401             fprintf(f, " ");
13402             linelen++;
13403         }
13404         fprintf(f, "%s", move_buffer);
13405         linelen += movelen;
13406
13407         /* [AS] Add PV info if present */
13408         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13409             /* [HGM] add time */
13410             char buf[MSG_SIZ]; int seconds;
13411
13412             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13413
13414             if( seconds <= 0)
13415               buf[0] = 0;
13416             else
13417               if( seconds < 30 )
13418                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13419               else
13420                 {
13421                   seconds = (seconds + 4)/10; // round to full seconds
13422                   if( seconds < 60 )
13423                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13424                   else
13425                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13426                 }
13427
13428             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13429                       pvInfoList[i].score >= 0 ? "+" : "",
13430                       pvInfoList[i].score / 100.0,
13431                       pvInfoList[i].depth,
13432                       buf );
13433
13434             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13435
13436             /* Print score/depth */
13437             blank = linelen > 0 && movelen > 0;
13438             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13439                 fprintf(f, "\n");
13440                 linelen = 0;
13441                 blank = 0;
13442             }
13443             if (blank) {
13444                 fprintf(f, " ");
13445                 linelen++;
13446             }
13447             fprintf(f, "%s", move_buffer);
13448             linelen += movelen;
13449         }
13450
13451         i++;
13452     }
13453
13454     /* Start a new line */
13455     if (linelen > 0) fprintf(f, "\n");
13456
13457     /* Print comments after last move */
13458     if (commentList[i] != NULL) {
13459         fprintf(f, "%s\n", commentList[i]);
13460     }
13461
13462     /* Print result */
13463     if (gameInfo.resultDetails != NULL &&
13464         gameInfo.resultDetails[0] != NULLCHAR) {
13465         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13466         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13467            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13468             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13469         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13470     } else {
13471         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13472     }
13473
13474     fclose(f);
13475     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13476     return TRUE;
13477 }
13478
13479 /* Save game in old style and close the file */
13480 int
13481 SaveGameOldStyle (FILE *f)
13482 {
13483     int i, offset;
13484     time_t tm;
13485
13486     tm = time((time_t *) NULL);
13487
13488     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13489     PrintOpponents(f);
13490
13491     if (backwardMostMove > 0 || startedFromSetupPosition) {
13492         fprintf(f, "\n[--------------\n");
13493         PrintPosition(f, backwardMostMove);
13494         fprintf(f, "--------------]\n");
13495     } else {
13496         fprintf(f, "\n");
13497     }
13498
13499     i = backwardMostMove;
13500     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13501
13502     while (i < forwardMostMove) {
13503         if (commentList[i] != NULL) {
13504             fprintf(f, "[%s]\n", commentList[i]);
13505         }
13506
13507         if ((i % 2) == 1) {
13508             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13509             i++;
13510         } else {
13511             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13512             i++;
13513             if (commentList[i] != NULL) {
13514                 fprintf(f, "\n");
13515                 continue;
13516             }
13517             if (i >= forwardMostMove) {
13518                 fprintf(f, "\n");
13519                 break;
13520             }
13521             fprintf(f, "%s\n", parseList[i]);
13522             i++;
13523         }
13524     }
13525
13526     if (commentList[i] != NULL) {
13527         fprintf(f, "[%s]\n", commentList[i]);
13528     }
13529
13530     /* This isn't really the old style, but it's close enough */
13531     if (gameInfo.resultDetails != NULL &&
13532         gameInfo.resultDetails[0] != NULLCHAR) {
13533         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13534                 gameInfo.resultDetails);
13535     } else {
13536         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13537     }
13538
13539     fclose(f);
13540     return TRUE;
13541 }
13542
13543 /* Save the current game to open file f and close the file */
13544 int
13545 SaveGame (FILE *f, int dummy, char *dummy2)
13546 {
13547     if (gameMode == EditPosition) EditPositionDone(TRUE);
13548     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13549     if (appData.oldSaveStyle)
13550       return SaveGameOldStyle(f);
13551     else
13552       return SaveGamePGN(f);
13553 }
13554
13555 /* Save the current position to the given file */
13556 int
13557 SavePositionToFile (char *filename)
13558 {
13559     FILE *f;
13560     char buf[MSG_SIZ];
13561
13562     if (strcmp(filename, "-") == 0) {
13563         return SavePosition(stdout, 0, NULL);
13564     } else {
13565         f = fopen(filename, "a");
13566         if (f == NULL) {
13567             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13568             DisplayError(buf, errno);
13569             return FALSE;
13570         } else {
13571             safeStrCpy(buf, lastMsg, MSG_SIZ);
13572             DisplayMessage(_("Waiting for access to save file"), "");
13573             flock(fileno(f), LOCK_EX); // [HGM] lock
13574             DisplayMessage(_("Saving position"), "");
13575             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13576             SavePosition(f, 0, NULL);
13577             DisplayMessage(buf, "");
13578             return TRUE;
13579         }
13580     }
13581 }
13582
13583 /* Save the current position to the given open file and close the file */
13584 int
13585 SavePosition (FILE *f, int dummy, char *dummy2)
13586 {
13587     time_t tm;
13588     char *fen;
13589
13590     if (gameMode == EditPosition) EditPositionDone(TRUE);
13591     if (appData.oldSaveStyle) {
13592         tm = time((time_t *) NULL);
13593
13594         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13595         PrintOpponents(f);
13596         fprintf(f, "[--------------\n");
13597         PrintPosition(f, currentMove);
13598         fprintf(f, "--------------]\n");
13599     } else {
13600         fen = PositionToFEN(currentMove, NULL, 1);
13601         fprintf(f, "%s\n", fen);
13602         free(fen);
13603     }
13604     fclose(f);
13605     return TRUE;
13606 }
13607
13608 void
13609 ReloadCmailMsgEvent (int unregister)
13610 {
13611 #if !WIN32
13612     static char *inFilename = NULL;
13613     static char *outFilename;
13614     int i;
13615     struct stat inbuf, outbuf;
13616     int status;
13617
13618     /* Any registered moves are unregistered if unregister is set, */
13619     /* i.e. invoked by the signal handler */
13620     if (unregister) {
13621         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13622             cmailMoveRegistered[i] = FALSE;
13623             if (cmailCommentList[i] != NULL) {
13624                 free(cmailCommentList[i]);
13625                 cmailCommentList[i] = NULL;
13626             }
13627         }
13628         nCmailMovesRegistered = 0;
13629     }
13630
13631     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13632         cmailResult[i] = CMAIL_NOT_RESULT;
13633     }
13634     nCmailResults = 0;
13635
13636     if (inFilename == NULL) {
13637         /* Because the filenames are static they only get malloced once  */
13638         /* and they never get freed                                      */
13639         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13640         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13641
13642         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13643         sprintf(outFilename, "%s.out", appData.cmailGameName);
13644     }
13645
13646     status = stat(outFilename, &outbuf);
13647     if (status < 0) {
13648         cmailMailedMove = FALSE;
13649     } else {
13650         status = stat(inFilename, &inbuf);
13651         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13652     }
13653
13654     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13655        counts the games, notes how each one terminated, etc.
13656
13657        It would be nice to remove this kludge and instead gather all
13658        the information while building the game list.  (And to keep it
13659        in the game list nodes instead of having a bunch of fixed-size
13660        parallel arrays.)  Note this will require getting each game's
13661        termination from the PGN tags, as the game list builder does
13662        not process the game moves.  --mann
13663        */
13664     cmailMsgLoaded = TRUE;
13665     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13666
13667     /* Load first game in the file or popup game menu */
13668     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13669
13670 #endif /* !WIN32 */
13671     return;
13672 }
13673
13674 int
13675 RegisterMove ()
13676 {
13677     FILE *f;
13678     char string[MSG_SIZ];
13679
13680     if (   cmailMailedMove
13681         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13682         return TRUE;            /* Allow free viewing  */
13683     }
13684
13685     /* Unregister move to ensure that we don't leave RegisterMove        */
13686     /* with the move registered when the conditions for registering no   */
13687     /* longer hold                                                       */
13688     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13689         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13690         nCmailMovesRegistered --;
13691
13692         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13693           {
13694               free(cmailCommentList[lastLoadGameNumber - 1]);
13695               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13696           }
13697     }
13698
13699     if (cmailOldMove == -1) {
13700         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13701         return FALSE;
13702     }
13703
13704     if (currentMove > cmailOldMove + 1) {
13705         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13706         return FALSE;
13707     }
13708
13709     if (currentMove < cmailOldMove) {
13710         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13711         return FALSE;
13712     }
13713
13714     if (forwardMostMove > currentMove) {
13715         /* Silently truncate extra moves */
13716         TruncateGame();
13717     }
13718
13719     if (   (currentMove == cmailOldMove + 1)
13720         || (   (currentMove == cmailOldMove)
13721             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13722                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13723         if (gameInfo.result != GameUnfinished) {
13724             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13725         }
13726
13727         if (commentList[currentMove] != NULL) {
13728             cmailCommentList[lastLoadGameNumber - 1]
13729               = StrSave(commentList[currentMove]);
13730         }
13731         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13732
13733         if (appData.debugMode)
13734           fprintf(debugFP, "Saving %s for game %d\n",
13735                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13736
13737         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13738
13739         f = fopen(string, "w");
13740         if (appData.oldSaveStyle) {
13741             SaveGameOldStyle(f); /* also closes the file */
13742
13743             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13744             f = fopen(string, "w");
13745             SavePosition(f, 0, NULL); /* also closes the file */
13746         } else {
13747             fprintf(f, "{--------------\n");
13748             PrintPosition(f, currentMove);
13749             fprintf(f, "--------------}\n\n");
13750
13751             SaveGame(f, 0, NULL); /* also closes the file*/
13752         }
13753
13754         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13755         nCmailMovesRegistered ++;
13756     } else if (nCmailGames == 1) {
13757         DisplayError(_("You have not made a move yet"), 0);
13758         return FALSE;
13759     }
13760
13761     return TRUE;
13762 }
13763
13764 void
13765 MailMoveEvent ()
13766 {
13767 #if !WIN32
13768     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13769     FILE *commandOutput;
13770     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13771     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13772     int nBuffers;
13773     int i;
13774     int archived;
13775     char *arcDir;
13776
13777     if (! cmailMsgLoaded) {
13778         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13779         return;
13780     }
13781
13782     if (nCmailGames == nCmailResults) {
13783         DisplayError(_("No unfinished games"), 0);
13784         return;
13785     }
13786
13787 #if CMAIL_PROHIBIT_REMAIL
13788     if (cmailMailedMove) {
13789       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);
13790         DisplayError(msg, 0);
13791         return;
13792     }
13793 #endif
13794
13795     if (! (cmailMailedMove || RegisterMove())) return;
13796
13797     if (   cmailMailedMove
13798         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13799       snprintf(string, MSG_SIZ, partCommandString,
13800                appData.debugMode ? " -v" : "", appData.cmailGameName);
13801         commandOutput = popen(string, "r");
13802
13803         if (commandOutput == NULL) {
13804             DisplayError(_("Failed to invoke cmail"), 0);
13805         } else {
13806             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13807                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13808             }
13809             if (nBuffers > 1) {
13810                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13811                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13812                 nBytes = MSG_SIZ - 1;
13813             } else {
13814                 (void) memcpy(msg, buffer, nBytes);
13815             }
13816             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13817
13818             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13819                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13820
13821                 archived = TRUE;
13822                 for (i = 0; i < nCmailGames; i ++) {
13823                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13824                         archived = FALSE;
13825                     }
13826                 }
13827                 if (   archived
13828                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13829                         != NULL)) {
13830                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13831                            arcDir,
13832                            appData.cmailGameName,
13833                            gameInfo.date);
13834                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13835                     cmailMsgLoaded = FALSE;
13836                 }
13837             }
13838
13839             DisplayInformation(msg);
13840             pclose(commandOutput);
13841         }
13842     } else {
13843         if ((*cmailMsg) != '\0') {
13844             DisplayInformation(cmailMsg);
13845         }
13846     }
13847
13848     return;
13849 #endif /* !WIN32 */
13850 }
13851
13852 char *
13853 CmailMsg ()
13854 {
13855 #if WIN32
13856     return NULL;
13857 #else
13858     int  prependComma = 0;
13859     char number[5];
13860     char string[MSG_SIZ];       /* Space for game-list */
13861     int  i;
13862
13863     if (!cmailMsgLoaded) return "";
13864
13865     if (cmailMailedMove) {
13866       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13867     } else {
13868         /* Create a list of games left */
13869       snprintf(string, MSG_SIZ, "[");
13870         for (i = 0; i < nCmailGames; i ++) {
13871             if (! (   cmailMoveRegistered[i]
13872                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13873                 if (prependComma) {
13874                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13875                 } else {
13876                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13877                     prependComma = 1;
13878                 }
13879
13880                 strcat(string, number);
13881             }
13882         }
13883         strcat(string, "]");
13884
13885         if (nCmailMovesRegistered + nCmailResults == 0) {
13886             switch (nCmailGames) {
13887               case 1:
13888                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13889                 break;
13890
13891               case 2:
13892                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13893                 break;
13894
13895               default:
13896                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13897                          nCmailGames);
13898                 break;
13899             }
13900         } else {
13901             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13902               case 1:
13903                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13904                          string);
13905                 break;
13906
13907               case 0:
13908                 if (nCmailResults == nCmailGames) {
13909                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13910                 } else {
13911                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13912                 }
13913                 break;
13914
13915               default:
13916                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13917                          string);
13918             }
13919         }
13920     }
13921     return cmailMsg;
13922 #endif /* WIN32 */
13923 }
13924
13925 void
13926 ResetGameEvent ()
13927 {
13928     if (gameMode == Training)
13929       SetTrainingModeOff();
13930
13931     Reset(TRUE, TRUE);
13932     cmailMsgLoaded = FALSE;
13933     if (appData.icsActive) {
13934       SendToICS(ics_prefix);
13935       SendToICS("refresh\n");
13936     }
13937 }
13938
13939 void
13940 ExitEvent (int status)
13941 {
13942     exiting++;
13943     if (exiting > 2) {
13944       /* Give up on clean exit */
13945       exit(status);
13946     }
13947     if (exiting > 1) {
13948       /* Keep trying for clean exit */
13949       return;
13950     }
13951
13952     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13953
13954     if (telnetISR != NULL) {
13955       RemoveInputSource(telnetISR);
13956     }
13957     if (icsPR != NoProc) {
13958       DestroyChildProcess(icsPR, TRUE);
13959     }
13960
13961     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13962     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13963
13964     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13965     /* make sure this other one finishes before killing it!                  */
13966     if(endingGame) { int count = 0;
13967         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13968         while(endingGame && count++ < 10) DoSleep(1);
13969         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13970     }
13971
13972     /* Kill off chess programs */
13973     if (first.pr != NoProc) {
13974         ExitAnalyzeMode();
13975
13976         DoSleep( appData.delayBeforeQuit );
13977         SendToProgram("quit\n", &first);
13978         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
13979     }
13980     if (second.pr != NoProc) {
13981         DoSleep( appData.delayBeforeQuit );
13982         SendToProgram("quit\n", &second);
13983         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
13984     }
13985     if (first.isr != NULL) {
13986         RemoveInputSource(first.isr);
13987     }
13988     if (second.isr != NULL) {
13989         RemoveInputSource(second.isr);
13990     }
13991
13992     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13993     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13994
13995     ShutDownFrontEnd();
13996     exit(status);
13997 }
13998
13999 void
14000 PauseEngine (ChessProgramState *cps)
14001 {
14002     SendToProgram("pause\n", cps);
14003     cps->pause = 2;
14004 }
14005
14006 void
14007 UnPauseEngine (ChessProgramState *cps)
14008 {
14009     SendToProgram("resume\n", cps);
14010     cps->pause = 1;
14011 }
14012
14013 void
14014 PauseEvent ()
14015 {
14016     if (appData.debugMode)
14017         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14018     if (pausing) {
14019         pausing = FALSE;
14020         ModeHighlight();
14021         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14022             StartClocks();
14023             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14024                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14025                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14026             }
14027             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14028             HandleMachineMove(stashedInputMove, stalledEngine);
14029             stalledEngine = NULL;
14030             return;
14031         }
14032         if (gameMode == MachinePlaysWhite ||
14033             gameMode == TwoMachinesPlay   ||
14034             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14035             if(first.pause)  UnPauseEngine(&first);
14036             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14037             if(second.pause) UnPauseEngine(&second);
14038             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14039             StartClocks();
14040         } else {
14041             DisplayBothClocks();
14042         }
14043         if (gameMode == PlayFromGameFile) {
14044             if (appData.timeDelay >= 0)
14045                 AutoPlayGameLoop();
14046         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14047             Reset(FALSE, TRUE);
14048             SendToICS(ics_prefix);
14049             SendToICS("refresh\n");
14050         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14051             ForwardInner(forwardMostMove);
14052         }
14053         pauseExamInvalid = FALSE;
14054     } else {
14055         switch (gameMode) {
14056           default:
14057             return;
14058           case IcsExamining:
14059             pauseExamForwardMostMove = forwardMostMove;
14060             pauseExamInvalid = FALSE;
14061             /* fall through */
14062           case IcsObserving:
14063           case IcsPlayingWhite:
14064           case IcsPlayingBlack:
14065             pausing = TRUE;
14066             ModeHighlight();
14067             return;
14068           case PlayFromGameFile:
14069             (void) StopLoadGameTimer();
14070             pausing = TRUE;
14071             ModeHighlight();
14072             break;
14073           case BeginningOfGame:
14074             if (appData.icsActive) return;
14075             /* else fall through */
14076           case MachinePlaysWhite:
14077           case MachinePlaysBlack:
14078           case TwoMachinesPlay:
14079             if (forwardMostMove == 0)
14080               return;           /* don't pause if no one has moved */
14081             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14082                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14083                 if(onMove->pause) {           // thinking engine can be paused
14084                     PauseEngine(onMove);      // do it
14085                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14086                         PauseEngine(onMove->other);
14087                     else
14088                         SendToProgram("easy\n", onMove->other);
14089                     StopClocks();
14090                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14091             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14092                 if(first.pause) {
14093                     PauseEngine(&first);
14094                     StopClocks();
14095                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14096             } else { // human on move, pause pondering by either method
14097                 if(first.pause)
14098                     PauseEngine(&first);
14099                 else if(appData.ponderNextMove)
14100                     SendToProgram("easy\n", &first);
14101                 StopClocks();
14102             }
14103             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14104           case AnalyzeMode:
14105             pausing = TRUE;
14106             ModeHighlight();
14107             break;
14108         }
14109     }
14110 }
14111
14112 void
14113 EditCommentEvent ()
14114 {
14115     char title[MSG_SIZ];
14116
14117     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14118       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14119     } else {
14120       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14121                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14122                parseList[currentMove - 1]);
14123     }
14124
14125     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14126 }
14127
14128
14129 void
14130 EditTagsEvent ()
14131 {
14132     char *tags = PGNTags(&gameInfo);
14133     bookUp = FALSE;
14134     EditTagsPopUp(tags, NULL);
14135     free(tags);
14136 }
14137
14138 void
14139 ToggleSecond ()
14140 {
14141   if(second.analyzing) {
14142     SendToProgram("exit\n", &second);
14143     second.analyzing = FALSE;
14144   } else {
14145     if (second.pr == NoProc) StartChessProgram(&second);
14146     InitChessProgram(&second, FALSE);
14147     FeedMovesToProgram(&second, currentMove);
14148
14149     SendToProgram("analyze\n", &second);
14150     second.analyzing = TRUE;
14151   }
14152 }
14153
14154 /* Toggle ShowThinking */
14155 void
14156 ToggleShowThinking()
14157 {
14158   appData.showThinking = !appData.showThinking;
14159   ShowThinkingEvent();
14160 }
14161
14162 int
14163 AnalyzeModeEvent ()
14164 {
14165     char buf[MSG_SIZ];
14166
14167     if (!first.analysisSupport) {
14168       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14169       DisplayError(buf, 0);
14170       return 0;
14171     }
14172     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14173     if (appData.icsActive) {
14174         if (gameMode != IcsObserving) {
14175           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14176             DisplayError(buf, 0);
14177             /* secure check */
14178             if (appData.icsEngineAnalyze) {
14179                 if (appData.debugMode)
14180                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14181                 ExitAnalyzeMode();
14182                 ModeHighlight();
14183             }
14184             return 0;
14185         }
14186         /* if enable, user wants to disable icsEngineAnalyze */
14187         if (appData.icsEngineAnalyze) {
14188                 ExitAnalyzeMode();
14189                 ModeHighlight();
14190                 return 0;
14191         }
14192         appData.icsEngineAnalyze = TRUE;
14193         if (appData.debugMode)
14194             fprintf(debugFP, "ICS engine analyze starting... \n");
14195     }
14196
14197     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14198     if (appData.noChessProgram || gameMode == AnalyzeMode)
14199       return 0;
14200
14201     if (gameMode != AnalyzeFile) {
14202         if (!appData.icsEngineAnalyze) {
14203                EditGameEvent();
14204                if (gameMode != EditGame) return 0;
14205         }
14206         if (!appData.showThinking) ToggleShowThinking();
14207         ResurrectChessProgram();
14208         SendToProgram("analyze\n", &first);
14209         first.analyzing = TRUE;
14210         /*first.maybeThinking = TRUE;*/
14211         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14212         EngineOutputPopUp();
14213     }
14214     if (!appData.icsEngineAnalyze) {
14215         gameMode = AnalyzeMode;
14216         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14217     }
14218     pausing = FALSE;
14219     ModeHighlight();
14220     SetGameInfo();
14221
14222     StartAnalysisClock();
14223     GetTimeMark(&lastNodeCountTime);
14224     lastNodeCount = 0;
14225     return 1;
14226 }
14227
14228 void
14229 AnalyzeFileEvent ()
14230 {
14231     if (appData.noChessProgram || gameMode == AnalyzeFile)
14232       return;
14233
14234     if (!first.analysisSupport) {
14235       char buf[MSG_SIZ];
14236       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14237       DisplayError(buf, 0);
14238       return;
14239     }
14240
14241     if (gameMode != AnalyzeMode) {
14242         keepInfo = 1; // mere annotating should not alter PGN tags
14243         EditGameEvent();
14244         keepInfo = 0;
14245         if (gameMode != EditGame) return;
14246         if (!appData.showThinking) ToggleShowThinking();
14247         ResurrectChessProgram();
14248         SendToProgram("analyze\n", &first);
14249         first.analyzing = TRUE;
14250         /*first.maybeThinking = TRUE;*/
14251         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14252         EngineOutputPopUp();
14253     }
14254     gameMode = AnalyzeFile;
14255     pausing = FALSE;
14256     ModeHighlight();
14257
14258     StartAnalysisClock();
14259     GetTimeMark(&lastNodeCountTime);
14260     lastNodeCount = 0;
14261     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14262     AnalysisPeriodicEvent(1);
14263 }
14264
14265 void
14266 MachineWhiteEvent ()
14267 {
14268     char buf[MSG_SIZ];
14269     char *bookHit = NULL;
14270
14271     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14272       return;
14273
14274
14275     if (gameMode == PlayFromGameFile ||
14276         gameMode == TwoMachinesPlay  ||
14277         gameMode == Training         ||
14278         gameMode == AnalyzeMode      ||
14279         gameMode == EndOfGame)
14280         EditGameEvent();
14281
14282     if (gameMode == EditPosition)
14283         EditPositionDone(TRUE);
14284
14285     if (!WhiteOnMove(currentMove)) {
14286         DisplayError(_("It is not White's turn"), 0);
14287         return;
14288     }
14289
14290     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14291       ExitAnalyzeMode();
14292
14293     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14294         gameMode == AnalyzeFile)
14295         TruncateGame();
14296
14297     ResurrectChessProgram();    /* in case it isn't running */
14298     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14299         gameMode = MachinePlaysWhite;
14300         ResetClocks();
14301     } else
14302     gameMode = MachinePlaysWhite;
14303     pausing = FALSE;
14304     ModeHighlight();
14305     SetGameInfo();
14306     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14307     DisplayTitle(buf);
14308     if (first.sendName) {
14309       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14310       SendToProgram(buf, &first);
14311     }
14312     if (first.sendTime) {
14313       if (first.useColors) {
14314         SendToProgram("black\n", &first); /*gnu kludge*/
14315       }
14316       SendTimeRemaining(&first, TRUE);
14317     }
14318     if (first.useColors) {
14319       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14320     }
14321     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14322     SetMachineThinkingEnables();
14323     first.maybeThinking = TRUE;
14324     StartClocks();
14325     firstMove = FALSE;
14326
14327     if (appData.autoFlipView && !flipView) {
14328       flipView = !flipView;
14329       DrawPosition(FALSE, NULL);
14330       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14331     }
14332
14333     if(bookHit) { // [HGM] book: simulate book reply
14334         static char bookMove[MSG_SIZ]; // a bit generous?
14335
14336         programStats.nodes = programStats.depth = programStats.time =
14337         programStats.score = programStats.got_only_move = 0;
14338         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14339
14340         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14341         strcat(bookMove, bookHit);
14342         HandleMachineMove(bookMove, &first);
14343     }
14344 }
14345
14346 void
14347 MachineBlackEvent ()
14348 {
14349   char buf[MSG_SIZ];
14350   char *bookHit = NULL;
14351
14352     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14353         return;
14354
14355
14356     if (gameMode == PlayFromGameFile ||
14357         gameMode == TwoMachinesPlay  ||
14358         gameMode == Training         ||
14359         gameMode == AnalyzeMode      ||
14360         gameMode == EndOfGame)
14361         EditGameEvent();
14362
14363     if (gameMode == EditPosition)
14364         EditPositionDone(TRUE);
14365
14366     if (WhiteOnMove(currentMove)) {
14367         DisplayError(_("It is not Black's turn"), 0);
14368         return;
14369     }
14370
14371     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14372       ExitAnalyzeMode();
14373
14374     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14375         gameMode == AnalyzeFile)
14376         TruncateGame();
14377
14378     ResurrectChessProgram();    /* in case it isn't running */
14379     gameMode = MachinePlaysBlack;
14380     pausing = FALSE;
14381     ModeHighlight();
14382     SetGameInfo();
14383     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14384     DisplayTitle(buf);
14385     if (first.sendName) {
14386       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14387       SendToProgram(buf, &first);
14388     }
14389     if (first.sendTime) {
14390       if (first.useColors) {
14391         SendToProgram("white\n", &first); /*gnu kludge*/
14392       }
14393       SendTimeRemaining(&first, FALSE);
14394     }
14395     if (first.useColors) {
14396       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14397     }
14398     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14399     SetMachineThinkingEnables();
14400     first.maybeThinking = TRUE;
14401     StartClocks();
14402
14403     if (appData.autoFlipView && flipView) {
14404       flipView = !flipView;
14405       DrawPosition(FALSE, NULL);
14406       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14407     }
14408     if(bookHit) { // [HGM] book: simulate book reply
14409         static char bookMove[MSG_SIZ]; // a bit generous?
14410
14411         programStats.nodes = programStats.depth = programStats.time =
14412         programStats.score = programStats.got_only_move = 0;
14413         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14414
14415         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14416         strcat(bookMove, bookHit);
14417         HandleMachineMove(bookMove, &first);
14418     }
14419 }
14420
14421
14422 void
14423 DisplayTwoMachinesTitle ()
14424 {
14425     char buf[MSG_SIZ];
14426     if (appData.matchGames > 0) {
14427         if(appData.tourneyFile[0]) {
14428           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14429                    gameInfo.white, _("vs."), gameInfo.black,
14430                    nextGame+1, appData.matchGames+1,
14431                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14432         } else
14433         if (first.twoMachinesColor[0] == 'w') {
14434           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14435                    gameInfo.white, _("vs."),  gameInfo.black,
14436                    first.matchWins, second.matchWins,
14437                    matchGame - 1 - (first.matchWins + second.matchWins));
14438         } else {
14439           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14440                    gameInfo.white, _("vs."), gameInfo.black,
14441                    second.matchWins, first.matchWins,
14442                    matchGame - 1 - (first.matchWins + second.matchWins));
14443         }
14444     } else {
14445       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14446     }
14447     DisplayTitle(buf);
14448 }
14449
14450 void
14451 SettingsMenuIfReady ()
14452 {
14453   if (second.lastPing != second.lastPong) {
14454     DisplayMessage("", _("Waiting for second chess program"));
14455     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14456     return;
14457   }
14458   ThawUI();
14459   DisplayMessage("", "");
14460   SettingsPopUp(&second);
14461 }
14462
14463 int
14464 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14465 {
14466     char buf[MSG_SIZ];
14467     if (cps->pr == NoProc) {
14468         StartChessProgram(cps);
14469         if (cps->protocolVersion == 1) {
14470           retry();
14471           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14472         } else {
14473           /* kludge: allow timeout for initial "feature" command */
14474           if(retry != TwoMachinesEventIfReady) FreezeUI();
14475           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14476           DisplayMessage("", buf);
14477           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14478         }
14479         return 1;
14480     }
14481     return 0;
14482 }
14483
14484 void
14485 TwoMachinesEvent P((void))
14486 {
14487     int i;
14488     char buf[MSG_SIZ];
14489     ChessProgramState *onmove;
14490     char *bookHit = NULL;
14491     static int stalling = 0;
14492     TimeMark now;
14493     long wait;
14494
14495     if (appData.noChessProgram) return;
14496
14497     switch (gameMode) {
14498       case TwoMachinesPlay:
14499         return;
14500       case MachinePlaysWhite:
14501       case MachinePlaysBlack:
14502         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14503             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14504             return;
14505         }
14506         /* fall through */
14507       case BeginningOfGame:
14508       case PlayFromGameFile:
14509       case EndOfGame:
14510         EditGameEvent();
14511         if (gameMode != EditGame) return;
14512         break;
14513       case EditPosition:
14514         EditPositionDone(TRUE);
14515         break;
14516       case AnalyzeMode:
14517       case AnalyzeFile:
14518         ExitAnalyzeMode();
14519         break;
14520       case EditGame:
14521       default:
14522         break;
14523     }
14524
14525 //    forwardMostMove = currentMove;
14526     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14527     startingEngine = TRUE;
14528
14529     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14530
14531     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14532     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14533       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14534       return;
14535     }
14536     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14537
14538     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14539                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14540         startingEngine = FALSE;
14541         DisplayError("second engine does not play this", 0);
14542         return;
14543     }
14544
14545     if(!stalling) {
14546       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14547       SendToProgram("force\n", &second);
14548       stalling = 1;
14549       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14550       return;
14551     }
14552     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14553     if(appData.matchPause>10000 || appData.matchPause<10)
14554                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14555     wait = SubtractTimeMarks(&now, &pauseStart);
14556     if(wait < appData.matchPause) {
14557         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14558         return;
14559     }
14560     // we are now committed to starting the game
14561     stalling = 0;
14562     DisplayMessage("", "");
14563     if (startedFromSetupPosition) {
14564         SendBoard(&second, backwardMostMove);
14565     if (appData.debugMode) {
14566         fprintf(debugFP, "Two Machines\n");
14567     }
14568     }
14569     for (i = backwardMostMove; i < forwardMostMove; i++) {
14570         SendMoveToProgram(i, &second);
14571     }
14572
14573     gameMode = TwoMachinesPlay;
14574     pausing = startingEngine = FALSE;
14575     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14576     SetGameInfo();
14577     DisplayTwoMachinesTitle();
14578     firstMove = TRUE;
14579     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14580         onmove = &first;
14581     } else {
14582         onmove = &second;
14583     }
14584     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14585     SendToProgram(first.computerString, &first);
14586     if (first.sendName) {
14587       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14588       SendToProgram(buf, &first);
14589     }
14590     SendToProgram(second.computerString, &second);
14591     if (second.sendName) {
14592       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14593       SendToProgram(buf, &second);
14594     }
14595
14596     ResetClocks();
14597     if (!first.sendTime || !second.sendTime) {
14598         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14599         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14600     }
14601     if (onmove->sendTime) {
14602       if (onmove->useColors) {
14603         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14604       }
14605       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14606     }
14607     if (onmove->useColors) {
14608       SendToProgram(onmove->twoMachinesColor, onmove);
14609     }
14610     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14611 //    SendToProgram("go\n", onmove);
14612     onmove->maybeThinking = TRUE;
14613     SetMachineThinkingEnables();
14614
14615     StartClocks();
14616
14617     if(bookHit) { // [HGM] book: simulate book reply
14618         static char bookMove[MSG_SIZ]; // a bit generous?
14619
14620         programStats.nodes = programStats.depth = programStats.time =
14621         programStats.score = programStats.got_only_move = 0;
14622         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14623
14624         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14625         strcat(bookMove, bookHit);
14626         savedMessage = bookMove; // args for deferred call
14627         savedState = onmove;
14628         ScheduleDelayedEvent(DeferredBookMove, 1);
14629     }
14630 }
14631
14632 void
14633 TrainingEvent ()
14634 {
14635     if (gameMode == Training) {
14636       SetTrainingModeOff();
14637       gameMode = PlayFromGameFile;
14638       DisplayMessage("", _("Training mode off"));
14639     } else {
14640       gameMode = Training;
14641       animateTraining = appData.animate;
14642
14643       /* make sure we are not already at the end of the game */
14644       if (currentMove < forwardMostMove) {
14645         SetTrainingModeOn();
14646         DisplayMessage("", _("Training mode on"));
14647       } else {
14648         gameMode = PlayFromGameFile;
14649         DisplayError(_("Already at end of game"), 0);
14650       }
14651     }
14652     ModeHighlight();
14653 }
14654
14655 void
14656 IcsClientEvent ()
14657 {
14658     if (!appData.icsActive) return;
14659     switch (gameMode) {
14660       case IcsPlayingWhite:
14661       case IcsPlayingBlack:
14662       case IcsObserving:
14663       case IcsIdle:
14664       case BeginningOfGame:
14665       case IcsExamining:
14666         return;
14667
14668       case EditGame:
14669         break;
14670
14671       case EditPosition:
14672         EditPositionDone(TRUE);
14673         break;
14674
14675       case AnalyzeMode:
14676       case AnalyzeFile:
14677         ExitAnalyzeMode();
14678         break;
14679
14680       default:
14681         EditGameEvent();
14682         break;
14683     }
14684
14685     gameMode = IcsIdle;
14686     ModeHighlight();
14687     return;
14688 }
14689
14690 void
14691 EditGameEvent ()
14692 {
14693     int i;
14694
14695     switch (gameMode) {
14696       case Training:
14697         SetTrainingModeOff();
14698         break;
14699       case MachinePlaysWhite:
14700       case MachinePlaysBlack:
14701       case BeginningOfGame:
14702         SendToProgram("force\n", &first);
14703         SetUserThinkingEnables();
14704         break;
14705       case PlayFromGameFile:
14706         (void) StopLoadGameTimer();
14707         if (gameFileFP != NULL) {
14708             gameFileFP = NULL;
14709         }
14710         break;
14711       case EditPosition:
14712         EditPositionDone(TRUE);
14713         break;
14714       case AnalyzeMode:
14715       case AnalyzeFile:
14716         ExitAnalyzeMode();
14717         SendToProgram("force\n", &first);
14718         break;
14719       case TwoMachinesPlay:
14720         GameEnds(EndOfFile, NULL, GE_PLAYER);
14721         ResurrectChessProgram();
14722         SetUserThinkingEnables();
14723         break;
14724       case EndOfGame:
14725         ResurrectChessProgram();
14726         break;
14727       case IcsPlayingBlack:
14728       case IcsPlayingWhite:
14729         DisplayError(_("Warning: You are still playing a game"), 0);
14730         break;
14731       case IcsObserving:
14732         DisplayError(_("Warning: You are still observing a game"), 0);
14733         break;
14734       case IcsExamining:
14735         DisplayError(_("Warning: You are still examining a game"), 0);
14736         break;
14737       case IcsIdle:
14738         break;
14739       case EditGame:
14740       default:
14741         return;
14742     }
14743
14744     pausing = FALSE;
14745     StopClocks();
14746     first.offeredDraw = second.offeredDraw = 0;
14747
14748     if (gameMode == PlayFromGameFile) {
14749         whiteTimeRemaining = timeRemaining[0][currentMove];
14750         blackTimeRemaining = timeRemaining[1][currentMove];
14751         DisplayTitle("");
14752     }
14753
14754     if (gameMode == MachinePlaysWhite ||
14755         gameMode == MachinePlaysBlack ||
14756         gameMode == TwoMachinesPlay ||
14757         gameMode == EndOfGame) {
14758         i = forwardMostMove;
14759         while (i > currentMove) {
14760             SendToProgram("undo\n", &first);
14761             i--;
14762         }
14763         if(!adjustedClock) {
14764         whiteTimeRemaining = timeRemaining[0][currentMove];
14765         blackTimeRemaining = timeRemaining[1][currentMove];
14766         DisplayBothClocks();
14767         }
14768         if (whiteFlag || blackFlag) {
14769             whiteFlag = blackFlag = 0;
14770         }
14771         DisplayTitle("");
14772     }
14773
14774     gameMode = EditGame;
14775     ModeHighlight();
14776     SetGameInfo();
14777 }
14778
14779
14780 void
14781 EditPositionEvent ()
14782 {
14783     if (gameMode == EditPosition) {
14784         EditGameEvent();
14785         return;
14786     }
14787
14788     EditGameEvent();
14789     if (gameMode != EditGame) return;
14790
14791     gameMode = EditPosition;
14792     ModeHighlight();
14793     SetGameInfo();
14794     if (currentMove > 0)
14795       CopyBoard(boards[0], boards[currentMove]);
14796
14797     blackPlaysFirst = !WhiteOnMove(currentMove);
14798     ResetClocks();
14799     currentMove = forwardMostMove = backwardMostMove = 0;
14800     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14801     DisplayMove(-1);
14802     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14803 }
14804
14805 void
14806 ExitAnalyzeMode ()
14807 {
14808     /* [DM] icsEngineAnalyze - possible call from other functions */
14809     if (appData.icsEngineAnalyze) {
14810         appData.icsEngineAnalyze = FALSE;
14811
14812         DisplayMessage("",_("Close ICS engine analyze..."));
14813     }
14814     if (first.analysisSupport && first.analyzing) {
14815       SendToBoth("exit\n");
14816       first.analyzing = second.analyzing = FALSE;
14817     }
14818     thinkOutput[0] = NULLCHAR;
14819 }
14820
14821 void
14822 EditPositionDone (Boolean fakeRights)
14823 {
14824     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14825
14826     startedFromSetupPosition = TRUE;
14827     InitChessProgram(&first, FALSE);
14828     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14829       boards[0][EP_STATUS] = EP_NONE;
14830       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14831       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14832         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14833         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14834       } else boards[0][CASTLING][2] = NoRights;
14835       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14836         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14837         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14838       } else boards[0][CASTLING][5] = NoRights;
14839       if(gameInfo.variant == VariantSChess) {
14840         int i;
14841         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14842           boards[0][VIRGIN][i] = 0;
14843           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14844           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14845         }
14846       }
14847     }
14848     SendToProgram("force\n", &first);
14849     if (blackPlaysFirst) {
14850         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14851         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14852         currentMove = forwardMostMove = backwardMostMove = 1;
14853         CopyBoard(boards[1], boards[0]);
14854     } else {
14855         currentMove = forwardMostMove = backwardMostMove = 0;
14856     }
14857     SendBoard(&first, forwardMostMove);
14858     if (appData.debugMode) {
14859         fprintf(debugFP, "EditPosDone\n");
14860     }
14861     DisplayTitle("");
14862     DisplayMessage("", "");
14863     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14864     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14865     gameMode = EditGame;
14866     ModeHighlight();
14867     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14868     ClearHighlights(); /* [AS] */
14869 }
14870
14871 /* Pause for `ms' milliseconds */
14872 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14873 void
14874 TimeDelay (long ms)
14875 {
14876     TimeMark m1, m2;
14877
14878     GetTimeMark(&m1);
14879     do {
14880         GetTimeMark(&m2);
14881     } while (SubtractTimeMarks(&m2, &m1) < ms);
14882 }
14883
14884 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14885 void
14886 SendMultiLineToICS (char *buf)
14887 {
14888     char temp[MSG_SIZ+1], *p;
14889     int len;
14890
14891     len = strlen(buf);
14892     if (len > MSG_SIZ)
14893       len = MSG_SIZ;
14894
14895     strncpy(temp, buf, len);
14896     temp[len] = 0;
14897
14898     p = temp;
14899     while (*p) {
14900         if (*p == '\n' || *p == '\r')
14901           *p = ' ';
14902         ++p;
14903     }
14904
14905     strcat(temp, "\n");
14906     SendToICS(temp);
14907     SendToPlayer(temp, strlen(temp));
14908 }
14909
14910 void
14911 SetWhiteToPlayEvent ()
14912 {
14913     if (gameMode == EditPosition) {
14914         blackPlaysFirst = FALSE;
14915         DisplayBothClocks();    /* works because currentMove is 0 */
14916     } else if (gameMode == IcsExamining) {
14917         SendToICS(ics_prefix);
14918         SendToICS("tomove white\n");
14919     }
14920 }
14921
14922 void
14923 SetBlackToPlayEvent ()
14924 {
14925     if (gameMode == EditPosition) {
14926         blackPlaysFirst = TRUE;
14927         currentMove = 1;        /* kludge */
14928         DisplayBothClocks();
14929         currentMove = 0;
14930     } else if (gameMode == IcsExamining) {
14931         SendToICS(ics_prefix);
14932         SendToICS("tomove black\n");
14933     }
14934 }
14935
14936 void
14937 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14938 {
14939     char buf[MSG_SIZ];
14940     ChessSquare piece = boards[0][y][x];
14941     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14942     static int lastVariant;
14943
14944     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14945
14946     switch (selection) {
14947       case ClearBoard:
14948         CopyBoard(currentBoard, boards[0]);
14949         CopyBoard(menuBoard, initialPosition);
14950         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14951             SendToICS(ics_prefix);
14952             SendToICS("bsetup clear\n");
14953         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14954             SendToICS(ics_prefix);
14955             SendToICS("clearboard\n");
14956         } else {
14957             int nonEmpty = 0;
14958             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14959                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14960                 for (y = 0; y < BOARD_HEIGHT; y++) {
14961                     if (gameMode == IcsExamining) {
14962                         if (boards[currentMove][y][x] != EmptySquare) {
14963                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14964                                     AAA + x, ONE + y);
14965                             SendToICS(buf);
14966                         }
14967                     } else {
14968                         if(boards[0][y][x] != p) nonEmpty++;
14969                         boards[0][y][x] = p;
14970                     }
14971                 }
14972                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14973             }
14974             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14975                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14976                     ChessSquare p = menuBoard[0][x];
14977                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14978                     p = menuBoard[BOARD_HEIGHT-1][x];
14979                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14980                 }
14981                 DisplayMessage("Clicking clock again restores position", "");
14982                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14983                 if(!nonEmpty) { // asked to clear an empty board
14984                     CopyBoard(boards[0], menuBoard);
14985                 } else
14986                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14987                     CopyBoard(boards[0], initialPosition);
14988                 } else
14989                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14990                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14991                     CopyBoard(boards[0], erasedBoard);
14992                 } else
14993                     CopyBoard(erasedBoard, currentBoard);
14994
14995             }
14996         }
14997         if (gameMode == EditPosition) {
14998             DrawPosition(FALSE, boards[0]);
14999         }
15000         break;
15001
15002       case WhitePlay:
15003         SetWhiteToPlayEvent();
15004         break;
15005
15006       case BlackPlay:
15007         SetBlackToPlayEvent();
15008         break;
15009
15010       case EmptySquare:
15011         if (gameMode == IcsExamining) {
15012             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15013             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15014             SendToICS(buf);
15015         } else {
15016             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15017                 if(x == BOARD_LEFT-2) {
15018                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15019                     boards[0][y][1] = 0;
15020                 } else
15021                 if(x == BOARD_RGHT+1) {
15022                     if(y >= gameInfo.holdingsSize) break;
15023                     boards[0][y][BOARD_WIDTH-2] = 0;
15024                 } else break;
15025             }
15026             boards[0][y][x] = EmptySquare;
15027             DrawPosition(FALSE, boards[0]);
15028         }
15029         break;
15030
15031       case PromotePiece:
15032         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15033            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15034             selection = (ChessSquare) (PROMOTED piece);
15035         } else if(piece == EmptySquare) selection = WhiteSilver;
15036         else selection = (ChessSquare)((int)piece - 1);
15037         goto defaultlabel;
15038
15039       case DemotePiece:
15040         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15041            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15042             selection = (ChessSquare) (DEMOTED piece);
15043         } else if(piece == EmptySquare) selection = BlackSilver;
15044         else selection = (ChessSquare)((int)piece + 1);
15045         goto defaultlabel;
15046
15047       case WhiteQueen:
15048       case BlackQueen:
15049         if(gameInfo.variant == VariantShatranj ||
15050            gameInfo.variant == VariantXiangqi  ||
15051            gameInfo.variant == VariantCourier  ||
15052            gameInfo.variant == VariantASEAN    ||
15053            gameInfo.variant == VariantMakruk     )
15054             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15055         goto defaultlabel;
15056
15057       case WhiteKing:
15058       case BlackKing:
15059         if(gameInfo.variant == VariantXiangqi)
15060             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15061         if(gameInfo.variant == VariantKnightmate)
15062             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15063       default:
15064         defaultlabel:
15065         if (gameMode == IcsExamining) {
15066             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15067             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15068                      PieceToChar(selection), AAA + x, ONE + y);
15069             SendToICS(buf);
15070         } else {
15071             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15072                 int n;
15073                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15074                     n = PieceToNumber(selection - BlackPawn);
15075                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15076                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15077                     boards[0][BOARD_HEIGHT-1-n][1]++;
15078                 } else
15079                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15080                     n = PieceToNumber(selection);
15081                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15082                     boards[0][n][BOARD_WIDTH-1] = selection;
15083                     boards[0][n][BOARD_WIDTH-2]++;
15084                 }
15085             } else
15086             boards[0][y][x] = selection;
15087             DrawPosition(TRUE, boards[0]);
15088             ClearHighlights();
15089             fromX = fromY = -1;
15090         }
15091         break;
15092     }
15093 }
15094
15095
15096 void
15097 DropMenuEvent (ChessSquare selection, int x, int y)
15098 {
15099     ChessMove moveType;
15100
15101     switch (gameMode) {
15102       case IcsPlayingWhite:
15103       case MachinePlaysBlack:
15104         if (!WhiteOnMove(currentMove)) {
15105             DisplayMoveError(_("It is Black's turn"));
15106             return;
15107         }
15108         moveType = WhiteDrop;
15109         break;
15110       case IcsPlayingBlack:
15111       case MachinePlaysWhite:
15112         if (WhiteOnMove(currentMove)) {
15113             DisplayMoveError(_("It is White's turn"));
15114             return;
15115         }
15116         moveType = BlackDrop;
15117         break;
15118       case EditGame:
15119         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15120         break;
15121       default:
15122         return;
15123     }
15124
15125     if (moveType == BlackDrop && selection < BlackPawn) {
15126       selection = (ChessSquare) ((int) selection
15127                                  + (int) BlackPawn - (int) WhitePawn);
15128     }
15129     if (boards[currentMove][y][x] != EmptySquare) {
15130         DisplayMoveError(_("That square is occupied"));
15131         return;
15132     }
15133
15134     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15135 }
15136
15137 void
15138 AcceptEvent ()
15139 {
15140     /* Accept a pending offer of any kind from opponent */
15141
15142     if (appData.icsActive) {
15143         SendToICS(ics_prefix);
15144         SendToICS("accept\n");
15145     } else if (cmailMsgLoaded) {
15146         if (currentMove == cmailOldMove &&
15147             commentList[cmailOldMove] != NULL &&
15148             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15149                    "Black offers a draw" : "White offers a draw")) {
15150             TruncateGame();
15151             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15152             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15153         } else {
15154             DisplayError(_("There is no pending offer on this move"), 0);
15155             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15156         }
15157     } else {
15158         /* Not used for offers from chess program */
15159     }
15160 }
15161
15162 void
15163 DeclineEvent ()
15164 {
15165     /* Decline a pending offer of any kind from opponent */
15166
15167     if (appData.icsActive) {
15168         SendToICS(ics_prefix);
15169         SendToICS("decline\n");
15170     } else if (cmailMsgLoaded) {
15171         if (currentMove == cmailOldMove &&
15172             commentList[cmailOldMove] != NULL &&
15173             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15174                    "Black offers a draw" : "White offers a draw")) {
15175 #ifdef NOTDEF
15176             AppendComment(cmailOldMove, "Draw declined", TRUE);
15177             DisplayComment(cmailOldMove - 1, "Draw declined");
15178 #endif /*NOTDEF*/
15179         } else {
15180             DisplayError(_("There is no pending offer on this move"), 0);
15181         }
15182     } else {
15183         /* Not used for offers from chess program */
15184     }
15185 }
15186
15187 void
15188 RematchEvent ()
15189 {
15190     /* Issue ICS rematch command */
15191     if (appData.icsActive) {
15192         SendToICS(ics_prefix);
15193         SendToICS("rematch\n");
15194     }
15195 }
15196
15197 void
15198 CallFlagEvent ()
15199 {
15200     /* Call your opponent's flag (claim a win on time) */
15201     if (appData.icsActive) {
15202         SendToICS(ics_prefix);
15203         SendToICS("flag\n");
15204     } else {
15205         switch (gameMode) {
15206           default:
15207             return;
15208           case MachinePlaysWhite:
15209             if (whiteFlag) {
15210                 if (blackFlag)
15211                   GameEnds(GameIsDrawn, "Both players ran out of time",
15212                            GE_PLAYER);
15213                 else
15214                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15215             } else {
15216                 DisplayError(_("Your opponent is not out of time"), 0);
15217             }
15218             break;
15219           case MachinePlaysBlack:
15220             if (blackFlag) {
15221                 if (whiteFlag)
15222                   GameEnds(GameIsDrawn, "Both players ran out of time",
15223                            GE_PLAYER);
15224                 else
15225                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15226             } else {
15227                 DisplayError(_("Your opponent is not out of time"), 0);
15228             }
15229             break;
15230         }
15231     }
15232 }
15233
15234 void
15235 ClockClick (int which)
15236 {       // [HGM] code moved to back-end from winboard.c
15237         if(which) { // black clock
15238           if (gameMode == EditPosition || gameMode == IcsExamining) {
15239             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15240             SetBlackToPlayEvent();
15241           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15242           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15243           } else if (shiftKey) {
15244             AdjustClock(which, -1);
15245           } else if (gameMode == IcsPlayingWhite ||
15246                      gameMode == MachinePlaysBlack) {
15247             CallFlagEvent();
15248           }
15249         } else { // white clock
15250           if (gameMode == EditPosition || gameMode == IcsExamining) {
15251             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15252             SetWhiteToPlayEvent();
15253           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15254           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15255           } else if (shiftKey) {
15256             AdjustClock(which, -1);
15257           } else if (gameMode == IcsPlayingBlack ||
15258                    gameMode == MachinePlaysWhite) {
15259             CallFlagEvent();
15260           }
15261         }
15262 }
15263
15264 void
15265 DrawEvent ()
15266 {
15267     /* Offer draw or accept pending draw offer from opponent */
15268
15269     if (appData.icsActive) {
15270         /* Note: tournament rules require draw offers to be
15271            made after you make your move but before you punch
15272            your clock.  Currently ICS doesn't let you do that;
15273            instead, you immediately punch your clock after making
15274            a move, but you can offer a draw at any time. */
15275
15276         SendToICS(ics_prefix);
15277         SendToICS("draw\n");
15278         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15279     } else if (cmailMsgLoaded) {
15280         if (currentMove == cmailOldMove &&
15281             commentList[cmailOldMove] != NULL &&
15282             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15283                    "Black offers a draw" : "White offers a draw")) {
15284             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15285             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15286         } else if (currentMove == cmailOldMove + 1) {
15287             char *offer = WhiteOnMove(cmailOldMove) ?
15288               "White offers a draw" : "Black offers a draw";
15289             AppendComment(currentMove, offer, TRUE);
15290             DisplayComment(currentMove - 1, offer);
15291             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15292         } else {
15293             DisplayError(_("You must make your move before offering a draw"), 0);
15294             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15295         }
15296     } else if (first.offeredDraw) {
15297         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15298     } else {
15299         if (first.sendDrawOffers) {
15300             SendToProgram("draw\n", &first);
15301             userOfferedDraw = TRUE;
15302         }
15303     }
15304 }
15305
15306 void
15307 AdjournEvent ()
15308 {
15309     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15310
15311     if (appData.icsActive) {
15312         SendToICS(ics_prefix);
15313         SendToICS("adjourn\n");
15314     } else {
15315         /* Currently GNU Chess doesn't offer or accept Adjourns */
15316     }
15317 }
15318
15319
15320 void
15321 AbortEvent ()
15322 {
15323     /* Offer Abort or accept pending Abort offer from opponent */
15324
15325     if (appData.icsActive) {
15326         SendToICS(ics_prefix);
15327         SendToICS("abort\n");
15328     } else {
15329         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15330     }
15331 }
15332
15333 void
15334 ResignEvent ()
15335 {
15336     /* Resign.  You can do this even if it's not your turn. */
15337
15338     if (appData.icsActive) {
15339         SendToICS(ics_prefix);
15340         SendToICS("resign\n");
15341     } else {
15342         switch (gameMode) {
15343           case MachinePlaysWhite:
15344             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15345             break;
15346           case MachinePlaysBlack:
15347             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15348             break;
15349           case EditGame:
15350             if (cmailMsgLoaded) {
15351                 TruncateGame();
15352                 if (WhiteOnMove(cmailOldMove)) {
15353                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15354                 } else {
15355                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15356                 }
15357                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15358             }
15359             break;
15360           default:
15361             break;
15362         }
15363     }
15364 }
15365
15366
15367 void
15368 StopObservingEvent ()
15369 {
15370     /* Stop observing current games */
15371     SendToICS(ics_prefix);
15372     SendToICS("unobserve\n");
15373 }
15374
15375 void
15376 StopExaminingEvent ()
15377 {
15378     /* Stop observing current game */
15379     SendToICS(ics_prefix);
15380     SendToICS("unexamine\n");
15381 }
15382
15383 void
15384 ForwardInner (int target)
15385 {
15386     int limit; int oldSeekGraphUp = seekGraphUp;
15387
15388     if (appData.debugMode)
15389         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15390                 target, currentMove, forwardMostMove);
15391
15392     if (gameMode == EditPosition)
15393       return;
15394
15395     seekGraphUp = FALSE;
15396     MarkTargetSquares(1);
15397
15398     if (gameMode == PlayFromGameFile && !pausing)
15399       PauseEvent();
15400
15401     if (gameMode == IcsExamining && pausing)
15402       limit = pauseExamForwardMostMove;
15403     else
15404       limit = forwardMostMove;
15405
15406     if (target > limit) target = limit;
15407
15408     if (target > 0 && moveList[target - 1][0]) {
15409         int fromX, fromY, toX, toY;
15410         toX = moveList[target - 1][2] - AAA;
15411         toY = moveList[target - 1][3] - ONE;
15412         if (moveList[target - 1][1] == '@') {
15413             if (appData.highlightLastMove) {
15414                 SetHighlights(-1, -1, toX, toY);
15415             }
15416         } else {
15417             fromX = moveList[target - 1][0] - AAA;
15418             fromY = moveList[target - 1][1] - ONE;
15419             if (target == currentMove + 1) {
15420                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15421             }
15422             if (appData.highlightLastMove) {
15423                 SetHighlights(fromX, fromY, toX, toY);
15424             }
15425         }
15426     }
15427     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15428         gameMode == Training || gameMode == PlayFromGameFile ||
15429         gameMode == AnalyzeFile) {
15430         while (currentMove < target) {
15431             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15432             SendMoveToProgram(currentMove++, &first);
15433         }
15434     } else {
15435         currentMove = target;
15436     }
15437
15438     if (gameMode == EditGame || gameMode == EndOfGame) {
15439         whiteTimeRemaining = timeRemaining[0][currentMove];
15440         blackTimeRemaining = timeRemaining[1][currentMove];
15441     }
15442     DisplayBothClocks();
15443     DisplayMove(currentMove - 1);
15444     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15445     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15446     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15447         DisplayComment(currentMove - 1, commentList[currentMove]);
15448     }
15449     ClearMap(); // [HGM] exclude: invalidate map
15450 }
15451
15452
15453 void
15454 ForwardEvent ()
15455 {
15456     if (gameMode == IcsExamining && !pausing) {
15457         SendToICS(ics_prefix);
15458         SendToICS("forward\n");
15459     } else {
15460         ForwardInner(currentMove + 1);
15461     }
15462 }
15463
15464 void
15465 ToEndEvent ()
15466 {
15467     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15468         /* to optimze, we temporarily turn off analysis mode while we feed
15469          * the remaining moves to the engine. Otherwise we get analysis output
15470          * after each move.
15471          */
15472         if (first.analysisSupport) {
15473           SendToProgram("exit\nforce\n", &first);
15474           first.analyzing = FALSE;
15475         }
15476     }
15477
15478     if (gameMode == IcsExamining && !pausing) {
15479         SendToICS(ics_prefix);
15480         SendToICS("forward 999999\n");
15481     } else {
15482         ForwardInner(forwardMostMove);
15483     }
15484
15485     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15486         /* we have fed all the moves, so reactivate analysis mode */
15487         SendToProgram("analyze\n", &first);
15488         first.analyzing = TRUE;
15489         /*first.maybeThinking = TRUE;*/
15490         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15491     }
15492 }
15493
15494 void
15495 BackwardInner (int target)
15496 {
15497     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15498
15499     if (appData.debugMode)
15500         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15501                 target, currentMove, forwardMostMove);
15502
15503     if (gameMode == EditPosition) return;
15504     seekGraphUp = FALSE;
15505     MarkTargetSquares(1);
15506     if (currentMove <= backwardMostMove) {
15507         ClearHighlights();
15508         DrawPosition(full_redraw, boards[currentMove]);
15509         return;
15510     }
15511     if (gameMode == PlayFromGameFile && !pausing)
15512       PauseEvent();
15513
15514     if (moveList[target][0]) {
15515         int fromX, fromY, toX, toY;
15516         toX = moveList[target][2] - AAA;
15517         toY = moveList[target][3] - ONE;
15518         if (moveList[target][1] == '@') {
15519             if (appData.highlightLastMove) {
15520                 SetHighlights(-1, -1, toX, toY);
15521             }
15522         } else {
15523             fromX = moveList[target][0] - AAA;
15524             fromY = moveList[target][1] - ONE;
15525             if (target == currentMove - 1) {
15526                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15527             }
15528             if (appData.highlightLastMove) {
15529                 SetHighlights(fromX, fromY, toX, toY);
15530             }
15531         }
15532     }
15533     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15534         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15535         while (currentMove > target) {
15536             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15537                 // null move cannot be undone. Reload program with move history before it.
15538                 int i;
15539                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15540                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15541                 }
15542                 SendBoard(&first, i);
15543               if(second.analyzing) SendBoard(&second, i);
15544                 for(currentMove=i; currentMove<target; currentMove++) {
15545                     SendMoveToProgram(currentMove, &first);
15546                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15547                 }
15548                 break;
15549             }
15550             SendToBoth("undo\n");
15551             currentMove--;
15552         }
15553     } else {
15554         currentMove = target;
15555     }
15556
15557     if (gameMode == EditGame || gameMode == EndOfGame) {
15558         whiteTimeRemaining = timeRemaining[0][currentMove];
15559         blackTimeRemaining = timeRemaining[1][currentMove];
15560     }
15561     DisplayBothClocks();
15562     DisplayMove(currentMove - 1);
15563     DrawPosition(full_redraw, boards[currentMove]);
15564     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15565     // [HGM] PV info: routine tests if comment empty
15566     DisplayComment(currentMove - 1, commentList[currentMove]);
15567     ClearMap(); // [HGM] exclude: invalidate map
15568 }
15569
15570 void
15571 BackwardEvent ()
15572 {
15573     if (gameMode == IcsExamining && !pausing) {
15574         SendToICS(ics_prefix);
15575         SendToICS("backward\n");
15576     } else {
15577         BackwardInner(currentMove - 1);
15578     }
15579 }
15580
15581 void
15582 ToStartEvent ()
15583 {
15584     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15585         /* to optimize, we temporarily turn off analysis mode while we undo
15586          * all the moves. Otherwise we get analysis output after each undo.
15587          */
15588         if (first.analysisSupport) {
15589           SendToProgram("exit\nforce\n", &first);
15590           first.analyzing = FALSE;
15591         }
15592     }
15593
15594     if (gameMode == IcsExamining && !pausing) {
15595         SendToICS(ics_prefix);
15596         SendToICS("backward 999999\n");
15597     } else {
15598         BackwardInner(backwardMostMove);
15599     }
15600
15601     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15602         /* we have fed all the moves, so reactivate analysis mode */
15603         SendToProgram("analyze\n", &first);
15604         first.analyzing = TRUE;
15605         /*first.maybeThinking = TRUE;*/
15606         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15607     }
15608 }
15609
15610 void
15611 ToNrEvent (int to)
15612 {
15613   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15614   if (to >= forwardMostMove) to = forwardMostMove;
15615   if (to <= backwardMostMove) to = backwardMostMove;
15616   if (to < currentMove) {
15617     BackwardInner(to);
15618   } else {
15619     ForwardInner(to);
15620   }
15621 }
15622
15623 void
15624 RevertEvent (Boolean annotate)
15625 {
15626     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15627         return;
15628     }
15629     if (gameMode != IcsExamining) {
15630         DisplayError(_("You are not examining a game"), 0);
15631         return;
15632     }
15633     if (pausing) {
15634         DisplayError(_("You can't revert while pausing"), 0);
15635         return;
15636     }
15637     SendToICS(ics_prefix);
15638     SendToICS("revert\n");
15639 }
15640
15641 void
15642 RetractMoveEvent ()
15643 {
15644     switch (gameMode) {
15645       case MachinePlaysWhite:
15646       case MachinePlaysBlack:
15647         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15648             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15649             return;
15650         }
15651         if (forwardMostMove < 2) return;
15652         currentMove = forwardMostMove = forwardMostMove - 2;
15653         whiteTimeRemaining = timeRemaining[0][currentMove];
15654         blackTimeRemaining = timeRemaining[1][currentMove];
15655         DisplayBothClocks();
15656         DisplayMove(currentMove - 1);
15657         ClearHighlights();/*!! could figure this out*/
15658         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15659         SendToProgram("remove\n", &first);
15660         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15661         break;
15662
15663       case BeginningOfGame:
15664       default:
15665         break;
15666
15667       case IcsPlayingWhite:
15668       case IcsPlayingBlack:
15669         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15670             SendToICS(ics_prefix);
15671             SendToICS("takeback 2\n");
15672         } else {
15673             SendToICS(ics_prefix);
15674             SendToICS("takeback 1\n");
15675         }
15676         break;
15677     }
15678 }
15679
15680 void
15681 MoveNowEvent ()
15682 {
15683     ChessProgramState *cps;
15684
15685     switch (gameMode) {
15686       case MachinePlaysWhite:
15687         if (!WhiteOnMove(forwardMostMove)) {
15688             DisplayError(_("It is your turn"), 0);
15689             return;
15690         }
15691         cps = &first;
15692         break;
15693       case MachinePlaysBlack:
15694         if (WhiteOnMove(forwardMostMove)) {
15695             DisplayError(_("It is your turn"), 0);
15696             return;
15697         }
15698         cps = &first;
15699         break;
15700       case TwoMachinesPlay:
15701         if (WhiteOnMove(forwardMostMove) ==
15702             (first.twoMachinesColor[0] == 'w')) {
15703             cps = &first;
15704         } else {
15705             cps = &second;
15706         }
15707         break;
15708       case BeginningOfGame:
15709       default:
15710         return;
15711     }
15712     SendToProgram("?\n", cps);
15713 }
15714
15715 void
15716 TruncateGameEvent ()
15717 {
15718     EditGameEvent();
15719     if (gameMode != EditGame) return;
15720     TruncateGame();
15721 }
15722
15723 void
15724 TruncateGame ()
15725 {
15726     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15727     if (forwardMostMove > currentMove) {
15728         if (gameInfo.resultDetails != NULL) {
15729             free(gameInfo.resultDetails);
15730             gameInfo.resultDetails = NULL;
15731             gameInfo.result = GameUnfinished;
15732         }
15733         forwardMostMove = currentMove;
15734         HistorySet(parseList, backwardMostMove, forwardMostMove,
15735                    currentMove-1);
15736     }
15737 }
15738
15739 void
15740 HintEvent ()
15741 {
15742     if (appData.noChessProgram) return;
15743     switch (gameMode) {
15744       case MachinePlaysWhite:
15745         if (WhiteOnMove(forwardMostMove)) {
15746             DisplayError(_("Wait until your turn."), 0);
15747             return;
15748         }
15749         break;
15750       case BeginningOfGame:
15751       case MachinePlaysBlack:
15752         if (!WhiteOnMove(forwardMostMove)) {
15753             DisplayError(_("Wait until your turn."), 0);
15754             return;
15755         }
15756         break;
15757       default:
15758         DisplayError(_("No hint available"), 0);
15759         return;
15760     }
15761     SendToProgram("hint\n", &first);
15762     hintRequested = TRUE;
15763 }
15764
15765 void
15766 CreateBookEvent ()
15767 {
15768     ListGame * lg = (ListGame *) gameList.head;
15769     FILE *f, *g;
15770     int nItem;
15771     static int secondTime = FALSE;
15772
15773     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15774         DisplayError(_("Game list not loaded or empty"), 0);
15775         return;
15776     }
15777
15778     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15779         fclose(g);
15780         secondTime++;
15781         DisplayNote(_("Book file exists! Try again for overwrite."));
15782         return;
15783     }
15784
15785     creatingBook = TRUE;
15786     secondTime = FALSE;
15787
15788     /* Get list size */
15789     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15790         LoadGame(f, nItem, "", TRUE);
15791         AddGameToBook(TRUE);
15792         lg = (ListGame *) lg->node.succ;
15793     }
15794
15795     creatingBook = FALSE;
15796     FlushBook();
15797 }
15798
15799 void
15800 BookEvent ()
15801 {
15802     if (appData.noChessProgram) return;
15803     switch (gameMode) {
15804       case MachinePlaysWhite:
15805         if (WhiteOnMove(forwardMostMove)) {
15806             DisplayError(_("Wait until your turn."), 0);
15807             return;
15808         }
15809         break;
15810       case BeginningOfGame:
15811       case MachinePlaysBlack:
15812         if (!WhiteOnMove(forwardMostMove)) {
15813             DisplayError(_("Wait until your turn."), 0);
15814             return;
15815         }
15816         break;
15817       case EditPosition:
15818         EditPositionDone(TRUE);
15819         break;
15820       case TwoMachinesPlay:
15821         return;
15822       default:
15823         break;
15824     }
15825     SendToProgram("bk\n", &first);
15826     bookOutput[0] = NULLCHAR;
15827     bookRequested = TRUE;
15828 }
15829
15830 void
15831 AboutGameEvent ()
15832 {
15833     char *tags = PGNTags(&gameInfo);
15834     TagsPopUp(tags, CmailMsg());
15835     free(tags);
15836 }
15837
15838 /* end button procedures */
15839
15840 void
15841 PrintPosition (FILE *fp, int move)
15842 {
15843     int i, j;
15844
15845     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15846         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15847             char c = PieceToChar(boards[move][i][j]);
15848             fputc(c == 'x' ? '.' : c, fp);
15849             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15850         }
15851     }
15852     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15853       fprintf(fp, "white to play\n");
15854     else
15855       fprintf(fp, "black to play\n");
15856 }
15857
15858 void
15859 PrintOpponents (FILE *fp)
15860 {
15861     if (gameInfo.white != NULL) {
15862         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15863     } else {
15864         fprintf(fp, "\n");
15865     }
15866 }
15867
15868 /* Find last component of program's own name, using some heuristics */
15869 void
15870 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15871 {
15872     char *p, *q, c;
15873     int local = (strcmp(host, "localhost") == 0);
15874     while (!local && (p = strchr(prog, ';')) != NULL) {
15875         p++;
15876         while (*p == ' ') p++;
15877         prog = p;
15878     }
15879     if (*prog == '"' || *prog == '\'') {
15880         q = strchr(prog + 1, *prog);
15881     } else {
15882         q = strchr(prog, ' ');
15883     }
15884     if (q == NULL) q = prog + strlen(prog);
15885     p = q;
15886     while (p >= prog && *p != '/' && *p != '\\') p--;
15887     p++;
15888     if(p == prog && *p == '"') p++;
15889     c = *q; *q = 0;
15890     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15891     memcpy(buf, p, q - p);
15892     buf[q - p] = NULLCHAR;
15893     if (!local) {
15894         strcat(buf, "@");
15895         strcat(buf, host);
15896     }
15897 }
15898
15899 char *
15900 TimeControlTagValue ()
15901 {
15902     char buf[MSG_SIZ];
15903     if (!appData.clockMode) {
15904       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15905     } else if (movesPerSession > 0) {
15906       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15907     } else if (timeIncrement == 0) {
15908       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15909     } else {
15910       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15911     }
15912     return StrSave(buf);
15913 }
15914
15915 void
15916 SetGameInfo ()
15917 {
15918     /* This routine is used only for certain modes */
15919     VariantClass v = gameInfo.variant;
15920     ChessMove r = GameUnfinished;
15921     char *p = NULL;
15922
15923     if(keepInfo) return;
15924
15925     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15926         r = gameInfo.result;
15927         p = gameInfo.resultDetails;
15928         gameInfo.resultDetails = NULL;
15929     }
15930     ClearGameInfo(&gameInfo);
15931     gameInfo.variant = v;
15932
15933     switch (gameMode) {
15934       case MachinePlaysWhite:
15935         gameInfo.event = StrSave( appData.pgnEventHeader );
15936         gameInfo.site = StrSave(HostName());
15937         gameInfo.date = PGNDate();
15938         gameInfo.round = StrSave("-");
15939         gameInfo.white = StrSave(first.tidy);
15940         gameInfo.black = StrSave(UserName());
15941         gameInfo.timeControl = TimeControlTagValue();
15942         break;
15943
15944       case MachinePlaysBlack:
15945         gameInfo.event = StrSave( appData.pgnEventHeader );
15946         gameInfo.site = StrSave(HostName());
15947         gameInfo.date = PGNDate();
15948         gameInfo.round = StrSave("-");
15949         gameInfo.white = StrSave(UserName());
15950         gameInfo.black = StrSave(first.tidy);
15951         gameInfo.timeControl = TimeControlTagValue();
15952         break;
15953
15954       case TwoMachinesPlay:
15955         gameInfo.event = StrSave( appData.pgnEventHeader );
15956         gameInfo.site = StrSave(HostName());
15957         gameInfo.date = PGNDate();
15958         if (roundNr > 0) {
15959             char buf[MSG_SIZ];
15960             snprintf(buf, MSG_SIZ, "%d", roundNr);
15961             gameInfo.round = StrSave(buf);
15962         } else {
15963             gameInfo.round = StrSave("-");
15964         }
15965         if (first.twoMachinesColor[0] == 'w') {
15966             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15967             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15968         } else {
15969             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15970             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15971         }
15972         gameInfo.timeControl = TimeControlTagValue();
15973         break;
15974
15975       case EditGame:
15976         gameInfo.event = StrSave("Edited game");
15977         gameInfo.site = StrSave(HostName());
15978         gameInfo.date = PGNDate();
15979         gameInfo.round = StrSave("-");
15980         gameInfo.white = StrSave("-");
15981         gameInfo.black = StrSave("-");
15982         gameInfo.result = r;
15983         gameInfo.resultDetails = p;
15984         break;
15985
15986       case EditPosition:
15987         gameInfo.event = StrSave("Edited position");
15988         gameInfo.site = StrSave(HostName());
15989         gameInfo.date = PGNDate();
15990         gameInfo.round = StrSave("-");
15991         gameInfo.white = StrSave("-");
15992         gameInfo.black = StrSave("-");
15993         break;
15994
15995       case IcsPlayingWhite:
15996       case IcsPlayingBlack:
15997       case IcsObserving:
15998       case IcsExamining:
15999         break;
16000
16001       case PlayFromGameFile:
16002         gameInfo.event = StrSave("Game from non-PGN file");
16003         gameInfo.site = StrSave(HostName());
16004         gameInfo.date = PGNDate();
16005         gameInfo.round = StrSave("-");
16006         gameInfo.white = StrSave("?");
16007         gameInfo.black = StrSave("?");
16008         break;
16009
16010       default:
16011         break;
16012     }
16013 }
16014
16015 void
16016 ReplaceComment (int index, char *text)
16017 {
16018     int len;
16019     char *p;
16020     float score;
16021
16022     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16023        pvInfoList[index-1].depth == len &&
16024        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16025        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16026     while (*text == '\n') text++;
16027     len = strlen(text);
16028     while (len > 0 && text[len - 1] == '\n') len--;
16029
16030     if (commentList[index] != NULL)
16031       free(commentList[index]);
16032
16033     if (len == 0) {
16034         commentList[index] = NULL;
16035         return;
16036     }
16037   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16038       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16039       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16040     commentList[index] = (char *) malloc(len + 2);
16041     strncpy(commentList[index], text, len);
16042     commentList[index][len] = '\n';
16043     commentList[index][len + 1] = NULLCHAR;
16044   } else {
16045     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16046     char *p;
16047     commentList[index] = (char *) malloc(len + 7);
16048     safeStrCpy(commentList[index], "{\n", 3);
16049     safeStrCpy(commentList[index]+2, text, len+1);
16050     commentList[index][len+2] = NULLCHAR;
16051     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16052     strcat(commentList[index], "\n}\n");
16053   }
16054 }
16055
16056 void
16057 CrushCRs (char *text)
16058 {
16059   char *p = text;
16060   char *q = text;
16061   char ch;
16062
16063   do {
16064     ch = *p++;
16065     if (ch == '\r') continue;
16066     *q++ = ch;
16067   } while (ch != '\0');
16068 }
16069
16070 void
16071 AppendComment (int index, char *text, Boolean addBraces)
16072 /* addBraces  tells if we should add {} */
16073 {
16074     int oldlen, len;
16075     char *old;
16076
16077 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16078     if(addBraces == 3) addBraces = 0; else // force appending literally
16079     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16080
16081     CrushCRs(text);
16082     while (*text == '\n') text++;
16083     len = strlen(text);
16084     while (len > 0 && text[len - 1] == '\n') len--;
16085     text[len] = NULLCHAR;
16086
16087     if (len == 0) return;
16088
16089     if (commentList[index] != NULL) {
16090       Boolean addClosingBrace = addBraces;
16091         old = commentList[index];
16092         oldlen = strlen(old);
16093         while(commentList[index][oldlen-1] ==  '\n')
16094           commentList[index][--oldlen] = NULLCHAR;
16095         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16096         safeStrCpy(commentList[index], old, oldlen + len + 6);
16097         free(old);
16098         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16099         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16100           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16101           while (*text == '\n') { text++; len--; }
16102           commentList[index][--oldlen] = NULLCHAR;
16103       }
16104         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16105         else          strcat(commentList[index], "\n");
16106         strcat(commentList[index], text);
16107         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16108         else          strcat(commentList[index], "\n");
16109     } else {
16110         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16111         if(addBraces)
16112           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16113         else commentList[index][0] = NULLCHAR;
16114         strcat(commentList[index], text);
16115         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16116         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16117     }
16118 }
16119
16120 static char *
16121 FindStr (char * text, char * sub_text)
16122 {
16123     char * result = strstr( text, sub_text );
16124
16125     if( result != NULL ) {
16126         result += strlen( sub_text );
16127     }
16128
16129     return result;
16130 }
16131
16132 /* [AS] Try to extract PV info from PGN comment */
16133 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16134 char *
16135 GetInfoFromComment (int index, char * text)
16136 {
16137     char * sep = text, *p;
16138
16139     if( text != NULL && index > 0 ) {
16140         int score = 0;
16141         int depth = 0;
16142         int time = -1, sec = 0, deci;
16143         char * s_eval = FindStr( text, "[%eval " );
16144         char * s_emt = FindStr( text, "[%emt " );
16145 #if 0
16146         if( s_eval != NULL || s_emt != NULL ) {
16147 #else
16148         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16149 #endif
16150             /* New style */
16151             char delim;
16152
16153             if( s_eval != NULL ) {
16154                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16155                     return text;
16156                 }
16157
16158                 if( delim != ']' ) {
16159                     return text;
16160                 }
16161             }
16162
16163             if( s_emt != NULL ) {
16164             }
16165                 return text;
16166         }
16167         else {
16168             /* We expect something like: [+|-]nnn.nn/dd */
16169             int score_lo = 0;
16170
16171             if(*text != '{') return text; // [HGM] braces: must be normal comment
16172
16173             sep = strchr( text, '/' );
16174             if( sep == NULL || sep < (text+4) ) {
16175                 return text;
16176             }
16177
16178             p = text;
16179             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16180             if(p[1] == '(') { // comment starts with PV
16181                p = strchr(p, ')'); // locate end of PV
16182                if(p == NULL || sep < p+5) return text;
16183                // at this point we have something like "{(.*) +0.23/6 ..."
16184                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16185                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16186                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16187             }
16188             time = -1; sec = -1; deci = -1;
16189             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16190                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16191                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16192                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16193                 return text;
16194             }
16195
16196             if( score_lo < 0 || score_lo >= 100 ) {
16197                 return text;
16198             }
16199
16200             if(sec >= 0) time = 600*time + 10*sec; else
16201             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16202
16203             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16204
16205             /* [HGM] PV time: now locate end of PV info */
16206             while( *++sep >= '0' && *sep <= '9'); // strip depth
16207             if(time >= 0)
16208             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16209             if(sec >= 0)
16210             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16211             if(deci >= 0)
16212             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16213             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16214         }
16215
16216         if( depth <= 0 ) {
16217             return text;
16218         }
16219
16220         if( time < 0 ) {
16221             time = -1;
16222         }
16223
16224         pvInfoList[index-1].depth = depth;
16225         pvInfoList[index-1].score = score;
16226         pvInfoList[index-1].time  = 10*time; // centi-sec
16227         if(*sep == '}') *sep = 0; else *--sep = '{';
16228         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16229     }
16230     return sep;
16231 }
16232
16233 void
16234 SendToProgram (char *message, ChessProgramState *cps)
16235 {
16236     int count, outCount, error;
16237     char buf[MSG_SIZ];
16238
16239     if (cps->pr == NoProc) return;
16240     Attention(cps);
16241
16242     if (appData.debugMode) {
16243         TimeMark now;
16244         GetTimeMark(&now);
16245         fprintf(debugFP, "%ld >%-6s: %s",
16246                 SubtractTimeMarks(&now, &programStartTime),
16247                 cps->which, message);
16248         if(serverFP)
16249             fprintf(serverFP, "%ld >%-6s: %s",
16250                 SubtractTimeMarks(&now, &programStartTime),
16251                 cps->which, message), fflush(serverFP);
16252     }
16253
16254     count = strlen(message);
16255     outCount = OutputToProcess(cps->pr, message, count, &error);
16256     if (outCount < count && !exiting
16257                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16258       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16259       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16260         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16261             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16262                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16263                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16264                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16265             } else {
16266                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16267                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16268                 gameInfo.result = res;
16269             }
16270             gameInfo.resultDetails = StrSave(buf);
16271         }
16272         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16273         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16274     }
16275 }
16276
16277 void
16278 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16279 {
16280     char *end_str;
16281     char buf[MSG_SIZ];
16282     ChessProgramState *cps = (ChessProgramState *)closure;
16283
16284     if (isr != cps->isr) return; /* Killed intentionally */
16285     if (count <= 0) {
16286         if (count == 0) {
16287             RemoveInputSource(cps->isr);
16288             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16289                     _(cps->which), cps->program);
16290             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16291             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16292                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16293                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16294                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16295                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16296                 } else {
16297                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16298                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16299                     gameInfo.result = res;
16300                 }
16301                 gameInfo.resultDetails = StrSave(buf);
16302             }
16303             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16304             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16305         } else {
16306             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16307                     _(cps->which), cps->program);
16308             RemoveInputSource(cps->isr);
16309
16310             /* [AS] Program is misbehaving badly... kill it */
16311             if( count == -2 ) {
16312                 DestroyChildProcess( cps->pr, 9 );
16313                 cps->pr = NoProc;
16314             }
16315
16316             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16317         }
16318         return;
16319     }
16320
16321     if ((end_str = strchr(message, '\r')) != NULL)
16322       *end_str = NULLCHAR;
16323     if ((end_str = strchr(message, '\n')) != NULL)
16324       *end_str = NULLCHAR;
16325
16326     if (appData.debugMode) {
16327         TimeMark now; int print = 1;
16328         char *quote = ""; char c; int i;
16329
16330         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16331                 char start = message[0];
16332                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16333                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16334                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16335                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16336                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16337                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16338                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16339                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16340                    sscanf(message, "hint: %c", &c)!=1 &&
16341                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16342                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16343                     print = (appData.engineComments >= 2);
16344                 }
16345                 message[0] = start; // restore original message
16346         }
16347         if(print) {
16348                 GetTimeMark(&now);
16349                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16350                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16351                         quote,
16352                         message);
16353                 if(serverFP)
16354                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16355                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16356                         quote,
16357                         message), fflush(serverFP);
16358         }
16359     }
16360
16361     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16362     if (appData.icsEngineAnalyze) {
16363         if (strstr(message, "whisper") != NULL ||
16364              strstr(message, "kibitz") != NULL ||
16365             strstr(message, "tellics") != NULL) return;
16366     }
16367
16368     HandleMachineMove(message, cps);
16369 }
16370
16371
16372 void
16373 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16374 {
16375     char buf[MSG_SIZ];
16376     int seconds;
16377
16378     if( timeControl_2 > 0 ) {
16379         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16380             tc = timeControl_2;
16381         }
16382     }
16383     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16384     inc /= cps->timeOdds;
16385     st  /= cps->timeOdds;
16386
16387     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16388
16389     if (st > 0) {
16390       /* Set exact time per move, normally using st command */
16391       if (cps->stKludge) {
16392         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16393         seconds = st % 60;
16394         if (seconds == 0) {
16395           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16396         } else {
16397           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16398         }
16399       } else {
16400         snprintf(buf, MSG_SIZ, "st %d\n", st);
16401       }
16402     } else {
16403       /* Set conventional or incremental time control, using level command */
16404       if (seconds == 0) {
16405         /* Note old gnuchess bug -- minutes:seconds used to not work.
16406            Fixed in later versions, but still avoid :seconds
16407            when seconds is 0. */
16408         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16409       } else {
16410         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16411                  seconds, inc/1000.);
16412       }
16413     }
16414     SendToProgram(buf, cps);
16415
16416     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16417     /* Orthogonally, limit search to given depth */
16418     if (sd > 0) {
16419       if (cps->sdKludge) {
16420         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16421       } else {
16422         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16423       }
16424       SendToProgram(buf, cps);
16425     }
16426
16427     if(cps->nps >= 0) { /* [HGM] nps */
16428         if(cps->supportsNPS == FALSE)
16429           cps->nps = -1; // don't use if engine explicitly says not supported!
16430         else {
16431           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16432           SendToProgram(buf, cps);
16433         }
16434     }
16435 }
16436
16437 ChessProgramState *
16438 WhitePlayer ()
16439 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16440 {
16441     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16442        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16443         return &second;
16444     return &first;
16445 }
16446
16447 void
16448 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16449 {
16450     char message[MSG_SIZ];
16451     long time, otime;
16452
16453     /* Note: this routine must be called when the clocks are stopped
16454        or when they have *just* been set or switched; otherwise
16455        it will be off by the time since the current tick started.
16456     */
16457     if (machineWhite) {
16458         time = whiteTimeRemaining / 10;
16459         otime = blackTimeRemaining / 10;
16460     } else {
16461         time = blackTimeRemaining / 10;
16462         otime = whiteTimeRemaining / 10;
16463     }
16464     /* [HGM] translate opponent's time by time-odds factor */
16465     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16466
16467     if (time <= 0) time = 1;
16468     if (otime <= 0) otime = 1;
16469
16470     snprintf(message, MSG_SIZ, "time %ld\n", time);
16471     SendToProgram(message, cps);
16472
16473     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16474     SendToProgram(message, cps);
16475 }
16476
16477 char *
16478 EngineDefinedVariant (ChessProgramState *cps, int n)
16479 {   // return name of n-th unknown variant that engine supports
16480     static char buf[MSG_SIZ];
16481     char *p, *s = cps->variants;
16482     if(!s) return NULL;
16483     do { // parse string from variants feature
16484       VariantClass v;
16485         p = strchr(s, ',');
16486         if(p) *p = NULLCHAR;
16487       v = StringToVariant(s);
16488       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16489         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16490             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16491         }
16492         if(p) *p++ = ',';
16493         if(n < 0) return buf;
16494     } while(s = p);
16495     return NULL;
16496 }
16497
16498 int
16499 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16500 {
16501   char buf[MSG_SIZ];
16502   int len = strlen(name);
16503   int val;
16504
16505   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16506     (*p) += len + 1;
16507     sscanf(*p, "%d", &val);
16508     *loc = (val != 0);
16509     while (**p && **p != ' ')
16510       (*p)++;
16511     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16512     SendToProgram(buf, cps);
16513     return TRUE;
16514   }
16515   return FALSE;
16516 }
16517
16518 int
16519 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16520 {
16521   char buf[MSG_SIZ];
16522   int len = strlen(name);
16523   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16524     (*p) += len + 1;
16525     sscanf(*p, "%d", loc);
16526     while (**p && **p != ' ') (*p)++;
16527     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16528     SendToProgram(buf, cps);
16529     return TRUE;
16530   }
16531   return FALSE;
16532 }
16533
16534 int
16535 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16536 {
16537   char buf[MSG_SIZ];
16538   int len = strlen(name);
16539   if (strncmp((*p), name, len) == 0
16540       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16541     (*p) += len + 2;
16542     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16543     sscanf(*p, "%[^\"]", *loc);
16544     while (**p && **p != '\"') (*p)++;
16545     if (**p == '\"') (*p)++;
16546     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16547     SendToProgram(buf, cps);
16548     return TRUE;
16549   }
16550   return FALSE;
16551 }
16552
16553 int
16554 ParseOption (Option *opt, ChessProgramState *cps)
16555 // [HGM] options: process the string that defines an engine option, and determine
16556 // name, type, default value, and allowed value range
16557 {
16558         char *p, *q, buf[MSG_SIZ];
16559         int n, min = (-1)<<31, max = 1<<31, def;
16560
16561         if(p = strstr(opt->name, " -spin ")) {
16562             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16563             if(max < min) max = min; // enforce consistency
16564             if(def < min) def = min;
16565             if(def > max) def = max;
16566             opt->value = def;
16567             opt->min = min;
16568             opt->max = max;
16569             opt->type = Spin;
16570         } else if((p = strstr(opt->name, " -slider "))) {
16571             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16572             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16573             if(max < min) max = min; // enforce consistency
16574             if(def < min) def = min;
16575             if(def > max) def = max;
16576             opt->value = def;
16577             opt->min = min;
16578             opt->max = max;
16579             opt->type = Spin; // Slider;
16580         } else if((p = strstr(opt->name, " -string "))) {
16581             opt->textValue = p+9;
16582             opt->type = TextBox;
16583         } else if((p = strstr(opt->name, " -file "))) {
16584             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16585             opt->textValue = p+7;
16586             opt->type = FileName; // FileName;
16587         } else if((p = strstr(opt->name, " -path "))) {
16588             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16589             opt->textValue = p+7;
16590             opt->type = PathName; // PathName;
16591         } else if(p = strstr(opt->name, " -check ")) {
16592             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16593             opt->value = (def != 0);
16594             opt->type = CheckBox;
16595         } else if(p = strstr(opt->name, " -combo ")) {
16596             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16597             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16598             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16599             opt->value = n = 0;
16600             while(q = StrStr(q, " /// ")) {
16601                 n++; *q = 0;    // count choices, and null-terminate each of them
16602                 q += 5;
16603                 if(*q == '*') { // remember default, which is marked with * prefix
16604                     q++;
16605                     opt->value = n;
16606                 }
16607                 cps->comboList[cps->comboCnt++] = q;
16608             }
16609             cps->comboList[cps->comboCnt++] = NULL;
16610             opt->max = n + 1;
16611             opt->type = ComboBox;
16612         } else if(p = strstr(opt->name, " -button")) {
16613             opt->type = Button;
16614         } else if(p = strstr(opt->name, " -save")) {
16615             opt->type = SaveButton;
16616         } else return FALSE;
16617         *p = 0; // terminate option name
16618         // now look if the command-line options define a setting for this engine option.
16619         if(cps->optionSettings && cps->optionSettings[0])
16620             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16621         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16622           snprintf(buf, MSG_SIZ, "option %s", p);
16623                 if(p = strstr(buf, ",")) *p = 0;
16624                 if(q = strchr(buf, '=')) switch(opt->type) {
16625                     case ComboBox:
16626                         for(n=0; n<opt->max; n++)
16627                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16628                         break;
16629                     case TextBox:
16630                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16631                         break;
16632                     case Spin:
16633                     case CheckBox:
16634                         opt->value = atoi(q+1);
16635                     default:
16636                         break;
16637                 }
16638                 strcat(buf, "\n");
16639                 SendToProgram(buf, cps);
16640         }
16641         return TRUE;
16642 }
16643
16644 void
16645 FeatureDone (ChessProgramState *cps, int val)
16646 {
16647   DelayedEventCallback cb = GetDelayedEvent();
16648   if ((cb == InitBackEnd3 && cps == &first) ||
16649       (cb == SettingsMenuIfReady && cps == &second) ||
16650       (cb == LoadEngine) ||
16651       (cb == TwoMachinesEventIfReady)) {
16652     CancelDelayedEvent();
16653     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16654   }
16655   cps->initDone = val;
16656   if(val) cps->reload = FALSE;
16657 }
16658
16659 /* Parse feature command from engine */
16660 void
16661 ParseFeatures (char *args, ChessProgramState *cps)
16662 {
16663   char *p = args;
16664   char *q = NULL;
16665   int val;
16666   char buf[MSG_SIZ];
16667
16668   for (;;) {
16669     while (*p == ' ') p++;
16670     if (*p == NULLCHAR) return;
16671
16672     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16673     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16674     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16675     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16676     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16677     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16678     if (BoolFeature(&p, "reuse", &val, cps)) {
16679       /* Engine can disable reuse, but can't enable it if user said no */
16680       if (!val) cps->reuse = FALSE;
16681       continue;
16682     }
16683     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16684     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16685       if (gameMode == TwoMachinesPlay) {
16686         DisplayTwoMachinesTitle();
16687       } else {
16688         DisplayTitle("");
16689       }
16690       continue;
16691     }
16692     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16693     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16694     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16695     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16696     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16697     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16698     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16699     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16700     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16701     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16702     if (IntFeature(&p, "done", &val, cps)) {
16703       FeatureDone(cps, val);
16704       continue;
16705     }
16706     /* Added by Tord: */
16707     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16708     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16709     /* End of additions by Tord */
16710
16711     /* [HGM] added features: */
16712     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16713     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16714     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16715     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16716     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16717     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16718     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16719     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16720         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16721         FREE(cps->option[cps->nrOptions].name);
16722         cps->option[cps->nrOptions].name = q; q = NULL;
16723         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16724           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16725             SendToProgram(buf, cps);
16726             continue;
16727         }
16728         if(cps->nrOptions >= MAX_OPTIONS) {
16729             cps->nrOptions--;
16730             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16731             DisplayError(buf, 0);
16732         }
16733         continue;
16734     }
16735     /* End of additions by HGM */
16736
16737     /* unknown feature: complain and skip */
16738     q = p;
16739     while (*q && *q != '=') q++;
16740     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16741     SendToProgram(buf, cps);
16742     p = q;
16743     if (*p == '=') {
16744       p++;
16745       if (*p == '\"') {
16746         p++;
16747         while (*p && *p != '\"') p++;
16748         if (*p == '\"') p++;
16749       } else {
16750         while (*p && *p != ' ') p++;
16751       }
16752     }
16753   }
16754
16755 }
16756
16757 void
16758 PeriodicUpdatesEvent (int newState)
16759 {
16760     if (newState == appData.periodicUpdates)
16761       return;
16762
16763     appData.periodicUpdates=newState;
16764
16765     /* Display type changes, so update it now */
16766 //    DisplayAnalysis();
16767
16768     /* Get the ball rolling again... */
16769     if (newState) {
16770         AnalysisPeriodicEvent(1);
16771         StartAnalysisClock();
16772     }
16773 }
16774
16775 void
16776 PonderNextMoveEvent (int newState)
16777 {
16778     if (newState == appData.ponderNextMove) return;
16779     if (gameMode == EditPosition) EditPositionDone(TRUE);
16780     if (newState) {
16781         SendToProgram("hard\n", &first);
16782         if (gameMode == TwoMachinesPlay) {
16783             SendToProgram("hard\n", &second);
16784         }
16785     } else {
16786         SendToProgram("easy\n", &first);
16787         thinkOutput[0] = NULLCHAR;
16788         if (gameMode == TwoMachinesPlay) {
16789             SendToProgram("easy\n", &second);
16790         }
16791     }
16792     appData.ponderNextMove = newState;
16793 }
16794
16795 void
16796 NewSettingEvent (int option, int *feature, char *command, int value)
16797 {
16798     char buf[MSG_SIZ];
16799
16800     if (gameMode == EditPosition) EditPositionDone(TRUE);
16801     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16802     if(feature == NULL || *feature) SendToProgram(buf, &first);
16803     if (gameMode == TwoMachinesPlay) {
16804         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16805     }
16806 }
16807
16808 void
16809 ShowThinkingEvent ()
16810 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16811 {
16812     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16813     int newState = appData.showThinking
16814         // [HGM] thinking: other features now need thinking output as well
16815         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16816
16817     if (oldState == newState) return;
16818     oldState = newState;
16819     if (gameMode == EditPosition) EditPositionDone(TRUE);
16820     if (oldState) {
16821         SendToProgram("post\n", &first);
16822         if (gameMode == TwoMachinesPlay) {
16823             SendToProgram("post\n", &second);
16824         }
16825     } else {
16826         SendToProgram("nopost\n", &first);
16827         thinkOutput[0] = NULLCHAR;
16828         if (gameMode == TwoMachinesPlay) {
16829             SendToProgram("nopost\n", &second);
16830         }
16831     }
16832 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16833 }
16834
16835 void
16836 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16837 {
16838   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16839   if (pr == NoProc) return;
16840   AskQuestion(title, question, replyPrefix, pr);
16841 }
16842
16843 void
16844 TypeInEvent (char firstChar)
16845 {
16846     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16847         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16848         gameMode == AnalyzeMode || gameMode == EditGame ||
16849         gameMode == EditPosition || gameMode == IcsExamining ||
16850         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16851         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16852                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16853                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16854         gameMode == Training) PopUpMoveDialog(firstChar);
16855 }
16856
16857 void
16858 TypeInDoneEvent (char *move)
16859 {
16860         Board board;
16861         int n, fromX, fromY, toX, toY;
16862         char promoChar;
16863         ChessMove moveType;
16864
16865         // [HGM] FENedit
16866         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16867                 EditPositionPasteFEN(move);
16868                 return;
16869         }
16870         // [HGM] movenum: allow move number to be typed in any mode
16871         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16872           ToNrEvent(2*n-1);
16873           return;
16874         }
16875         // undocumented kludge: allow command-line option to be typed in!
16876         // (potentially fatal, and does not implement the effect of the option.)
16877         // should only be used for options that are values on which future decisions will be made,
16878         // and definitely not on options that would be used during initialization.
16879         if(strstr(move, "!!! -") == move) {
16880             ParseArgsFromString(move+4);
16881             return;
16882         }
16883
16884       if (gameMode != EditGame && currentMove != forwardMostMove &&
16885         gameMode != Training) {
16886         DisplayMoveError(_("Displayed move is not current"));
16887       } else {
16888         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16889           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16890         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16891         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16892           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16893           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16894         } else {
16895           DisplayMoveError(_("Could not parse move"));
16896         }
16897       }
16898 }
16899
16900 void
16901 DisplayMove (int moveNumber)
16902 {
16903     char message[MSG_SIZ];
16904     char res[MSG_SIZ];
16905     char cpThinkOutput[MSG_SIZ];
16906
16907     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16908
16909     if (moveNumber == forwardMostMove - 1 ||
16910         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16911
16912         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16913
16914         if (strchr(cpThinkOutput, '\n')) {
16915             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16916         }
16917     } else {
16918         *cpThinkOutput = NULLCHAR;
16919     }
16920
16921     /* [AS] Hide thinking from human user */
16922     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16923         *cpThinkOutput = NULLCHAR;
16924         if( thinkOutput[0] != NULLCHAR ) {
16925             int i;
16926
16927             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16928                 cpThinkOutput[i] = '.';
16929             }
16930             cpThinkOutput[i] = NULLCHAR;
16931             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16932         }
16933     }
16934
16935     if (moveNumber == forwardMostMove - 1 &&
16936         gameInfo.resultDetails != NULL) {
16937         if (gameInfo.resultDetails[0] == NULLCHAR) {
16938           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16939         } else {
16940           snprintf(res, MSG_SIZ, " {%s} %s",
16941                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16942         }
16943     } else {
16944         res[0] = NULLCHAR;
16945     }
16946
16947     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16948         DisplayMessage(res, cpThinkOutput);
16949     } else {
16950       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16951                 WhiteOnMove(moveNumber) ? " " : ".. ",
16952                 parseList[moveNumber], res);
16953         DisplayMessage(message, cpThinkOutput);
16954     }
16955 }
16956
16957 void
16958 DisplayComment (int moveNumber, char *text)
16959 {
16960     char title[MSG_SIZ];
16961
16962     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16963       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16964     } else {
16965       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16966               WhiteOnMove(moveNumber) ? " " : ".. ",
16967               parseList[moveNumber]);
16968     }
16969     if (text != NULL && (appData.autoDisplayComment || commentUp))
16970         CommentPopUp(title, text);
16971 }
16972
16973 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16974  * might be busy thinking or pondering.  It can be omitted if your
16975  * gnuchess is configured to stop thinking immediately on any user
16976  * input.  However, that gnuchess feature depends on the FIONREAD
16977  * ioctl, which does not work properly on some flavors of Unix.
16978  */
16979 void
16980 Attention (ChessProgramState *cps)
16981 {
16982 #if ATTENTION
16983     if (!cps->useSigint) return;
16984     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16985     switch (gameMode) {
16986       case MachinePlaysWhite:
16987       case MachinePlaysBlack:
16988       case TwoMachinesPlay:
16989       case IcsPlayingWhite:
16990       case IcsPlayingBlack:
16991       case AnalyzeMode:
16992       case AnalyzeFile:
16993         /* Skip if we know it isn't thinking */
16994         if (!cps->maybeThinking) return;
16995         if (appData.debugMode)
16996           fprintf(debugFP, "Interrupting %s\n", cps->which);
16997         InterruptChildProcess(cps->pr);
16998         cps->maybeThinking = FALSE;
16999         break;
17000       default:
17001         break;
17002     }
17003 #endif /*ATTENTION*/
17004 }
17005
17006 int
17007 CheckFlags ()
17008 {
17009     if (whiteTimeRemaining <= 0) {
17010         if (!whiteFlag) {
17011             whiteFlag = TRUE;
17012             if (appData.icsActive) {
17013                 if (appData.autoCallFlag &&
17014                     gameMode == IcsPlayingBlack && !blackFlag) {
17015                   SendToICS(ics_prefix);
17016                   SendToICS("flag\n");
17017                 }
17018             } else {
17019                 if (blackFlag) {
17020                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17021                 } else {
17022                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17023                     if (appData.autoCallFlag) {
17024                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17025                         return TRUE;
17026                     }
17027                 }
17028             }
17029         }
17030     }
17031     if (blackTimeRemaining <= 0) {
17032         if (!blackFlag) {
17033             blackFlag = TRUE;
17034             if (appData.icsActive) {
17035                 if (appData.autoCallFlag &&
17036                     gameMode == IcsPlayingWhite && !whiteFlag) {
17037                   SendToICS(ics_prefix);
17038                   SendToICS("flag\n");
17039                 }
17040             } else {
17041                 if (whiteFlag) {
17042                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17043                 } else {
17044                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17045                     if (appData.autoCallFlag) {
17046                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17047                         return TRUE;
17048                     }
17049                 }
17050             }
17051         }
17052     }
17053     return FALSE;
17054 }
17055
17056 void
17057 CheckTimeControl ()
17058 {
17059     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17060         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17061
17062     /*
17063      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17064      */
17065     if ( !WhiteOnMove(forwardMostMove) ) {
17066         /* White made time control */
17067         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17068         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17069         /* [HGM] time odds: correct new time quota for time odds! */
17070                                             / WhitePlayer()->timeOdds;
17071         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17072     } else {
17073         lastBlack -= blackTimeRemaining;
17074         /* Black made time control */
17075         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17076                                             / WhitePlayer()->other->timeOdds;
17077         lastWhite = whiteTimeRemaining;
17078     }
17079 }
17080
17081 void
17082 DisplayBothClocks ()
17083 {
17084     int wom = gameMode == EditPosition ?
17085       !blackPlaysFirst : WhiteOnMove(currentMove);
17086     DisplayWhiteClock(whiteTimeRemaining, wom);
17087     DisplayBlackClock(blackTimeRemaining, !wom);
17088 }
17089
17090
17091 /* Timekeeping seems to be a portability nightmare.  I think everyone
17092    has ftime(), but I'm really not sure, so I'm including some ifdefs
17093    to use other calls if you don't.  Clocks will be less accurate if
17094    you have neither ftime nor gettimeofday.
17095 */
17096
17097 /* VS 2008 requires the #include outside of the function */
17098 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17099 #include <sys/timeb.h>
17100 #endif
17101
17102 /* Get the current time as a TimeMark */
17103 void
17104 GetTimeMark (TimeMark *tm)
17105 {
17106 #if HAVE_GETTIMEOFDAY
17107
17108     struct timeval timeVal;
17109     struct timezone timeZone;
17110
17111     gettimeofday(&timeVal, &timeZone);
17112     tm->sec = (long) timeVal.tv_sec;
17113     tm->ms = (int) (timeVal.tv_usec / 1000L);
17114
17115 #else /*!HAVE_GETTIMEOFDAY*/
17116 #if HAVE_FTIME
17117
17118 // include <sys/timeb.h> / moved to just above start of function
17119     struct timeb timeB;
17120
17121     ftime(&timeB);
17122     tm->sec = (long) timeB.time;
17123     tm->ms = (int) timeB.millitm;
17124
17125 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17126     tm->sec = (long) time(NULL);
17127     tm->ms = 0;
17128 #endif
17129 #endif
17130 }
17131
17132 /* Return the difference in milliseconds between two
17133    time marks.  We assume the difference will fit in a long!
17134 */
17135 long
17136 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17137 {
17138     return 1000L*(tm2->sec - tm1->sec) +
17139            (long) (tm2->ms - tm1->ms);
17140 }
17141
17142
17143 /*
17144  * Code to manage the game clocks.
17145  *
17146  * In tournament play, black starts the clock and then white makes a move.
17147  * We give the human user a slight advantage if he is playing white---the
17148  * clocks don't run until he makes his first move, so it takes zero time.
17149  * Also, we don't account for network lag, so we could get out of sync
17150  * with GNU Chess's clock -- but then, referees are always right.
17151  */
17152
17153 static TimeMark tickStartTM;
17154 static long intendedTickLength;
17155
17156 long
17157 NextTickLength (long timeRemaining)
17158 {
17159     long nominalTickLength, nextTickLength;
17160
17161     if (timeRemaining > 0L && timeRemaining <= 10000L)
17162       nominalTickLength = 100L;
17163     else
17164       nominalTickLength = 1000L;
17165     nextTickLength = timeRemaining % nominalTickLength;
17166     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17167
17168     return nextTickLength;
17169 }
17170
17171 /* Adjust clock one minute up or down */
17172 void
17173 AdjustClock (Boolean which, int dir)
17174 {
17175     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17176     if(which) blackTimeRemaining += 60000*dir;
17177     else      whiteTimeRemaining += 60000*dir;
17178     DisplayBothClocks();
17179     adjustedClock = TRUE;
17180 }
17181
17182 /* Stop clocks and reset to a fresh time control */
17183 void
17184 ResetClocks ()
17185 {
17186     (void) StopClockTimer();
17187     if (appData.icsActive) {
17188         whiteTimeRemaining = blackTimeRemaining = 0;
17189     } else if (searchTime) {
17190         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17191         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17192     } else { /* [HGM] correct new time quote for time odds */
17193         whiteTC = blackTC = fullTimeControlString;
17194         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17195         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17196     }
17197     if (whiteFlag || blackFlag) {
17198         DisplayTitle("");
17199         whiteFlag = blackFlag = FALSE;
17200     }
17201     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17202     DisplayBothClocks();
17203     adjustedClock = FALSE;
17204 }
17205
17206 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17207
17208 /* Decrement running clock by amount of time that has passed */
17209 void
17210 DecrementClocks ()
17211 {
17212     long timeRemaining;
17213     long lastTickLength, fudge;
17214     TimeMark now;
17215
17216     if (!appData.clockMode) return;
17217     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17218
17219     GetTimeMark(&now);
17220
17221     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17222
17223     /* Fudge if we woke up a little too soon */
17224     fudge = intendedTickLength - lastTickLength;
17225     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17226
17227     if (WhiteOnMove(forwardMostMove)) {
17228         if(whiteNPS >= 0) lastTickLength = 0;
17229         timeRemaining = whiteTimeRemaining -= lastTickLength;
17230         if(timeRemaining < 0 && !appData.icsActive) {
17231             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17232             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17233                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17234                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17235             }
17236         }
17237         DisplayWhiteClock(whiteTimeRemaining - fudge,
17238                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17239     } else {
17240         if(blackNPS >= 0) lastTickLength = 0;
17241         timeRemaining = blackTimeRemaining -= lastTickLength;
17242         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17243             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17244             if(suddenDeath) {
17245                 blackStartMove = forwardMostMove;
17246                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17247             }
17248         }
17249         DisplayBlackClock(blackTimeRemaining - fudge,
17250                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17251     }
17252     if (CheckFlags()) return;
17253
17254     if(twoBoards) { // count down secondary board's clocks as well
17255         activePartnerTime -= lastTickLength;
17256         partnerUp = 1;
17257         if(activePartner == 'W')
17258             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17259         else
17260             DisplayBlackClock(activePartnerTime, TRUE);
17261         partnerUp = 0;
17262     }
17263
17264     tickStartTM = now;
17265     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17266     StartClockTimer(intendedTickLength);
17267
17268     /* if the time remaining has fallen below the alarm threshold, sound the
17269      * alarm. if the alarm has sounded and (due to a takeback or time control
17270      * with increment) the time remaining has increased to a level above the
17271      * threshold, reset the alarm so it can sound again.
17272      */
17273
17274     if (appData.icsActive && appData.icsAlarm) {
17275
17276         /* make sure we are dealing with the user's clock */
17277         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17278                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17279            )) return;
17280
17281         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17282             alarmSounded = FALSE;
17283         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17284             PlayAlarmSound();
17285             alarmSounded = TRUE;
17286         }
17287     }
17288 }
17289
17290
17291 /* A player has just moved, so stop the previously running
17292    clock and (if in clock mode) start the other one.
17293    We redisplay both clocks in case we're in ICS mode, because
17294    ICS gives us an update to both clocks after every move.
17295    Note that this routine is called *after* forwardMostMove
17296    is updated, so the last fractional tick must be subtracted
17297    from the color that is *not* on move now.
17298 */
17299 void
17300 SwitchClocks (int newMoveNr)
17301 {
17302     long lastTickLength;
17303     TimeMark now;
17304     int flagged = FALSE;
17305
17306     GetTimeMark(&now);
17307
17308     if (StopClockTimer() && appData.clockMode) {
17309         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17310         if (!WhiteOnMove(forwardMostMove)) {
17311             if(blackNPS >= 0) lastTickLength = 0;
17312             blackTimeRemaining -= lastTickLength;
17313            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17314 //         if(pvInfoList[forwardMostMove].time == -1)
17315                  pvInfoList[forwardMostMove].time =               // use GUI time
17316                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17317         } else {
17318            if(whiteNPS >= 0) lastTickLength = 0;
17319            whiteTimeRemaining -= lastTickLength;
17320            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17321 //         if(pvInfoList[forwardMostMove].time == -1)
17322                  pvInfoList[forwardMostMove].time =
17323                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17324         }
17325         flagged = CheckFlags();
17326     }
17327     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17328     CheckTimeControl();
17329
17330     if (flagged || !appData.clockMode) return;
17331
17332     switch (gameMode) {
17333       case MachinePlaysBlack:
17334       case MachinePlaysWhite:
17335       case BeginningOfGame:
17336         if (pausing) return;
17337         break;
17338
17339       case EditGame:
17340       case PlayFromGameFile:
17341       case IcsExamining:
17342         return;
17343
17344       default:
17345         break;
17346     }
17347
17348     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17349         if(WhiteOnMove(forwardMostMove))
17350              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17351         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17352     }
17353
17354     tickStartTM = now;
17355     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17356       whiteTimeRemaining : blackTimeRemaining);
17357     StartClockTimer(intendedTickLength);
17358 }
17359
17360
17361 /* Stop both clocks */
17362 void
17363 StopClocks ()
17364 {
17365     long lastTickLength;
17366     TimeMark now;
17367
17368     if (!StopClockTimer()) return;
17369     if (!appData.clockMode) return;
17370
17371     GetTimeMark(&now);
17372
17373     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17374     if (WhiteOnMove(forwardMostMove)) {
17375         if(whiteNPS >= 0) lastTickLength = 0;
17376         whiteTimeRemaining -= lastTickLength;
17377         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17378     } else {
17379         if(blackNPS >= 0) lastTickLength = 0;
17380         blackTimeRemaining -= lastTickLength;
17381         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17382     }
17383     CheckFlags();
17384 }
17385
17386 /* Start clock of player on move.  Time may have been reset, so
17387    if clock is already running, stop and restart it. */
17388 void
17389 StartClocks ()
17390 {
17391     (void) StopClockTimer(); /* in case it was running already */
17392     DisplayBothClocks();
17393     if (CheckFlags()) return;
17394
17395     if (!appData.clockMode) return;
17396     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17397
17398     GetTimeMark(&tickStartTM);
17399     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17400       whiteTimeRemaining : blackTimeRemaining);
17401
17402    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17403     whiteNPS = blackNPS = -1;
17404     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17405        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17406         whiteNPS = first.nps;
17407     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17408        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17409         blackNPS = first.nps;
17410     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17411         whiteNPS = second.nps;
17412     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17413         blackNPS = second.nps;
17414     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17415
17416     StartClockTimer(intendedTickLength);
17417 }
17418
17419 char *
17420 TimeString (long ms)
17421 {
17422     long second, minute, hour, day;
17423     char *sign = "";
17424     static char buf[32];
17425
17426     if (ms > 0 && ms <= 9900) {
17427       /* convert milliseconds to tenths, rounding up */
17428       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17429
17430       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17431       return buf;
17432     }
17433
17434     /* convert milliseconds to seconds, rounding up */
17435     /* use floating point to avoid strangeness of integer division
17436        with negative dividends on many machines */
17437     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17438
17439     if (second < 0) {
17440         sign = "-";
17441         second = -second;
17442     }
17443
17444     day = second / (60 * 60 * 24);
17445     second = second % (60 * 60 * 24);
17446     hour = second / (60 * 60);
17447     second = second % (60 * 60);
17448     minute = second / 60;
17449     second = second % 60;
17450
17451     if (day > 0)
17452       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17453               sign, day, hour, minute, second);
17454     else if (hour > 0)
17455       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17456     else
17457       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17458
17459     return buf;
17460 }
17461
17462
17463 /*
17464  * This is necessary because some C libraries aren't ANSI C compliant yet.
17465  */
17466 char *
17467 StrStr (char *string, char *match)
17468 {
17469     int i, length;
17470
17471     length = strlen(match);
17472
17473     for (i = strlen(string) - length; i >= 0; i--, string++)
17474       if (!strncmp(match, string, length))
17475         return string;
17476
17477     return NULL;
17478 }
17479
17480 char *
17481 StrCaseStr (char *string, char *match)
17482 {
17483     int i, j, length;
17484
17485     length = strlen(match);
17486
17487     for (i = strlen(string) - length; i >= 0; i--, string++) {
17488         for (j = 0; j < length; j++) {
17489             if (ToLower(match[j]) != ToLower(string[j]))
17490               break;
17491         }
17492         if (j == length) return string;
17493     }
17494
17495     return NULL;
17496 }
17497
17498 #ifndef _amigados
17499 int
17500 StrCaseCmp (char *s1, char *s2)
17501 {
17502     char c1, c2;
17503
17504     for (;;) {
17505         c1 = ToLower(*s1++);
17506         c2 = ToLower(*s2++);
17507         if (c1 > c2) return 1;
17508         if (c1 < c2) return -1;
17509         if (c1 == NULLCHAR) return 0;
17510     }
17511 }
17512
17513
17514 int
17515 ToLower (int c)
17516 {
17517     return isupper(c) ? tolower(c) : c;
17518 }
17519
17520
17521 int
17522 ToUpper (int c)
17523 {
17524     return islower(c) ? toupper(c) : c;
17525 }
17526 #endif /* !_amigados    */
17527
17528 char *
17529 StrSave (char *s)
17530 {
17531   char *ret;
17532
17533   if ((ret = (char *) malloc(strlen(s) + 1)))
17534     {
17535       safeStrCpy(ret, s, strlen(s)+1);
17536     }
17537   return ret;
17538 }
17539
17540 char *
17541 StrSavePtr (char *s, char **savePtr)
17542 {
17543     if (*savePtr) {
17544         free(*savePtr);
17545     }
17546     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17547       safeStrCpy(*savePtr, s, strlen(s)+1);
17548     }
17549     return(*savePtr);
17550 }
17551
17552 char *
17553 PGNDate ()
17554 {
17555     time_t clock;
17556     struct tm *tm;
17557     char buf[MSG_SIZ];
17558
17559     clock = time((time_t *)NULL);
17560     tm = localtime(&clock);
17561     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17562             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17563     return StrSave(buf);
17564 }
17565
17566
17567 char *
17568 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17569 {
17570     int i, j, fromX, fromY, toX, toY;
17571     int whiteToPlay;
17572     char buf[MSG_SIZ];
17573     char *p, *q;
17574     int emptycount;
17575     ChessSquare piece;
17576
17577     whiteToPlay = (gameMode == EditPosition) ?
17578       !blackPlaysFirst : (move % 2 == 0);
17579     p = buf;
17580
17581     /* Piece placement data */
17582     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17583         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17584         emptycount = 0;
17585         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17586             if (boards[move][i][j] == EmptySquare) {
17587                 emptycount++;
17588             } else { ChessSquare piece = boards[move][i][j];
17589                 if (emptycount > 0) {
17590                     if(emptycount<10) /* [HGM] can be >= 10 */
17591                         *p++ = '0' + emptycount;
17592                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17593                     emptycount = 0;
17594                 }
17595                 if(PieceToChar(piece) == '+') {
17596                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17597                     *p++ = '+';
17598                     piece = (ChessSquare)(DEMOTED piece);
17599                 }
17600                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17601                 if(p[-1] == '~') {
17602                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17603                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17604                     *p++ = '~';
17605                 }
17606             }
17607         }
17608         if (emptycount > 0) {
17609             if(emptycount<10) /* [HGM] can be >= 10 */
17610                 *p++ = '0' + emptycount;
17611             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17612             emptycount = 0;
17613         }
17614         *p++ = '/';
17615     }
17616     *(p - 1) = ' ';
17617
17618     /* [HGM] print Crazyhouse or Shogi holdings */
17619     if( gameInfo.holdingsWidth ) {
17620         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17621         q = p;
17622         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17623             piece = boards[move][i][BOARD_WIDTH-1];
17624             if( piece != EmptySquare )
17625               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17626                   *p++ = PieceToChar(piece);
17627         }
17628         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17629             piece = boards[move][BOARD_HEIGHT-i-1][0];
17630             if( piece != EmptySquare )
17631               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17632                   *p++ = PieceToChar(piece);
17633         }
17634
17635         if( q == p ) *p++ = '-';
17636         *p++ = ']';
17637         *p++ = ' ';
17638     }
17639
17640     /* Active color */
17641     *p++ = whiteToPlay ? 'w' : 'b';
17642     *p++ = ' ';
17643
17644   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17645     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17646   } else {
17647   if(nrCastlingRights) {
17648      q = p;
17649      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17650        /* [HGM] write directly from rights */
17651            if(boards[move][CASTLING][2] != NoRights &&
17652               boards[move][CASTLING][0] != NoRights   )
17653                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17654            if(boards[move][CASTLING][2] != NoRights &&
17655               boards[move][CASTLING][1] != NoRights   )
17656                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17657            if(boards[move][CASTLING][5] != NoRights &&
17658               boards[move][CASTLING][3] != NoRights   )
17659                 *p++ = boards[move][CASTLING][3] + AAA;
17660            if(boards[move][CASTLING][5] != NoRights &&
17661               boards[move][CASTLING][4] != NoRights   )
17662                 *p++ = boards[move][CASTLING][4] + AAA;
17663      } else {
17664
17665         /* [HGM] write true castling rights */
17666         if( nrCastlingRights == 6 ) {
17667             int q, k=0;
17668             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17669                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17670             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17671                  boards[move][CASTLING][2] != NoRights  );
17672             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17673                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17674                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17675                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17676                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17677             }
17678             if(q) *p++ = 'Q';
17679             k = 0;
17680             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17681                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17682             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17683                  boards[move][CASTLING][5] != NoRights  );
17684             if(gameInfo.variant == VariantSChess) {
17685                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17686                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17687                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17688                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17689             }
17690             if(q) *p++ = 'q';
17691         }
17692      }
17693      if (q == p) *p++ = '-'; /* No castling rights */
17694      *p++ = ' ';
17695   }
17696
17697   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17698      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17699      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17700     /* En passant target square */
17701     if (move > backwardMostMove) {
17702         fromX = moveList[move - 1][0] - AAA;
17703         fromY = moveList[move - 1][1] - ONE;
17704         toX = moveList[move - 1][2] - AAA;
17705         toY = moveList[move - 1][3] - ONE;
17706         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17707             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17708             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17709             fromX == toX) {
17710             /* 2-square pawn move just happened */
17711             *p++ = toX + AAA;
17712             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17713         } else {
17714             *p++ = '-';
17715         }
17716     } else if(move == backwardMostMove) {
17717         // [HGM] perhaps we should always do it like this, and forget the above?
17718         if((signed char)boards[move][EP_STATUS] >= 0) {
17719             *p++ = boards[move][EP_STATUS] + AAA;
17720             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17721         } else {
17722             *p++ = '-';
17723         }
17724     } else {
17725         *p++ = '-';
17726     }
17727     *p++ = ' ';
17728   }
17729   }
17730
17731     if(moveCounts)
17732     {   int i = 0, j=move;
17733
17734         /* [HGM] find reversible plies */
17735         if (appData.debugMode) { int k;
17736             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17737             for(k=backwardMostMove; k<=forwardMostMove; k++)
17738                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17739
17740         }
17741
17742         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17743         if( j == backwardMostMove ) i += initialRulePlies;
17744         sprintf(p, "%d ", i);
17745         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17746
17747         /* Fullmove number */
17748         sprintf(p, "%d", (move / 2) + 1);
17749     } else *--p = NULLCHAR;
17750
17751     return StrSave(buf);
17752 }
17753
17754 Boolean
17755 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17756 {
17757     int i, j, k, w=0;
17758     char *p, c;
17759     int emptycount, virgin[BOARD_FILES];
17760     ChessSquare piece;
17761
17762     p = fen;
17763
17764     /* Piece placement data */
17765     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17766         j = 0;
17767         for (;;) {
17768             if (*p == '/' || *p == ' ' || *p == '[' ) {
17769                 if(j > w) w = j;
17770                 emptycount = gameInfo.boardWidth - j;
17771                 while (emptycount--)
17772                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17773                 if (*p == '/') p++;
17774                 else if(autoSize) { // we stumbled unexpectedly into end of board
17775                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17776                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17777                     }
17778                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17779                 }
17780                 break;
17781 #if(BOARD_FILES >= 10)
17782             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17783                 p++; emptycount=10;
17784                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17785                 while (emptycount--)
17786                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17787 #endif
17788             } else if (*p == '*') {
17789                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17790             } else if (isdigit(*p)) {
17791                 emptycount = *p++ - '0';
17792                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17793                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17794                 while (emptycount--)
17795                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17796             } else if (*p == '+' || isalpha(*p)) {
17797                 if (j >= gameInfo.boardWidth) return FALSE;
17798                 if(*p=='+') {
17799                     piece = CharToPiece(*++p);
17800                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17801                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17802                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17803                 } else piece = CharToPiece(*p++);
17804
17805                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17806                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17807                     piece = (ChessSquare) (PROMOTED piece);
17808                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17809                     p++;
17810                 }
17811                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17812             } else {
17813                 return FALSE;
17814             }
17815         }
17816     }
17817     while (*p == '/' || *p == ' ') p++;
17818
17819     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17820
17821     /* [HGM] by default clear Crazyhouse holdings, if present */
17822     if(gameInfo.holdingsWidth) {
17823        for(i=0; i<BOARD_HEIGHT; i++) {
17824            board[i][0]             = EmptySquare; /* black holdings */
17825            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17826            board[i][1]             = (ChessSquare) 0; /* black counts */
17827            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17828        }
17829     }
17830
17831     /* [HGM] look for Crazyhouse holdings here */
17832     while(*p==' ') p++;
17833     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17834         if(*p == '[') p++;
17835         if(*p == '-' ) p++; /* empty holdings */ else {
17836             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17837             /* if we would allow FEN reading to set board size, we would   */
17838             /* have to add holdings and shift the board read so far here   */
17839             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17840                 p++;
17841                 if((int) piece >= (int) BlackPawn ) {
17842                     i = (int)piece - (int)BlackPawn;
17843                     i = PieceToNumber((ChessSquare)i);
17844                     if( i >= gameInfo.holdingsSize ) return FALSE;
17845                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17846                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17847                 } else {
17848                     i = (int)piece - (int)WhitePawn;
17849                     i = PieceToNumber((ChessSquare)i);
17850                     if( i >= gameInfo.holdingsSize ) return FALSE;
17851                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17852                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17853                 }
17854             }
17855         }
17856         if(*p == ']') p++;
17857     }
17858
17859     while(*p == ' ') p++;
17860
17861     /* Active color */
17862     c = *p++;
17863     if(appData.colorNickNames) {
17864       if( c == appData.colorNickNames[0] ) c = 'w'; else
17865       if( c == appData.colorNickNames[1] ) c = 'b';
17866     }
17867     switch (c) {
17868       case 'w':
17869         *blackPlaysFirst = FALSE;
17870         break;
17871       case 'b':
17872         *blackPlaysFirst = TRUE;
17873         break;
17874       default:
17875         return FALSE;
17876     }
17877
17878     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17879     /* return the extra info in global variiables             */
17880
17881     /* set defaults in case FEN is incomplete */
17882     board[EP_STATUS] = EP_UNKNOWN;
17883     for(i=0; i<nrCastlingRights; i++ ) {
17884         board[CASTLING][i] =
17885             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17886     }   /* assume possible unless obviously impossible */
17887     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17888     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17889     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17890                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17891     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17892     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17893     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17894                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17895     FENrulePlies = 0;
17896
17897     while(*p==' ') p++;
17898     if(nrCastlingRights) {
17899       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17900       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17901           /* castling indicator present, so default becomes no castlings */
17902           for(i=0; i<nrCastlingRights; i++ ) {
17903                  board[CASTLING][i] = NoRights;
17904           }
17905       }
17906       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17907              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17908              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17909              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17910         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17911
17912         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17913             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17914             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17915         }
17916         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17917             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17918         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17919                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17920         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17921                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17922         switch(c) {
17923           case'K':
17924               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17925               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17926               board[CASTLING][2] = whiteKingFile;
17927               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17928               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17929               break;
17930           case'Q':
17931               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17932               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17933               board[CASTLING][2] = whiteKingFile;
17934               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17935               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17936               break;
17937           case'k':
17938               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17939               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17940               board[CASTLING][5] = blackKingFile;
17941               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17942               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17943               break;
17944           case'q':
17945               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17946               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17947               board[CASTLING][5] = blackKingFile;
17948               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17949               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17950           case '-':
17951               break;
17952           default: /* FRC castlings */
17953               if(c >= 'a') { /* black rights */
17954                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17955                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17956                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17957                   if(i == BOARD_RGHT) break;
17958                   board[CASTLING][5] = i;
17959                   c -= AAA;
17960                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17961                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17962                   if(c > i)
17963                       board[CASTLING][3] = c;
17964                   else
17965                       board[CASTLING][4] = c;
17966               } else { /* white rights */
17967                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17968                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17969                     if(board[0][i] == WhiteKing) break;
17970                   if(i == BOARD_RGHT) break;
17971                   board[CASTLING][2] = i;
17972                   c -= AAA - 'a' + 'A';
17973                   if(board[0][c] >= WhiteKing) break;
17974                   if(c > i)
17975                       board[CASTLING][0] = c;
17976                   else
17977                       board[CASTLING][1] = c;
17978               }
17979         }
17980       }
17981       for(i=0; i<nrCastlingRights; i++)
17982         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17983       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17984     if (appData.debugMode) {
17985         fprintf(debugFP, "FEN castling rights:");
17986         for(i=0; i<nrCastlingRights; i++)
17987         fprintf(debugFP, " %d", board[CASTLING][i]);
17988         fprintf(debugFP, "\n");
17989     }
17990
17991       while(*p==' ') p++;
17992     }
17993
17994     /* read e.p. field in games that know e.p. capture */
17995     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17996        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17997        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17998       if(*p=='-') {
17999         p++; board[EP_STATUS] = EP_NONE;
18000       } else {
18001          char c = *p++ - AAA;
18002
18003          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18004          if(*p >= '0' && *p <='9') p++;
18005          board[EP_STATUS] = c;
18006       }
18007     }
18008
18009
18010     if(sscanf(p, "%d", &i) == 1) {
18011         FENrulePlies = i; /* 50-move ply counter */
18012         /* (The move number is still ignored)    */
18013     }
18014
18015     return TRUE;
18016 }
18017
18018 void
18019 EditPositionPasteFEN (char *fen)
18020 {
18021   if (fen != NULL) {
18022     Board initial_position;
18023
18024     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18025       DisplayError(_("Bad FEN position in clipboard"), 0);
18026       return ;
18027     } else {
18028       int savedBlackPlaysFirst = blackPlaysFirst;
18029       EditPositionEvent();
18030       blackPlaysFirst = savedBlackPlaysFirst;
18031       CopyBoard(boards[0], initial_position);
18032       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18033       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18034       DisplayBothClocks();
18035       DrawPosition(FALSE, boards[currentMove]);
18036     }
18037   }
18038 }
18039
18040 static char cseq[12] = "\\   ";
18041
18042 Boolean
18043 set_cont_sequence (char *new_seq)
18044 {
18045     int len;
18046     Boolean ret;
18047
18048     // handle bad attempts to set the sequence
18049         if (!new_seq)
18050                 return 0; // acceptable error - no debug
18051
18052     len = strlen(new_seq);
18053     ret = (len > 0) && (len < sizeof(cseq));
18054     if (ret)
18055       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18056     else if (appData.debugMode)
18057       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18058     return ret;
18059 }
18060
18061 /*
18062     reformat a source message so words don't cross the width boundary.  internal
18063     newlines are not removed.  returns the wrapped size (no null character unless
18064     included in source message).  If dest is NULL, only calculate the size required
18065     for the dest buffer.  lp argument indicats line position upon entry, and it's
18066     passed back upon exit.
18067 */
18068 int
18069 wrap (char *dest, char *src, int count, int width, int *lp)
18070 {
18071     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18072
18073     cseq_len = strlen(cseq);
18074     old_line = line = *lp;
18075     ansi = len = clen = 0;
18076
18077     for (i=0; i < count; i++)
18078     {
18079         if (src[i] == '\033')
18080             ansi = 1;
18081
18082         // if we hit the width, back up
18083         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18084         {
18085             // store i & len in case the word is too long
18086             old_i = i, old_len = len;
18087
18088             // find the end of the last word
18089             while (i && src[i] != ' ' && src[i] != '\n')
18090             {
18091                 i--;
18092                 len--;
18093             }
18094
18095             // word too long?  restore i & len before splitting it
18096             if ((old_i-i+clen) >= width)
18097             {
18098                 i = old_i;
18099                 len = old_len;
18100             }
18101
18102             // extra space?
18103             if (i && src[i-1] == ' ')
18104                 len--;
18105
18106             if (src[i] != ' ' && src[i] != '\n')
18107             {
18108                 i--;
18109                 if (len)
18110                     len--;
18111             }
18112
18113             // now append the newline and continuation sequence
18114             if (dest)
18115                 dest[len] = '\n';
18116             len++;
18117             if (dest)
18118                 strncpy(dest+len, cseq, cseq_len);
18119             len += cseq_len;
18120             line = cseq_len;
18121             clen = cseq_len;
18122             continue;
18123         }
18124
18125         if (dest)
18126             dest[len] = src[i];
18127         len++;
18128         if (!ansi)
18129             line++;
18130         if (src[i] == '\n')
18131             line = 0;
18132         if (src[i] == 'm')
18133             ansi = 0;
18134     }
18135     if (dest && appData.debugMode)
18136     {
18137         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18138             count, width, line, len, *lp);
18139         show_bytes(debugFP, src, count);
18140         fprintf(debugFP, "\ndest: ");
18141         show_bytes(debugFP, dest, len);
18142         fprintf(debugFP, "\n");
18143     }
18144     *lp = dest ? line : old_line;
18145
18146     return len;
18147 }
18148
18149 // [HGM] vari: routines for shelving variations
18150 Boolean modeRestore = FALSE;
18151
18152 void
18153 PushInner (int firstMove, int lastMove)
18154 {
18155         int i, j, nrMoves = lastMove - firstMove;
18156
18157         // push current tail of game on stack
18158         savedResult[storedGames] = gameInfo.result;
18159         savedDetails[storedGames] = gameInfo.resultDetails;
18160         gameInfo.resultDetails = NULL;
18161         savedFirst[storedGames] = firstMove;
18162         savedLast [storedGames] = lastMove;
18163         savedFramePtr[storedGames] = framePtr;
18164         framePtr -= nrMoves; // reserve space for the boards
18165         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18166             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18167             for(j=0; j<MOVE_LEN; j++)
18168                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18169             for(j=0; j<2*MOVE_LEN; j++)
18170                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18171             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18172             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18173             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18174             pvInfoList[firstMove+i-1].depth = 0;
18175             commentList[framePtr+i] = commentList[firstMove+i];
18176             commentList[firstMove+i] = NULL;
18177         }
18178
18179         storedGames++;
18180         forwardMostMove = firstMove; // truncate game so we can start variation
18181 }
18182
18183 void
18184 PushTail (int firstMove, int lastMove)
18185 {
18186         if(appData.icsActive) { // only in local mode
18187                 forwardMostMove = currentMove; // mimic old ICS behavior
18188                 return;
18189         }
18190         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18191
18192         PushInner(firstMove, lastMove);
18193         if(storedGames == 1) GreyRevert(FALSE);
18194         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18195 }
18196
18197 void
18198 PopInner (Boolean annotate)
18199 {
18200         int i, j, nrMoves;
18201         char buf[8000], moveBuf[20];
18202
18203         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18204         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18205         nrMoves = savedLast[storedGames] - currentMove;
18206         if(annotate) {
18207                 int cnt = 10;
18208                 if(!WhiteOnMove(currentMove))
18209                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18210                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18211                 for(i=currentMove; i<forwardMostMove; i++) {
18212                         if(WhiteOnMove(i))
18213                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18214                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18215                         strcat(buf, moveBuf);
18216                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18217                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18218                 }
18219                 strcat(buf, ")");
18220         }
18221         for(i=1; i<=nrMoves; i++) { // copy last variation back
18222             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18223             for(j=0; j<MOVE_LEN; j++)
18224                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18225             for(j=0; j<2*MOVE_LEN; j++)
18226                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18227             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18228             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18229             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18230             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18231             commentList[currentMove+i] = commentList[framePtr+i];
18232             commentList[framePtr+i] = NULL;
18233         }
18234         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18235         framePtr = savedFramePtr[storedGames];
18236         gameInfo.result = savedResult[storedGames];
18237         if(gameInfo.resultDetails != NULL) {
18238             free(gameInfo.resultDetails);
18239       }
18240         gameInfo.resultDetails = savedDetails[storedGames];
18241         forwardMostMove = currentMove + nrMoves;
18242 }
18243
18244 Boolean
18245 PopTail (Boolean annotate)
18246 {
18247         if(appData.icsActive) return FALSE; // only in local mode
18248         if(!storedGames) return FALSE; // sanity
18249         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18250
18251         PopInner(annotate);
18252         if(currentMove < forwardMostMove) ForwardEvent(); else
18253         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18254
18255         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18256         return TRUE;
18257 }
18258
18259 void
18260 CleanupTail ()
18261 {       // remove all shelved variations
18262         int i;
18263         for(i=0; i<storedGames; i++) {
18264             if(savedDetails[i])
18265                 free(savedDetails[i]);
18266             savedDetails[i] = NULL;
18267         }
18268         for(i=framePtr; i<MAX_MOVES; i++) {
18269                 if(commentList[i]) free(commentList[i]);
18270                 commentList[i] = NULL;
18271         }
18272         framePtr = MAX_MOVES-1;
18273         storedGames = 0;
18274 }
18275
18276 void
18277 LoadVariation (int index, char *text)
18278 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18279         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18280         int level = 0, move;
18281
18282         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18283         // first find outermost bracketing variation
18284         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18285             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18286                 if(*p == '{') wait = '}'; else
18287                 if(*p == '[') wait = ']'; else
18288                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18289                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18290             }
18291             if(*p == wait) wait = NULLCHAR; // closing ]} found
18292             p++;
18293         }
18294         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18295         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18296         end[1] = NULLCHAR; // clip off comment beyond variation
18297         ToNrEvent(currentMove-1);
18298         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18299         // kludge: use ParsePV() to append variation to game
18300         move = currentMove;
18301         ParsePV(start, TRUE, TRUE);
18302         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18303         ClearPremoveHighlights();
18304         CommentPopDown();
18305         ToNrEvent(currentMove+1);
18306 }
18307
18308 void
18309 LoadTheme ()
18310 {
18311     char *p, *q, buf[MSG_SIZ];
18312     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18313         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18314         ParseArgsFromString(buf);
18315         ActivateTheme(TRUE); // also redo colors
18316         return;
18317     }
18318     p = nickName;
18319     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18320     {
18321         int len;
18322         q = appData.themeNames;
18323         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18324       if(appData.useBitmaps) {
18325         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18326                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18327                 appData.liteBackTextureMode,
18328                 appData.darkBackTextureMode );
18329       } else {
18330         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18331                 Col2Text(2),   // lightSquareColor
18332                 Col2Text(3) ); // darkSquareColor
18333       }
18334       if(appData.useBorder) {
18335         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18336                 appData.border);
18337       } else {
18338         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18339       }
18340       if(appData.useFont) {
18341         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18342                 appData.renderPiecesWithFont,
18343                 appData.fontToPieceTable,
18344                 Col2Text(9),    // appData.fontBackColorWhite
18345                 Col2Text(10) ); // appData.fontForeColorBlack
18346       } else {
18347         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18348                 appData.pieceDirectory);
18349         if(!appData.pieceDirectory[0])
18350           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18351                 Col2Text(0),   // whitePieceColor
18352                 Col2Text(1) ); // blackPieceColor
18353       }
18354       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18355                 Col2Text(4),   // highlightSquareColor
18356                 Col2Text(5) ); // premoveHighlightColor
18357         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18358         if(insert != q) insert[-1] = NULLCHAR;
18359         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18360         if(q)   free(q);
18361     }
18362     ActivateTheme(FALSE);
18363 }