Allow hide/show of columns in Engine Output
[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) gameMode = AnalyzeMode;
14215     pausing = FALSE;
14216     ModeHighlight();
14217     SetGameInfo();
14218
14219     StartAnalysisClock();
14220     GetTimeMark(&lastNodeCountTime);
14221     lastNodeCount = 0;
14222     return 1;
14223 }
14224
14225 void
14226 AnalyzeFileEvent ()
14227 {
14228     if (appData.noChessProgram || gameMode == AnalyzeFile)
14229       return;
14230
14231     if (!first.analysisSupport) {
14232       char buf[MSG_SIZ];
14233       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14234       DisplayError(buf, 0);
14235       return;
14236     }
14237
14238     if (gameMode != AnalyzeMode) {
14239         keepInfo = 1; // mere annotating should not alter PGN tags
14240         EditGameEvent();
14241         keepInfo = 0;
14242         if (gameMode != EditGame) return;
14243         if (!appData.showThinking) ToggleShowThinking();
14244         ResurrectChessProgram();
14245         SendToProgram("analyze\n", &first);
14246         first.analyzing = TRUE;
14247         /*first.maybeThinking = TRUE;*/
14248         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14249         EngineOutputPopUp();
14250     }
14251     gameMode = AnalyzeFile;
14252     pausing = FALSE;
14253     ModeHighlight();
14254
14255     StartAnalysisClock();
14256     GetTimeMark(&lastNodeCountTime);
14257     lastNodeCount = 0;
14258     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14259     AnalysisPeriodicEvent(1);
14260 }
14261
14262 void
14263 MachineWhiteEvent ()
14264 {
14265     char buf[MSG_SIZ];
14266     char *bookHit = NULL;
14267
14268     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14269       return;
14270
14271
14272     if (gameMode == PlayFromGameFile ||
14273         gameMode == TwoMachinesPlay  ||
14274         gameMode == Training         ||
14275         gameMode == AnalyzeMode      ||
14276         gameMode == EndOfGame)
14277         EditGameEvent();
14278
14279     if (gameMode == EditPosition)
14280         EditPositionDone(TRUE);
14281
14282     if (!WhiteOnMove(currentMove)) {
14283         DisplayError(_("It is not White's turn"), 0);
14284         return;
14285     }
14286
14287     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14288       ExitAnalyzeMode();
14289
14290     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14291         gameMode == AnalyzeFile)
14292         TruncateGame();
14293
14294     ResurrectChessProgram();    /* in case it isn't running */
14295     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14296         gameMode = MachinePlaysWhite;
14297         ResetClocks();
14298     } else
14299     gameMode = MachinePlaysWhite;
14300     pausing = FALSE;
14301     ModeHighlight();
14302     SetGameInfo();
14303     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14304     DisplayTitle(buf);
14305     if (first.sendName) {
14306       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14307       SendToProgram(buf, &first);
14308     }
14309     if (first.sendTime) {
14310       if (first.useColors) {
14311         SendToProgram("black\n", &first); /*gnu kludge*/
14312       }
14313       SendTimeRemaining(&first, TRUE);
14314     }
14315     if (first.useColors) {
14316       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14317     }
14318     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14319     SetMachineThinkingEnables();
14320     first.maybeThinking = TRUE;
14321     StartClocks();
14322     firstMove = FALSE;
14323
14324     if (appData.autoFlipView && !flipView) {
14325       flipView = !flipView;
14326       DrawPosition(FALSE, NULL);
14327       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14328     }
14329
14330     if(bookHit) { // [HGM] book: simulate book reply
14331         static char bookMove[MSG_SIZ]; // a bit generous?
14332
14333         programStats.nodes = programStats.depth = programStats.time =
14334         programStats.score = programStats.got_only_move = 0;
14335         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14336
14337         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14338         strcat(bookMove, bookHit);
14339         HandleMachineMove(bookMove, &first);
14340     }
14341 }
14342
14343 void
14344 MachineBlackEvent ()
14345 {
14346   char buf[MSG_SIZ];
14347   char *bookHit = NULL;
14348
14349     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14350         return;
14351
14352
14353     if (gameMode == PlayFromGameFile ||
14354         gameMode == TwoMachinesPlay  ||
14355         gameMode == Training         ||
14356         gameMode == AnalyzeMode      ||
14357         gameMode == EndOfGame)
14358         EditGameEvent();
14359
14360     if (gameMode == EditPosition)
14361         EditPositionDone(TRUE);
14362
14363     if (WhiteOnMove(currentMove)) {
14364         DisplayError(_("It is not Black's turn"), 0);
14365         return;
14366     }
14367
14368     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14369       ExitAnalyzeMode();
14370
14371     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14372         gameMode == AnalyzeFile)
14373         TruncateGame();
14374
14375     ResurrectChessProgram();    /* in case it isn't running */
14376     gameMode = MachinePlaysBlack;
14377     pausing = FALSE;
14378     ModeHighlight();
14379     SetGameInfo();
14380     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14381     DisplayTitle(buf);
14382     if (first.sendName) {
14383       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14384       SendToProgram(buf, &first);
14385     }
14386     if (first.sendTime) {
14387       if (first.useColors) {
14388         SendToProgram("white\n", &first); /*gnu kludge*/
14389       }
14390       SendTimeRemaining(&first, FALSE);
14391     }
14392     if (first.useColors) {
14393       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14394     }
14395     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14396     SetMachineThinkingEnables();
14397     first.maybeThinking = TRUE;
14398     StartClocks();
14399
14400     if (appData.autoFlipView && flipView) {
14401       flipView = !flipView;
14402       DrawPosition(FALSE, NULL);
14403       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14404     }
14405     if(bookHit) { // [HGM] book: simulate book reply
14406         static char bookMove[MSG_SIZ]; // a bit generous?
14407
14408         programStats.nodes = programStats.depth = programStats.time =
14409         programStats.score = programStats.got_only_move = 0;
14410         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14411
14412         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14413         strcat(bookMove, bookHit);
14414         HandleMachineMove(bookMove, &first);
14415     }
14416 }
14417
14418
14419 void
14420 DisplayTwoMachinesTitle ()
14421 {
14422     char buf[MSG_SIZ];
14423     if (appData.matchGames > 0) {
14424         if(appData.tourneyFile[0]) {
14425           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14426                    gameInfo.white, _("vs."), gameInfo.black,
14427                    nextGame+1, appData.matchGames+1,
14428                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14429         } else
14430         if (first.twoMachinesColor[0] == 'w') {
14431           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14432                    gameInfo.white, _("vs."),  gameInfo.black,
14433                    first.matchWins, second.matchWins,
14434                    matchGame - 1 - (first.matchWins + second.matchWins));
14435         } else {
14436           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14437                    gameInfo.white, _("vs."), gameInfo.black,
14438                    second.matchWins, first.matchWins,
14439                    matchGame - 1 - (first.matchWins + second.matchWins));
14440         }
14441     } else {
14442       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14443     }
14444     DisplayTitle(buf);
14445 }
14446
14447 void
14448 SettingsMenuIfReady ()
14449 {
14450   if (second.lastPing != second.lastPong) {
14451     DisplayMessage("", _("Waiting for second chess program"));
14452     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14453     return;
14454   }
14455   ThawUI();
14456   DisplayMessage("", "");
14457   SettingsPopUp(&second);
14458 }
14459
14460 int
14461 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14462 {
14463     char buf[MSG_SIZ];
14464     if (cps->pr == NoProc) {
14465         StartChessProgram(cps);
14466         if (cps->protocolVersion == 1) {
14467           retry();
14468           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14469         } else {
14470           /* kludge: allow timeout for initial "feature" command */
14471           if(retry != TwoMachinesEventIfReady) FreezeUI();
14472           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14473           DisplayMessage("", buf);
14474           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14475         }
14476         return 1;
14477     }
14478     return 0;
14479 }
14480
14481 void
14482 TwoMachinesEvent P((void))
14483 {
14484     int i;
14485     char buf[MSG_SIZ];
14486     ChessProgramState *onmove;
14487     char *bookHit = NULL;
14488     static int stalling = 0;
14489     TimeMark now;
14490     long wait;
14491
14492     if (appData.noChessProgram) return;
14493
14494     switch (gameMode) {
14495       case TwoMachinesPlay:
14496         return;
14497       case MachinePlaysWhite:
14498       case MachinePlaysBlack:
14499         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14500             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14501             return;
14502         }
14503         /* fall through */
14504       case BeginningOfGame:
14505       case PlayFromGameFile:
14506       case EndOfGame:
14507         EditGameEvent();
14508         if (gameMode != EditGame) return;
14509         break;
14510       case EditPosition:
14511         EditPositionDone(TRUE);
14512         break;
14513       case AnalyzeMode:
14514       case AnalyzeFile:
14515         ExitAnalyzeMode();
14516         break;
14517       case EditGame:
14518       default:
14519         break;
14520     }
14521
14522 //    forwardMostMove = currentMove;
14523     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14524     startingEngine = TRUE;
14525
14526     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14527
14528     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14529     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14530       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14531       return;
14532     }
14533     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14534
14535     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14536                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14537         startingEngine = FALSE;
14538         DisplayError("second engine does not play this", 0);
14539         return;
14540     }
14541
14542     if(!stalling) {
14543       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14544       SendToProgram("force\n", &second);
14545       stalling = 1;
14546       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14547       return;
14548     }
14549     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14550     if(appData.matchPause>10000 || appData.matchPause<10)
14551                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14552     wait = SubtractTimeMarks(&now, &pauseStart);
14553     if(wait < appData.matchPause) {
14554         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14555         return;
14556     }
14557     // we are now committed to starting the game
14558     stalling = 0;
14559     DisplayMessage("", "");
14560     if (startedFromSetupPosition) {
14561         SendBoard(&second, backwardMostMove);
14562     if (appData.debugMode) {
14563         fprintf(debugFP, "Two Machines\n");
14564     }
14565     }
14566     for (i = backwardMostMove; i < forwardMostMove; i++) {
14567         SendMoveToProgram(i, &second);
14568     }
14569
14570     gameMode = TwoMachinesPlay;
14571     pausing = startingEngine = FALSE;
14572     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14573     SetGameInfo();
14574     DisplayTwoMachinesTitle();
14575     firstMove = TRUE;
14576     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14577         onmove = &first;
14578     } else {
14579         onmove = &second;
14580     }
14581     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14582     SendToProgram(first.computerString, &first);
14583     if (first.sendName) {
14584       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14585       SendToProgram(buf, &first);
14586     }
14587     SendToProgram(second.computerString, &second);
14588     if (second.sendName) {
14589       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14590       SendToProgram(buf, &second);
14591     }
14592
14593     ResetClocks();
14594     if (!first.sendTime || !second.sendTime) {
14595         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14596         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14597     }
14598     if (onmove->sendTime) {
14599       if (onmove->useColors) {
14600         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14601       }
14602       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14603     }
14604     if (onmove->useColors) {
14605       SendToProgram(onmove->twoMachinesColor, onmove);
14606     }
14607     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14608 //    SendToProgram("go\n", onmove);
14609     onmove->maybeThinking = TRUE;
14610     SetMachineThinkingEnables();
14611
14612     StartClocks();
14613
14614     if(bookHit) { // [HGM] book: simulate book reply
14615         static char bookMove[MSG_SIZ]; // a bit generous?
14616
14617         programStats.nodes = programStats.depth = programStats.time =
14618         programStats.score = programStats.got_only_move = 0;
14619         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14620
14621         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14622         strcat(bookMove, bookHit);
14623         savedMessage = bookMove; // args for deferred call
14624         savedState = onmove;
14625         ScheduleDelayedEvent(DeferredBookMove, 1);
14626     }
14627 }
14628
14629 void
14630 TrainingEvent ()
14631 {
14632     if (gameMode == Training) {
14633       SetTrainingModeOff();
14634       gameMode = PlayFromGameFile;
14635       DisplayMessage("", _("Training mode off"));
14636     } else {
14637       gameMode = Training;
14638       animateTraining = appData.animate;
14639
14640       /* make sure we are not already at the end of the game */
14641       if (currentMove < forwardMostMove) {
14642         SetTrainingModeOn();
14643         DisplayMessage("", _("Training mode on"));
14644       } else {
14645         gameMode = PlayFromGameFile;
14646         DisplayError(_("Already at end of game"), 0);
14647       }
14648     }
14649     ModeHighlight();
14650 }
14651
14652 void
14653 IcsClientEvent ()
14654 {
14655     if (!appData.icsActive) return;
14656     switch (gameMode) {
14657       case IcsPlayingWhite:
14658       case IcsPlayingBlack:
14659       case IcsObserving:
14660       case IcsIdle:
14661       case BeginningOfGame:
14662       case IcsExamining:
14663         return;
14664
14665       case EditGame:
14666         break;
14667
14668       case EditPosition:
14669         EditPositionDone(TRUE);
14670         break;
14671
14672       case AnalyzeMode:
14673       case AnalyzeFile:
14674         ExitAnalyzeMode();
14675         break;
14676
14677       default:
14678         EditGameEvent();
14679         break;
14680     }
14681
14682     gameMode = IcsIdle;
14683     ModeHighlight();
14684     return;
14685 }
14686
14687 void
14688 EditGameEvent ()
14689 {
14690     int i;
14691
14692     switch (gameMode) {
14693       case Training:
14694         SetTrainingModeOff();
14695         break;
14696       case MachinePlaysWhite:
14697       case MachinePlaysBlack:
14698       case BeginningOfGame:
14699         SendToProgram("force\n", &first);
14700         SetUserThinkingEnables();
14701         break;
14702       case PlayFromGameFile:
14703         (void) StopLoadGameTimer();
14704         if (gameFileFP != NULL) {
14705             gameFileFP = NULL;
14706         }
14707         break;
14708       case EditPosition:
14709         EditPositionDone(TRUE);
14710         break;
14711       case AnalyzeMode:
14712       case AnalyzeFile:
14713         ExitAnalyzeMode();
14714         SendToProgram("force\n", &first);
14715         break;
14716       case TwoMachinesPlay:
14717         GameEnds(EndOfFile, NULL, GE_PLAYER);
14718         ResurrectChessProgram();
14719         SetUserThinkingEnables();
14720         break;
14721       case EndOfGame:
14722         ResurrectChessProgram();
14723         break;
14724       case IcsPlayingBlack:
14725       case IcsPlayingWhite:
14726         DisplayError(_("Warning: You are still playing a game"), 0);
14727         break;
14728       case IcsObserving:
14729         DisplayError(_("Warning: You are still observing a game"), 0);
14730         break;
14731       case IcsExamining:
14732         DisplayError(_("Warning: You are still examining a game"), 0);
14733         break;
14734       case IcsIdle:
14735         break;
14736       case EditGame:
14737       default:
14738         return;
14739     }
14740
14741     pausing = FALSE;
14742     StopClocks();
14743     first.offeredDraw = second.offeredDraw = 0;
14744
14745     if (gameMode == PlayFromGameFile) {
14746         whiteTimeRemaining = timeRemaining[0][currentMove];
14747         blackTimeRemaining = timeRemaining[1][currentMove];
14748         DisplayTitle("");
14749     }
14750
14751     if (gameMode == MachinePlaysWhite ||
14752         gameMode == MachinePlaysBlack ||
14753         gameMode == TwoMachinesPlay ||
14754         gameMode == EndOfGame) {
14755         i = forwardMostMove;
14756         while (i > currentMove) {
14757             SendToProgram("undo\n", &first);
14758             i--;
14759         }
14760         if(!adjustedClock) {
14761         whiteTimeRemaining = timeRemaining[0][currentMove];
14762         blackTimeRemaining = timeRemaining[1][currentMove];
14763         DisplayBothClocks();
14764         }
14765         if (whiteFlag || blackFlag) {
14766             whiteFlag = blackFlag = 0;
14767         }
14768         DisplayTitle("");
14769     }
14770
14771     gameMode = EditGame;
14772     ModeHighlight();
14773     SetGameInfo();
14774 }
14775
14776
14777 void
14778 EditPositionEvent ()
14779 {
14780     if (gameMode == EditPosition) {
14781         EditGameEvent();
14782         return;
14783     }
14784
14785     EditGameEvent();
14786     if (gameMode != EditGame) return;
14787
14788     gameMode = EditPosition;
14789     ModeHighlight();
14790     SetGameInfo();
14791     if (currentMove > 0)
14792       CopyBoard(boards[0], boards[currentMove]);
14793
14794     blackPlaysFirst = !WhiteOnMove(currentMove);
14795     ResetClocks();
14796     currentMove = forwardMostMove = backwardMostMove = 0;
14797     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14798     DisplayMove(-1);
14799     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14800 }
14801
14802 void
14803 ExitAnalyzeMode ()
14804 {
14805     /* [DM] icsEngineAnalyze - possible call from other functions */
14806     if (appData.icsEngineAnalyze) {
14807         appData.icsEngineAnalyze = FALSE;
14808
14809         DisplayMessage("",_("Close ICS engine analyze..."));
14810     }
14811     if (first.analysisSupport && first.analyzing) {
14812       SendToBoth("exit\n");
14813       first.analyzing = second.analyzing = FALSE;
14814     }
14815     thinkOutput[0] = NULLCHAR;
14816 }
14817
14818 void
14819 EditPositionDone (Boolean fakeRights)
14820 {
14821     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14822
14823     startedFromSetupPosition = TRUE;
14824     InitChessProgram(&first, FALSE);
14825     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14826       boards[0][EP_STATUS] = EP_NONE;
14827       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14828       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14829         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14830         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14831       } else boards[0][CASTLING][2] = NoRights;
14832       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14833         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14834         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14835       } else boards[0][CASTLING][5] = NoRights;
14836       if(gameInfo.variant == VariantSChess) {
14837         int i;
14838         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14839           boards[0][VIRGIN][i] = 0;
14840           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14841           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14842         }
14843       }
14844     }
14845     SendToProgram("force\n", &first);
14846     if (blackPlaysFirst) {
14847         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14848         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14849         currentMove = forwardMostMove = backwardMostMove = 1;
14850         CopyBoard(boards[1], boards[0]);
14851     } else {
14852         currentMove = forwardMostMove = backwardMostMove = 0;
14853     }
14854     SendBoard(&first, forwardMostMove);
14855     if (appData.debugMode) {
14856         fprintf(debugFP, "EditPosDone\n");
14857     }
14858     DisplayTitle("");
14859     DisplayMessage("", "");
14860     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14861     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14862     gameMode = EditGame;
14863     ModeHighlight();
14864     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14865     ClearHighlights(); /* [AS] */
14866 }
14867
14868 /* Pause for `ms' milliseconds */
14869 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14870 void
14871 TimeDelay (long ms)
14872 {
14873     TimeMark m1, m2;
14874
14875     GetTimeMark(&m1);
14876     do {
14877         GetTimeMark(&m2);
14878     } while (SubtractTimeMarks(&m2, &m1) < ms);
14879 }
14880
14881 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14882 void
14883 SendMultiLineToICS (char *buf)
14884 {
14885     char temp[MSG_SIZ+1], *p;
14886     int len;
14887
14888     len = strlen(buf);
14889     if (len > MSG_SIZ)
14890       len = MSG_SIZ;
14891
14892     strncpy(temp, buf, len);
14893     temp[len] = 0;
14894
14895     p = temp;
14896     while (*p) {
14897         if (*p == '\n' || *p == '\r')
14898           *p = ' ';
14899         ++p;
14900     }
14901
14902     strcat(temp, "\n");
14903     SendToICS(temp);
14904     SendToPlayer(temp, strlen(temp));
14905 }
14906
14907 void
14908 SetWhiteToPlayEvent ()
14909 {
14910     if (gameMode == EditPosition) {
14911         blackPlaysFirst = FALSE;
14912         DisplayBothClocks();    /* works because currentMove is 0 */
14913     } else if (gameMode == IcsExamining) {
14914         SendToICS(ics_prefix);
14915         SendToICS("tomove white\n");
14916     }
14917 }
14918
14919 void
14920 SetBlackToPlayEvent ()
14921 {
14922     if (gameMode == EditPosition) {
14923         blackPlaysFirst = TRUE;
14924         currentMove = 1;        /* kludge */
14925         DisplayBothClocks();
14926         currentMove = 0;
14927     } else if (gameMode == IcsExamining) {
14928         SendToICS(ics_prefix);
14929         SendToICS("tomove black\n");
14930     }
14931 }
14932
14933 void
14934 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14935 {
14936     char buf[MSG_SIZ];
14937     ChessSquare piece = boards[0][y][x];
14938     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14939     static int lastVariant;
14940
14941     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14942
14943     switch (selection) {
14944       case ClearBoard:
14945         CopyBoard(currentBoard, boards[0]);
14946         CopyBoard(menuBoard, initialPosition);
14947         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14948             SendToICS(ics_prefix);
14949             SendToICS("bsetup clear\n");
14950         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14951             SendToICS(ics_prefix);
14952             SendToICS("clearboard\n");
14953         } else {
14954             int nonEmpty = 0;
14955             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14956                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14957                 for (y = 0; y < BOARD_HEIGHT; y++) {
14958                     if (gameMode == IcsExamining) {
14959                         if (boards[currentMove][y][x] != EmptySquare) {
14960                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14961                                     AAA + x, ONE + y);
14962                             SendToICS(buf);
14963                         }
14964                     } else {
14965                         if(boards[0][y][x] != p) nonEmpty++;
14966                         boards[0][y][x] = p;
14967                     }
14968                 }
14969                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14970             }
14971             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14972                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14973                     ChessSquare p = menuBoard[0][x];
14974                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14975                     p = menuBoard[BOARD_HEIGHT-1][x];
14976                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14977                 }
14978                 DisplayMessage("Clicking clock again restores position", "");
14979                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14980                 if(!nonEmpty) { // asked to clear an empty board
14981                     CopyBoard(boards[0], menuBoard);
14982                 } else
14983                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14984                     CopyBoard(boards[0], initialPosition);
14985                 } else
14986                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14987                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14988                     CopyBoard(boards[0], erasedBoard);
14989                 } else
14990                     CopyBoard(erasedBoard, currentBoard);
14991
14992             }
14993         }
14994         if (gameMode == EditPosition) {
14995             DrawPosition(FALSE, boards[0]);
14996         }
14997         break;
14998
14999       case WhitePlay:
15000         SetWhiteToPlayEvent();
15001         break;
15002
15003       case BlackPlay:
15004         SetBlackToPlayEvent();
15005         break;
15006
15007       case EmptySquare:
15008         if (gameMode == IcsExamining) {
15009             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15010             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15011             SendToICS(buf);
15012         } else {
15013             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15014                 if(x == BOARD_LEFT-2) {
15015                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15016                     boards[0][y][1] = 0;
15017                 } else
15018                 if(x == BOARD_RGHT+1) {
15019                     if(y >= gameInfo.holdingsSize) break;
15020                     boards[0][y][BOARD_WIDTH-2] = 0;
15021                 } else break;
15022             }
15023             boards[0][y][x] = EmptySquare;
15024             DrawPosition(FALSE, boards[0]);
15025         }
15026         break;
15027
15028       case PromotePiece:
15029         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15030            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15031             selection = (ChessSquare) (PROMOTED piece);
15032         } else if(piece == EmptySquare) selection = WhiteSilver;
15033         else selection = (ChessSquare)((int)piece - 1);
15034         goto defaultlabel;
15035
15036       case DemotePiece:
15037         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15038            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15039             selection = (ChessSquare) (DEMOTED piece);
15040         } else if(piece == EmptySquare) selection = BlackSilver;
15041         else selection = (ChessSquare)((int)piece + 1);
15042         goto defaultlabel;
15043
15044       case WhiteQueen:
15045       case BlackQueen:
15046         if(gameInfo.variant == VariantShatranj ||
15047            gameInfo.variant == VariantXiangqi  ||
15048            gameInfo.variant == VariantCourier  ||
15049            gameInfo.variant == VariantASEAN    ||
15050            gameInfo.variant == VariantMakruk     )
15051             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15052         goto defaultlabel;
15053
15054       case WhiteKing:
15055       case BlackKing:
15056         if(gameInfo.variant == VariantXiangqi)
15057             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15058         if(gameInfo.variant == VariantKnightmate)
15059             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15060       default:
15061         defaultlabel:
15062         if (gameMode == IcsExamining) {
15063             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15064             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15065                      PieceToChar(selection), AAA + x, ONE + y);
15066             SendToICS(buf);
15067         } else {
15068             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15069                 int n;
15070                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15071                     n = PieceToNumber(selection - BlackPawn);
15072                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15073                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15074                     boards[0][BOARD_HEIGHT-1-n][1]++;
15075                 } else
15076                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15077                     n = PieceToNumber(selection);
15078                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15079                     boards[0][n][BOARD_WIDTH-1] = selection;
15080                     boards[0][n][BOARD_WIDTH-2]++;
15081                 }
15082             } else
15083             boards[0][y][x] = selection;
15084             DrawPosition(TRUE, boards[0]);
15085             ClearHighlights();
15086             fromX = fromY = -1;
15087         }
15088         break;
15089     }
15090 }
15091
15092
15093 void
15094 DropMenuEvent (ChessSquare selection, int x, int y)
15095 {
15096     ChessMove moveType;
15097
15098     switch (gameMode) {
15099       case IcsPlayingWhite:
15100       case MachinePlaysBlack:
15101         if (!WhiteOnMove(currentMove)) {
15102             DisplayMoveError(_("It is Black's turn"));
15103             return;
15104         }
15105         moveType = WhiteDrop;
15106         break;
15107       case IcsPlayingBlack:
15108       case MachinePlaysWhite:
15109         if (WhiteOnMove(currentMove)) {
15110             DisplayMoveError(_("It is White's turn"));
15111             return;
15112         }
15113         moveType = BlackDrop;
15114         break;
15115       case EditGame:
15116         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15117         break;
15118       default:
15119         return;
15120     }
15121
15122     if (moveType == BlackDrop && selection < BlackPawn) {
15123       selection = (ChessSquare) ((int) selection
15124                                  + (int) BlackPawn - (int) WhitePawn);
15125     }
15126     if (boards[currentMove][y][x] != EmptySquare) {
15127         DisplayMoveError(_("That square is occupied"));
15128         return;
15129     }
15130
15131     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15132 }
15133
15134 void
15135 AcceptEvent ()
15136 {
15137     /* Accept a pending offer of any kind from opponent */
15138
15139     if (appData.icsActive) {
15140         SendToICS(ics_prefix);
15141         SendToICS("accept\n");
15142     } else if (cmailMsgLoaded) {
15143         if (currentMove == cmailOldMove &&
15144             commentList[cmailOldMove] != NULL &&
15145             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15146                    "Black offers a draw" : "White offers a draw")) {
15147             TruncateGame();
15148             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15149             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15150         } else {
15151             DisplayError(_("There is no pending offer on this move"), 0);
15152             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15153         }
15154     } else {
15155         /* Not used for offers from chess program */
15156     }
15157 }
15158
15159 void
15160 DeclineEvent ()
15161 {
15162     /* Decline a pending offer of any kind from opponent */
15163
15164     if (appData.icsActive) {
15165         SendToICS(ics_prefix);
15166         SendToICS("decline\n");
15167     } else if (cmailMsgLoaded) {
15168         if (currentMove == cmailOldMove &&
15169             commentList[cmailOldMove] != NULL &&
15170             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15171                    "Black offers a draw" : "White offers a draw")) {
15172 #ifdef NOTDEF
15173             AppendComment(cmailOldMove, "Draw declined", TRUE);
15174             DisplayComment(cmailOldMove - 1, "Draw declined");
15175 #endif /*NOTDEF*/
15176         } else {
15177             DisplayError(_("There is no pending offer on this move"), 0);
15178         }
15179     } else {
15180         /* Not used for offers from chess program */
15181     }
15182 }
15183
15184 void
15185 RematchEvent ()
15186 {
15187     /* Issue ICS rematch command */
15188     if (appData.icsActive) {
15189         SendToICS(ics_prefix);
15190         SendToICS("rematch\n");
15191     }
15192 }
15193
15194 void
15195 CallFlagEvent ()
15196 {
15197     /* Call your opponent's flag (claim a win on time) */
15198     if (appData.icsActive) {
15199         SendToICS(ics_prefix);
15200         SendToICS("flag\n");
15201     } else {
15202         switch (gameMode) {
15203           default:
15204             return;
15205           case MachinePlaysWhite:
15206             if (whiteFlag) {
15207                 if (blackFlag)
15208                   GameEnds(GameIsDrawn, "Both players ran out of time",
15209                            GE_PLAYER);
15210                 else
15211                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15212             } else {
15213                 DisplayError(_("Your opponent is not out of time"), 0);
15214             }
15215             break;
15216           case MachinePlaysBlack:
15217             if (blackFlag) {
15218                 if (whiteFlag)
15219                   GameEnds(GameIsDrawn, "Both players ran out of time",
15220                            GE_PLAYER);
15221                 else
15222                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15223             } else {
15224                 DisplayError(_("Your opponent is not out of time"), 0);
15225             }
15226             break;
15227         }
15228     }
15229 }
15230
15231 void
15232 ClockClick (int which)
15233 {       // [HGM] code moved to back-end from winboard.c
15234         if(which) { // black clock
15235           if (gameMode == EditPosition || gameMode == IcsExamining) {
15236             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15237             SetBlackToPlayEvent();
15238           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15239           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15240           } else if (shiftKey) {
15241             AdjustClock(which, -1);
15242           } else if (gameMode == IcsPlayingWhite ||
15243                      gameMode == MachinePlaysBlack) {
15244             CallFlagEvent();
15245           }
15246         } else { // white clock
15247           if (gameMode == EditPosition || gameMode == IcsExamining) {
15248             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15249             SetWhiteToPlayEvent();
15250           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15251           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15252           } else if (shiftKey) {
15253             AdjustClock(which, -1);
15254           } else if (gameMode == IcsPlayingBlack ||
15255                    gameMode == MachinePlaysWhite) {
15256             CallFlagEvent();
15257           }
15258         }
15259 }
15260
15261 void
15262 DrawEvent ()
15263 {
15264     /* Offer draw or accept pending draw offer from opponent */
15265
15266     if (appData.icsActive) {
15267         /* Note: tournament rules require draw offers to be
15268            made after you make your move but before you punch
15269            your clock.  Currently ICS doesn't let you do that;
15270            instead, you immediately punch your clock after making
15271            a move, but you can offer a draw at any time. */
15272
15273         SendToICS(ics_prefix);
15274         SendToICS("draw\n");
15275         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15276     } else if (cmailMsgLoaded) {
15277         if (currentMove == cmailOldMove &&
15278             commentList[cmailOldMove] != NULL &&
15279             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15280                    "Black offers a draw" : "White offers a draw")) {
15281             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15282             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15283         } else if (currentMove == cmailOldMove + 1) {
15284             char *offer = WhiteOnMove(cmailOldMove) ?
15285               "White offers a draw" : "Black offers a draw";
15286             AppendComment(currentMove, offer, TRUE);
15287             DisplayComment(currentMove - 1, offer);
15288             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15289         } else {
15290             DisplayError(_("You must make your move before offering a draw"), 0);
15291             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15292         }
15293     } else if (first.offeredDraw) {
15294         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15295     } else {
15296         if (first.sendDrawOffers) {
15297             SendToProgram("draw\n", &first);
15298             userOfferedDraw = TRUE;
15299         }
15300     }
15301 }
15302
15303 void
15304 AdjournEvent ()
15305 {
15306     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15307
15308     if (appData.icsActive) {
15309         SendToICS(ics_prefix);
15310         SendToICS("adjourn\n");
15311     } else {
15312         /* Currently GNU Chess doesn't offer or accept Adjourns */
15313     }
15314 }
15315
15316
15317 void
15318 AbortEvent ()
15319 {
15320     /* Offer Abort or accept pending Abort offer from opponent */
15321
15322     if (appData.icsActive) {
15323         SendToICS(ics_prefix);
15324         SendToICS("abort\n");
15325     } else {
15326         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15327     }
15328 }
15329
15330 void
15331 ResignEvent ()
15332 {
15333     /* Resign.  You can do this even if it's not your turn. */
15334
15335     if (appData.icsActive) {
15336         SendToICS(ics_prefix);
15337         SendToICS("resign\n");
15338     } else {
15339         switch (gameMode) {
15340           case MachinePlaysWhite:
15341             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15342             break;
15343           case MachinePlaysBlack:
15344             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15345             break;
15346           case EditGame:
15347             if (cmailMsgLoaded) {
15348                 TruncateGame();
15349                 if (WhiteOnMove(cmailOldMove)) {
15350                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15351                 } else {
15352                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15353                 }
15354                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15355             }
15356             break;
15357           default:
15358             break;
15359         }
15360     }
15361 }
15362
15363
15364 void
15365 StopObservingEvent ()
15366 {
15367     /* Stop observing current games */
15368     SendToICS(ics_prefix);
15369     SendToICS("unobserve\n");
15370 }
15371
15372 void
15373 StopExaminingEvent ()
15374 {
15375     /* Stop observing current game */
15376     SendToICS(ics_prefix);
15377     SendToICS("unexamine\n");
15378 }
15379
15380 void
15381 ForwardInner (int target)
15382 {
15383     int limit; int oldSeekGraphUp = seekGraphUp;
15384
15385     if (appData.debugMode)
15386         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15387                 target, currentMove, forwardMostMove);
15388
15389     if (gameMode == EditPosition)
15390       return;
15391
15392     seekGraphUp = FALSE;
15393     MarkTargetSquares(1);
15394
15395     if (gameMode == PlayFromGameFile && !pausing)
15396       PauseEvent();
15397
15398     if (gameMode == IcsExamining && pausing)
15399       limit = pauseExamForwardMostMove;
15400     else
15401       limit = forwardMostMove;
15402
15403     if (target > limit) target = limit;
15404
15405     if (target > 0 && moveList[target - 1][0]) {
15406         int fromX, fromY, toX, toY;
15407         toX = moveList[target - 1][2] - AAA;
15408         toY = moveList[target - 1][3] - ONE;
15409         if (moveList[target - 1][1] == '@') {
15410             if (appData.highlightLastMove) {
15411                 SetHighlights(-1, -1, toX, toY);
15412             }
15413         } else {
15414             fromX = moveList[target - 1][0] - AAA;
15415             fromY = moveList[target - 1][1] - ONE;
15416             if (target == currentMove + 1) {
15417                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15418             }
15419             if (appData.highlightLastMove) {
15420                 SetHighlights(fromX, fromY, toX, toY);
15421             }
15422         }
15423     }
15424     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15425         gameMode == Training || gameMode == PlayFromGameFile ||
15426         gameMode == AnalyzeFile) {
15427         while (currentMove < target) {
15428             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15429             SendMoveToProgram(currentMove++, &first);
15430         }
15431     } else {
15432         currentMove = target;
15433     }
15434
15435     if (gameMode == EditGame || gameMode == EndOfGame) {
15436         whiteTimeRemaining = timeRemaining[0][currentMove];
15437         blackTimeRemaining = timeRemaining[1][currentMove];
15438     }
15439     DisplayBothClocks();
15440     DisplayMove(currentMove - 1);
15441     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15442     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15443     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15444         DisplayComment(currentMove - 1, commentList[currentMove]);
15445     }
15446     ClearMap(); // [HGM] exclude: invalidate map
15447 }
15448
15449
15450 void
15451 ForwardEvent ()
15452 {
15453     if (gameMode == IcsExamining && !pausing) {
15454         SendToICS(ics_prefix);
15455         SendToICS("forward\n");
15456     } else {
15457         ForwardInner(currentMove + 1);
15458     }
15459 }
15460
15461 void
15462 ToEndEvent ()
15463 {
15464     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15465         /* to optimze, we temporarily turn off analysis mode while we feed
15466          * the remaining moves to the engine. Otherwise we get analysis output
15467          * after each move.
15468          */
15469         if (first.analysisSupport) {
15470           SendToProgram("exit\nforce\n", &first);
15471           first.analyzing = FALSE;
15472         }
15473     }
15474
15475     if (gameMode == IcsExamining && !pausing) {
15476         SendToICS(ics_prefix);
15477         SendToICS("forward 999999\n");
15478     } else {
15479         ForwardInner(forwardMostMove);
15480     }
15481
15482     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15483         /* we have fed all the moves, so reactivate analysis mode */
15484         SendToProgram("analyze\n", &first);
15485         first.analyzing = TRUE;
15486         /*first.maybeThinking = TRUE;*/
15487         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15488     }
15489 }
15490
15491 void
15492 BackwardInner (int target)
15493 {
15494     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15495
15496     if (appData.debugMode)
15497         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15498                 target, currentMove, forwardMostMove);
15499
15500     if (gameMode == EditPosition) return;
15501     seekGraphUp = FALSE;
15502     MarkTargetSquares(1);
15503     if (currentMove <= backwardMostMove) {
15504         ClearHighlights();
15505         DrawPosition(full_redraw, boards[currentMove]);
15506         return;
15507     }
15508     if (gameMode == PlayFromGameFile && !pausing)
15509       PauseEvent();
15510
15511     if (moveList[target][0]) {
15512         int fromX, fromY, toX, toY;
15513         toX = moveList[target][2] - AAA;
15514         toY = moveList[target][3] - ONE;
15515         if (moveList[target][1] == '@') {
15516             if (appData.highlightLastMove) {
15517                 SetHighlights(-1, -1, toX, toY);
15518             }
15519         } else {
15520             fromX = moveList[target][0] - AAA;
15521             fromY = moveList[target][1] - ONE;
15522             if (target == currentMove - 1) {
15523                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15524             }
15525             if (appData.highlightLastMove) {
15526                 SetHighlights(fromX, fromY, toX, toY);
15527             }
15528         }
15529     }
15530     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15531         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15532         while (currentMove > target) {
15533             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15534                 // null move cannot be undone. Reload program with move history before it.
15535                 int i;
15536                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15537                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15538                 }
15539                 SendBoard(&first, i);
15540               if(second.analyzing) SendBoard(&second, i);
15541                 for(currentMove=i; currentMove<target; currentMove++) {
15542                     SendMoveToProgram(currentMove, &first);
15543                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15544                 }
15545                 break;
15546             }
15547             SendToBoth("undo\n");
15548             currentMove--;
15549         }
15550     } else {
15551         currentMove = target;
15552     }
15553
15554     if (gameMode == EditGame || gameMode == EndOfGame) {
15555         whiteTimeRemaining = timeRemaining[0][currentMove];
15556         blackTimeRemaining = timeRemaining[1][currentMove];
15557     }
15558     DisplayBothClocks();
15559     DisplayMove(currentMove - 1);
15560     DrawPosition(full_redraw, boards[currentMove]);
15561     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15562     // [HGM] PV info: routine tests if comment empty
15563     DisplayComment(currentMove - 1, commentList[currentMove]);
15564     ClearMap(); // [HGM] exclude: invalidate map
15565 }
15566
15567 void
15568 BackwardEvent ()
15569 {
15570     if (gameMode == IcsExamining && !pausing) {
15571         SendToICS(ics_prefix);
15572         SendToICS("backward\n");
15573     } else {
15574         BackwardInner(currentMove - 1);
15575     }
15576 }
15577
15578 void
15579 ToStartEvent ()
15580 {
15581     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15582         /* to optimize, we temporarily turn off analysis mode while we undo
15583          * all the moves. Otherwise we get analysis output after each undo.
15584          */
15585         if (first.analysisSupport) {
15586           SendToProgram("exit\nforce\n", &first);
15587           first.analyzing = FALSE;
15588         }
15589     }
15590
15591     if (gameMode == IcsExamining && !pausing) {
15592         SendToICS(ics_prefix);
15593         SendToICS("backward 999999\n");
15594     } else {
15595         BackwardInner(backwardMostMove);
15596     }
15597
15598     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15599         /* we have fed all the moves, so reactivate analysis mode */
15600         SendToProgram("analyze\n", &first);
15601         first.analyzing = TRUE;
15602         /*first.maybeThinking = TRUE;*/
15603         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15604     }
15605 }
15606
15607 void
15608 ToNrEvent (int to)
15609 {
15610   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15611   if (to >= forwardMostMove) to = forwardMostMove;
15612   if (to <= backwardMostMove) to = backwardMostMove;
15613   if (to < currentMove) {
15614     BackwardInner(to);
15615   } else {
15616     ForwardInner(to);
15617   }
15618 }
15619
15620 void
15621 RevertEvent (Boolean annotate)
15622 {
15623     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15624         return;
15625     }
15626     if (gameMode != IcsExamining) {
15627         DisplayError(_("You are not examining a game"), 0);
15628         return;
15629     }
15630     if (pausing) {
15631         DisplayError(_("You can't revert while pausing"), 0);
15632         return;
15633     }
15634     SendToICS(ics_prefix);
15635     SendToICS("revert\n");
15636 }
15637
15638 void
15639 RetractMoveEvent ()
15640 {
15641     switch (gameMode) {
15642       case MachinePlaysWhite:
15643       case MachinePlaysBlack:
15644         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15645             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15646             return;
15647         }
15648         if (forwardMostMove < 2) return;
15649         currentMove = forwardMostMove = forwardMostMove - 2;
15650         whiteTimeRemaining = timeRemaining[0][currentMove];
15651         blackTimeRemaining = timeRemaining[1][currentMove];
15652         DisplayBothClocks();
15653         DisplayMove(currentMove - 1);
15654         ClearHighlights();/*!! could figure this out*/
15655         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15656         SendToProgram("remove\n", &first);
15657         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15658         break;
15659
15660       case BeginningOfGame:
15661       default:
15662         break;
15663
15664       case IcsPlayingWhite:
15665       case IcsPlayingBlack:
15666         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15667             SendToICS(ics_prefix);
15668             SendToICS("takeback 2\n");
15669         } else {
15670             SendToICS(ics_prefix);
15671             SendToICS("takeback 1\n");
15672         }
15673         break;
15674     }
15675 }
15676
15677 void
15678 MoveNowEvent ()
15679 {
15680     ChessProgramState *cps;
15681
15682     switch (gameMode) {
15683       case MachinePlaysWhite:
15684         if (!WhiteOnMove(forwardMostMove)) {
15685             DisplayError(_("It is your turn"), 0);
15686             return;
15687         }
15688         cps = &first;
15689         break;
15690       case MachinePlaysBlack:
15691         if (WhiteOnMove(forwardMostMove)) {
15692             DisplayError(_("It is your turn"), 0);
15693             return;
15694         }
15695         cps = &first;
15696         break;
15697       case TwoMachinesPlay:
15698         if (WhiteOnMove(forwardMostMove) ==
15699             (first.twoMachinesColor[0] == 'w')) {
15700             cps = &first;
15701         } else {
15702             cps = &second;
15703         }
15704         break;
15705       case BeginningOfGame:
15706       default:
15707         return;
15708     }
15709     SendToProgram("?\n", cps);
15710 }
15711
15712 void
15713 TruncateGameEvent ()
15714 {
15715     EditGameEvent();
15716     if (gameMode != EditGame) return;
15717     TruncateGame();
15718 }
15719
15720 void
15721 TruncateGame ()
15722 {
15723     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15724     if (forwardMostMove > currentMove) {
15725         if (gameInfo.resultDetails != NULL) {
15726             free(gameInfo.resultDetails);
15727             gameInfo.resultDetails = NULL;
15728             gameInfo.result = GameUnfinished;
15729         }
15730         forwardMostMove = currentMove;
15731         HistorySet(parseList, backwardMostMove, forwardMostMove,
15732                    currentMove-1);
15733     }
15734 }
15735
15736 void
15737 HintEvent ()
15738 {
15739     if (appData.noChessProgram) return;
15740     switch (gameMode) {
15741       case MachinePlaysWhite:
15742         if (WhiteOnMove(forwardMostMove)) {
15743             DisplayError(_("Wait until your turn."), 0);
15744             return;
15745         }
15746         break;
15747       case BeginningOfGame:
15748       case MachinePlaysBlack:
15749         if (!WhiteOnMove(forwardMostMove)) {
15750             DisplayError(_("Wait until your turn."), 0);
15751             return;
15752         }
15753         break;
15754       default:
15755         DisplayError(_("No hint available"), 0);
15756         return;
15757     }
15758     SendToProgram("hint\n", &first);
15759     hintRequested = TRUE;
15760 }
15761
15762 void
15763 CreateBookEvent ()
15764 {
15765     ListGame * lg = (ListGame *) gameList.head;
15766     FILE *f, *g;
15767     int nItem;
15768     static int secondTime = FALSE;
15769
15770     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15771         DisplayError(_("Game list not loaded or empty"), 0);
15772         return;
15773     }
15774
15775     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15776         fclose(g);
15777         secondTime++;
15778         DisplayNote(_("Book file exists! Try again for overwrite."));
15779         return;
15780     }
15781
15782     creatingBook = TRUE;
15783     secondTime = FALSE;
15784
15785     /* Get list size */
15786     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15787         LoadGame(f, nItem, "", TRUE);
15788         AddGameToBook(TRUE);
15789         lg = (ListGame *) lg->node.succ;
15790     }
15791
15792     creatingBook = FALSE;
15793     FlushBook();
15794 }
15795
15796 void
15797 BookEvent ()
15798 {
15799     if (appData.noChessProgram) return;
15800     switch (gameMode) {
15801       case MachinePlaysWhite:
15802         if (WhiteOnMove(forwardMostMove)) {
15803             DisplayError(_("Wait until your turn."), 0);
15804             return;
15805         }
15806         break;
15807       case BeginningOfGame:
15808       case MachinePlaysBlack:
15809         if (!WhiteOnMove(forwardMostMove)) {
15810             DisplayError(_("Wait until your turn."), 0);
15811             return;
15812         }
15813         break;
15814       case EditPosition:
15815         EditPositionDone(TRUE);
15816         break;
15817       case TwoMachinesPlay:
15818         return;
15819       default:
15820         break;
15821     }
15822     SendToProgram("bk\n", &first);
15823     bookOutput[0] = NULLCHAR;
15824     bookRequested = TRUE;
15825 }
15826
15827 void
15828 AboutGameEvent ()
15829 {
15830     char *tags = PGNTags(&gameInfo);
15831     TagsPopUp(tags, CmailMsg());
15832     free(tags);
15833 }
15834
15835 /* end button procedures */
15836
15837 void
15838 PrintPosition (FILE *fp, int move)
15839 {
15840     int i, j;
15841
15842     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15843         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15844             char c = PieceToChar(boards[move][i][j]);
15845             fputc(c == 'x' ? '.' : c, fp);
15846             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15847         }
15848     }
15849     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15850       fprintf(fp, "white to play\n");
15851     else
15852       fprintf(fp, "black to play\n");
15853 }
15854
15855 void
15856 PrintOpponents (FILE *fp)
15857 {
15858     if (gameInfo.white != NULL) {
15859         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15860     } else {
15861         fprintf(fp, "\n");
15862     }
15863 }
15864
15865 /* Find last component of program's own name, using some heuristics */
15866 void
15867 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15868 {
15869     char *p, *q, c;
15870     int local = (strcmp(host, "localhost") == 0);
15871     while (!local && (p = strchr(prog, ';')) != NULL) {
15872         p++;
15873         while (*p == ' ') p++;
15874         prog = p;
15875     }
15876     if (*prog == '"' || *prog == '\'') {
15877         q = strchr(prog + 1, *prog);
15878     } else {
15879         q = strchr(prog, ' ');
15880     }
15881     if (q == NULL) q = prog + strlen(prog);
15882     p = q;
15883     while (p >= prog && *p != '/' && *p != '\\') p--;
15884     p++;
15885     if(p == prog && *p == '"') p++;
15886     c = *q; *q = 0;
15887     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15888     memcpy(buf, p, q - p);
15889     buf[q - p] = NULLCHAR;
15890     if (!local) {
15891         strcat(buf, "@");
15892         strcat(buf, host);
15893     }
15894 }
15895
15896 char *
15897 TimeControlTagValue ()
15898 {
15899     char buf[MSG_SIZ];
15900     if (!appData.clockMode) {
15901       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15902     } else if (movesPerSession > 0) {
15903       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15904     } else if (timeIncrement == 0) {
15905       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15906     } else {
15907       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15908     }
15909     return StrSave(buf);
15910 }
15911
15912 void
15913 SetGameInfo ()
15914 {
15915     /* This routine is used only for certain modes */
15916     VariantClass v = gameInfo.variant;
15917     ChessMove r = GameUnfinished;
15918     char *p = NULL;
15919
15920     if(keepInfo) return;
15921
15922     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15923         r = gameInfo.result;
15924         p = gameInfo.resultDetails;
15925         gameInfo.resultDetails = NULL;
15926     }
15927     ClearGameInfo(&gameInfo);
15928     gameInfo.variant = v;
15929
15930     switch (gameMode) {
15931       case MachinePlaysWhite:
15932         gameInfo.event = StrSave( appData.pgnEventHeader );
15933         gameInfo.site = StrSave(HostName());
15934         gameInfo.date = PGNDate();
15935         gameInfo.round = StrSave("-");
15936         gameInfo.white = StrSave(first.tidy);
15937         gameInfo.black = StrSave(UserName());
15938         gameInfo.timeControl = TimeControlTagValue();
15939         break;
15940
15941       case MachinePlaysBlack:
15942         gameInfo.event = StrSave( appData.pgnEventHeader );
15943         gameInfo.site = StrSave(HostName());
15944         gameInfo.date = PGNDate();
15945         gameInfo.round = StrSave("-");
15946         gameInfo.white = StrSave(UserName());
15947         gameInfo.black = StrSave(first.tidy);
15948         gameInfo.timeControl = TimeControlTagValue();
15949         break;
15950
15951       case TwoMachinesPlay:
15952         gameInfo.event = StrSave( appData.pgnEventHeader );
15953         gameInfo.site = StrSave(HostName());
15954         gameInfo.date = PGNDate();
15955         if (roundNr > 0) {
15956             char buf[MSG_SIZ];
15957             snprintf(buf, MSG_SIZ, "%d", roundNr);
15958             gameInfo.round = StrSave(buf);
15959         } else {
15960             gameInfo.round = StrSave("-");
15961         }
15962         if (first.twoMachinesColor[0] == 'w') {
15963             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15964             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15965         } else {
15966             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15967             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15968         }
15969         gameInfo.timeControl = TimeControlTagValue();
15970         break;
15971
15972       case EditGame:
15973         gameInfo.event = StrSave("Edited game");
15974         gameInfo.site = StrSave(HostName());
15975         gameInfo.date = PGNDate();
15976         gameInfo.round = StrSave("-");
15977         gameInfo.white = StrSave("-");
15978         gameInfo.black = StrSave("-");
15979         gameInfo.result = r;
15980         gameInfo.resultDetails = p;
15981         break;
15982
15983       case EditPosition:
15984         gameInfo.event = StrSave("Edited position");
15985         gameInfo.site = StrSave(HostName());
15986         gameInfo.date = PGNDate();
15987         gameInfo.round = StrSave("-");
15988         gameInfo.white = StrSave("-");
15989         gameInfo.black = StrSave("-");
15990         break;
15991
15992       case IcsPlayingWhite:
15993       case IcsPlayingBlack:
15994       case IcsObserving:
15995       case IcsExamining:
15996         break;
15997
15998       case PlayFromGameFile:
15999         gameInfo.event = StrSave("Game from non-PGN file");
16000         gameInfo.site = StrSave(HostName());
16001         gameInfo.date = PGNDate();
16002         gameInfo.round = StrSave("-");
16003         gameInfo.white = StrSave("?");
16004         gameInfo.black = StrSave("?");
16005         break;
16006
16007       default:
16008         break;
16009     }
16010 }
16011
16012 void
16013 ReplaceComment (int index, char *text)
16014 {
16015     int len;
16016     char *p;
16017     float score;
16018
16019     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16020        pvInfoList[index-1].depth == len &&
16021        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16022        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16023     while (*text == '\n') text++;
16024     len = strlen(text);
16025     while (len > 0 && text[len - 1] == '\n') len--;
16026
16027     if (commentList[index] != NULL)
16028       free(commentList[index]);
16029
16030     if (len == 0) {
16031         commentList[index] = NULL;
16032         return;
16033     }
16034   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16035       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16036       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16037     commentList[index] = (char *) malloc(len + 2);
16038     strncpy(commentList[index], text, len);
16039     commentList[index][len] = '\n';
16040     commentList[index][len + 1] = NULLCHAR;
16041   } else {
16042     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16043     char *p;
16044     commentList[index] = (char *) malloc(len + 7);
16045     safeStrCpy(commentList[index], "{\n", 3);
16046     safeStrCpy(commentList[index]+2, text, len+1);
16047     commentList[index][len+2] = NULLCHAR;
16048     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16049     strcat(commentList[index], "\n}\n");
16050   }
16051 }
16052
16053 void
16054 CrushCRs (char *text)
16055 {
16056   char *p = text;
16057   char *q = text;
16058   char ch;
16059
16060   do {
16061     ch = *p++;
16062     if (ch == '\r') continue;
16063     *q++ = ch;
16064   } while (ch != '\0');
16065 }
16066
16067 void
16068 AppendComment (int index, char *text, Boolean addBraces)
16069 /* addBraces  tells if we should add {} */
16070 {
16071     int oldlen, len;
16072     char *old;
16073
16074 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16075     if(addBraces == 3) addBraces = 0; else // force appending literally
16076     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16077
16078     CrushCRs(text);
16079     while (*text == '\n') text++;
16080     len = strlen(text);
16081     while (len > 0 && text[len - 1] == '\n') len--;
16082     text[len] = NULLCHAR;
16083
16084     if (len == 0) return;
16085
16086     if (commentList[index] != NULL) {
16087       Boolean addClosingBrace = addBraces;
16088         old = commentList[index];
16089         oldlen = strlen(old);
16090         while(commentList[index][oldlen-1] ==  '\n')
16091           commentList[index][--oldlen] = NULLCHAR;
16092         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16093         safeStrCpy(commentList[index], old, oldlen + len + 6);
16094         free(old);
16095         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16096         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16097           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16098           while (*text == '\n') { text++; len--; }
16099           commentList[index][--oldlen] = NULLCHAR;
16100       }
16101         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16102         else          strcat(commentList[index], "\n");
16103         strcat(commentList[index], text);
16104         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16105         else          strcat(commentList[index], "\n");
16106     } else {
16107         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16108         if(addBraces)
16109           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16110         else commentList[index][0] = NULLCHAR;
16111         strcat(commentList[index], text);
16112         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16113         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16114     }
16115 }
16116
16117 static char *
16118 FindStr (char * text, char * sub_text)
16119 {
16120     char * result = strstr( text, sub_text );
16121
16122     if( result != NULL ) {
16123         result += strlen( sub_text );
16124     }
16125
16126     return result;
16127 }
16128
16129 /* [AS] Try to extract PV info from PGN comment */
16130 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16131 char *
16132 GetInfoFromComment (int index, char * text)
16133 {
16134     char * sep = text, *p;
16135
16136     if( text != NULL && index > 0 ) {
16137         int score = 0;
16138         int depth = 0;
16139         int time = -1, sec = 0, deci;
16140         char * s_eval = FindStr( text, "[%eval " );
16141         char * s_emt = FindStr( text, "[%emt " );
16142 #if 0
16143         if( s_eval != NULL || s_emt != NULL ) {
16144 #else
16145         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16146 #endif
16147             /* New style */
16148             char delim;
16149
16150             if( s_eval != NULL ) {
16151                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16152                     return text;
16153                 }
16154
16155                 if( delim != ']' ) {
16156                     return text;
16157                 }
16158             }
16159
16160             if( s_emt != NULL ) {
16161             }
16162                 return text;
16163         }
16164         else {
16165             /* We expect something like: [+|-]nnn.nn/dd */
16166             int score_lo = 0;
16167
16168             if(*text != '{') return text; // [HGM] braces: must be normal comment
16169
16170             sep = strchr( text, '/' );
16171             if( sep == NULL || sep < (text+4) ) {
16172                 return text;
16173             }
16174
16175             p = text;
16176             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16177             if(p[1] == '(') { // comment starts with PV
16178                p = strchr(p, ')'); // locate end of PV
16179                if(p == NULL || sep < p+5) return text;
16180                // at this point we have something like "{(.*) +0.23/6 ..."
16181                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16182                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16183                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16184             }
16185             time = -1; sec = -1; deci = -1;
16186             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16187                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16188                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16189                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16190                 return text;
16191             }
16192
16193             if( score_lo < 0 || score_lo >= 100 ) {
16194                 return text;
16195             }
16196
16197             if(sec >= 0) time = 600*time + 10*sec; else
16198             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16199
16200             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16201
16202             /* [HGM] PV time: now locate end of PV info */
16203             while( *++sep >= '0' && *sep <= '9'); // strip depth
16204             if(time >= 0)
16205             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16206             if(sec >= 0)
16207             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16208             if(deci >= 0)
16209             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16210             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16211         }
16212
16213         if( depth <= 0 ) {
16214             return text;
16215         }
16216
16217         if( time < 0 ) {
16218             time = -1;
16219         }
16220
16221         pvInfoList[index-1].depth = depth;
16222         pvInfoList[index-1].score = score;
16223         pvInfoList[index-1].time  = 10*time; // centi-sec
16224         if(*sep == '}') *sep = 0; else *--sep = '{';
16225         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16226     }
16227     return sep;
16228 }
16229
16230 void
16231 SendToProgram (char *message, ChessProgramState *cps)
16232 {
16233     int count, outCount, error;
16234     char buf[MSG_SIZ];
16235
16236     if (cps->pr == NoProc) return;
16237     Attention(cps);
16238
16239     if (appData.debugMode) {
16240         TimeMark now;
16241         GetTimeMark(&now);
16242         fprintf(debugFP, "%ld >%-6s: %s",
16243                 SubtractTimeMarks(&now, &programStartTime),
16244                 cps->which, message);
16245         if(serverFP)
16246             fprintf(serverFP, "%ld >%-6s: %s",
16247                 SubtractTimeMarks(&now, &programStartTime),
16248                 cps->which, message), fflush(serverFP);
16249     }
16250
16251     count = strlen(message);
16252     outCount = OutputToProcess(cps->pr, message, count, &error);
16253     if (outCount < count && !exiting
16254                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16255       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16256       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16257         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16258             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16259                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16260                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16261                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16262             } else {
16263                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16264                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16265                 gameInfo.result = res;
16266             }
16267             gameInfo.resultDetails = StrSave(buf);
16268         }
16269         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16270         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16271     }
16272 }
16273
16274 void
16275 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16276 {
16277     char *end_str;
16278     char buf[MSG_SIZ];
16279     ChessProgramState *cps = (ChessProgramState *)closure;
16280
16281     if (isr != cps->isr) return; /* Killed intentionally */
16282     if (count <= 0) {
16283         if (count == 0) {
16284             RemoveInputSource(cps->isr);
16285             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16286                     _(cps->which), cps->program);
16287             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16288             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16289                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16290                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16291                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16292                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16293                 } else {
16294                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16295                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16296                     gameInfo.result = res;
16297                 }
16298                 gameInfo.resultDetails = StrSave(buf);
16299             }
16300             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16301             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16302         } else {
16303             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16304                     _(cps->which), cps->program);
16305             RemoveInputSource(cps->isr);
16306
16307             /* [AS] Program is misbehaving badly... kill it */
16308             if( count == -2 ) {
16309                 DestroyChildProcess( cps->pr, 9 );
16310                 cps->pr = NoProc;
16311             }
16312
16313             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16314         }
16315         return;
16316     }
16317
16318     if ((end_str = strchr(message, '\r')) != NULL)
16319       *end_str = NULLCHAR;
16320     if ((end_str = strchr(message, '\n')) != NULL)
16321       *end_str = NULLCHAR;
16322
16323     if (appData.debugMode) {
16324         TimeMark now; int print = 1;
16325         char *quote = ""; char c; int i;
16326
16327         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16328                 char start = message[0];
16329                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16330                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16331                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16332                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16333                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16334                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16335                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16336                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16337                    sscanf(message, "hint: %c", &c)!=1 &&
16338                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16339                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16340                     print = (appData.engineComments >= 2);
16341                 }
16342                 message[0] = start; // restore original message
16343         }
16344         if(print) {
16345                 GetTimeMark(&now);
16346                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16347                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16348                         quote,
16349                         message);
16350                 if(serverFP)
16351                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16352                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16353                         quote,
16354                         message), fflush(serverFP);
16355         }
16356     }
16357
16358     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16359     if (appData.icsEngineAnalyze) {
16360         if (strstr(message, "whisper") != NULL ||
16361              strstr(message, "kibitz") != NULL ||
16362             strstr(message, "tellics") != NULL) return;
16363     }
16364
16365     HandleMachineMove(message, cps);
16366 }
16367
16368
16369 void
16370 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16371 {
16372     char buf[MSG_SIZ];
16373     int seconds;
16374
16375     if( timeControl_2 > 0 ) {
16376         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16377             tc = timeControl_2;
16378         }
16379     }
16380     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16381     inc /= cps->timeOdds;
16382     st  /= cps->timeOdds;
16383
16384     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16385
16386     if (st > 0) {
16387       /* Set exact time per move, normally using st command */
16388       if (cps->stKludge) {
16389         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16390         seconds = st % 60;
16391         if (seconds == 0) {
16392           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16393         } else {
16394           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16395         }
16396       } else {
16397         snprintf(buf, MSG_SIZ, "st %d\n", st);
16398       }
16399     } else {
16400       /* Set conventional or incremental time control, using level command */
16401       if (seconds == 0) {
16402         /* Note old gnuchess bug -- minutes:seconds used to not work.
16403            Fixed in later versions, but still avoid :seconds
16404            when seconds is 0. */
16405         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16406       } else {
16407         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16408                  seconds, inc/1000.);
16409       }
16410     }
16411     SendToProgram(buf, cps);
16412
16413     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16414     /* Orthogonally, limit search to given depth */
16415     if (sd > 0) {
16416       if (cps->sdKludge) {
16417         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16418       } else {
16419         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16420       }
16421       SendToProgram(buf, cps);
16422     }
16423
16424     if(cps->nps >= 0) { /* [HGM] nps */
16425         if(cps->supportsNPS == FALSE)
16426           cps->nps = -1; // don't use if engine explicitly says not supported!
16427         else {
16428           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16429           SendToProgram(buf, cps);
16430         }
16431     }
16432 }
16433
16434 ChessProgramState *
16435 WhitePlayer ()
16436 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16437 {
16438     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16439        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16440         return &second;
16441     return &first;
16442 }
16443
16444 void
16445 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16446 {
16447     char message[MSG_SIZ];
16448     long time, otime;
16449
16450     /* Note: this routine must be called when the clocks are stopped
16451        or when they have *just* been set or switched; otherwise
16452        it will be off by the time since the current tick started.
16453     */
16454     if (machineWhite) {
16455         time = whiteTimeRemaining / 10;
16456         otime = blackTimeRemaining / 10;
16457     } else {
16458         time = blackTimeRemaining / 10;
16459         otime = whiteTimeRemaining / 10;
16460     }
16461     /* [HGM] translate opponent's time by time-odds factor */
16462     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16463
16464     if (time <= 0) time = 1;
16465     if (otime <= 0) otime = 1;
16466
16467     snprintf(message, MSG_SIZ, "time %ld\n", time);
16468     SendToProgram(message, cps);
16469
16470     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16471     SendToProgram(message, cps);
16472 }
16473
16474 char *
16475 EngineDefinedVariant (ChessProgramState *cps, int n)
16476 {   // return name of n-th unknown variant that engine supports
16477     static char buf[MSG_SIZ];
16478     char *p, *s = cps->variants;
16479     if(!s) return NULL;
16480     do { // parse string from variants feature
16481       VariantClass v;
16482         p = strchr(s, ',');
16483         if(p) *p = NULLCHAR;
16484       v = StringToVariant(s);
16485       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16486         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16487             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16488         }
16489         if(p) *p++ = ',';
16490         if(n < 0) return buf;
16491     } while(s = p);
16492     return NULL;
16493 }
16494
16495 int
16496 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16497 {
16498   char buf[MSG_SIZ];
16499   int len = strlen(name);
16500   int val;
16501
16502   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16503     (*p) += len + 1;
16504     sscanf(*p, "%d", &val);
16505     *loc = (val != 0);
16506     while (**p && **p != ' ')
16507       (*p)++;
16508     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16509     SendToProgram(buf, cps);
16510     return TRUE;
16511   }
16512   return FALSE;
16513 }
16514
16515 int
16516 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16517 {
16518   char buf[MSG_SIZ];
16519   int len = strlen(name);
16520   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16521     (*p) += len + 1;
16522     sscanf(*p, "%d", loc);
16523     while (**p && **p != ' ') (*p)++;
16524     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16525     SendToProgram(buf, cps);
16526     return TRUE;
16527   }
16528   return FALSE;
16529 }
16530
16531 int
16532 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16533 {
16534   char buf[MSG_SIZ];
16535   int len = strlen(name);
16536   if (strncmp((*p), name, len) == 0
16537       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16538     (*p) += len + 2;
16539     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16540     sscanf(*p, "%[^\"]", *loc);
16541     while (**p && **p != '\"') (*p)++;
16542     if (**p == '\"') (*p)++;
16543     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16544     SendToProgram(buf, cps);
16545     return TRUE;
16546   }
16547   return FALSE;
16548 }
16549
16550 int
16551 ParseOption (Option *opt, ChessProgramState *cps)
16552 // [HGM] options: process the string that defines an engine option, and determine
16553 // name, type, default value, and allowed value range
16554 {
16555         char *p, *q, buf[MSG_SIZ];
16556         int n, min = (-1)<<31, max = 1<<31, def;
16557
16558         if(p = strstr(opt->name, " -spin ")) {
16559             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16560             if(max < min) max = min; // enforce consistency
16561             if(def < min) def = min;
16562             if(def > max) def = max;
16563             opt->value = def;
16564             opt->min = min;
16565             opt->max = max;
16566             opt->type = Spin;
16567         } else if((p = strstr(opt->name, " -slider "))) {
16568             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16569             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16570             if(max < min) max = min; // enforce consistency
16571             if(def < min) def = min;
16572             if(def > max) def = max;
16573             opt->value = def;
16574             opt->min = min;
16575             opt->max = max;
16576             opt->type = Spin; // Slider;
16577         } else if((p = strstr(opt->name, " -string "))) {
16578             opt->textValue = p+9;
16579             opt->type = TextBox;
16580         } else if((p = strstr(opt->name, " -file "))) {
16581             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16582             opt->textValue = p+7;
16583             opt->type = FileName; // FileName;
16584         } else if((p = strstr(opt->name, " -path "))) {
16585             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16586             opt->textValue = p+7;
16587             opt->type = PathName; // PathName;
16588         } else if(p = strstr(opt->name, " -check ")) {
16589             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16590             opt->value = (def != 0);
16591             opt->type = CheckBox;
16592         } else if(p = strstr(opt->name, " -combo ")) {
16593             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16594             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16595             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16596             opt->value = n = 0;
16597             while(q = StrStr(q, " /// ")) {
16598                 n++; *q = 0;    // count choices, and null-terminate each of them
16599                 q += 5;
16600                 if(*q == '*') { // remember default, which is marked with * prefix
16601                     q++;
16602                     opt->value = n;
16603                 }
16604                 cps->comboList[cps->comboCnt++] = q;
16605             }
16606             cps->comboList[cps->comboCnt++] = NULL;
16607             opt->max = n + 1;
16608             opt->type = ComboBox;
16609         } else if(p = strstr(opt->name, " -button")) {
16610             opt->type = Button;
16611         } else if(p = strstr(opt->name, " -save")) {
16612             opt->type = SaveButton;
16613         } else return FALSE;
16614         *p = 0; // terminate option name
16615         // now look if the command-line options define a setting for this engine option.
16616         if(cps->optionSettings && cps->optionSettings[0])
16617             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16618         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16619           snprintf(buf, MSG_SIZ, "option %s", p);
16620                 if(p = strstr(buf, ",")) *p = 0;
16621                 if(q = strchr(buf, '=')) switch(opt->type) {
16622                     case ComboBox:
16623                         for(n=0; n<opt->max; n++)
16624                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16625                         break;
16626                     case TextBox:
16627                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16628                         break;
16629                     case Spin:
16630                     case CheckBox:
16631                         opt->value = atoi(q+1);
16632                     default:
16633                         break;
16634                 }
16635                 strcat(buf, "\n");
16636                 SendToProgram(buf, cps);
16637         }
16638         return TRUE;
16639 }
16640
16641 void
16642 FeatureDone (ChessProgramState *cps, int val)
16643 {
16644   DelayedEventCallback cb = GetDelayedEvent();
16645   if ((cb == InitBackEnd3 && cps == &first) ||
16646       (cb == SettingsMenuIfReady && cps == &second) ||
16647       (cb == LoadEngine) ||
16648       (cb == TwoMachinesEventIfReady)) {
16649     CancelDelayedEvent();
16650     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16651   }
16652   cps->initDone = val;
16653   if(val) cps->reload = FALSE;
16654 }
16655
16656 /* Parse feature command from engine */
16657 void
16658 ParseFeatures (char *args, ChessProgramState *cps)
16659 {
16660   char *p = args;
16661   char *q = NULL;
16662   int val;
16663   char buf[MSG_SIZ];
16664
16665   for (;;) {
16666     while (*p == ' ') p++;
16667     if (*p == NULLCHAR) return;
16668
16669     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16670     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16671     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16672     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16673     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16674     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16675     if (BoolFeature(&p, "reuse", &val, cps)) {
16676       /* Engine can disable reuse, but can't enable it if user said no */
16677       if (!val) cps->reuse = FALSE;
16678       continue;
16679     }
16680     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16681     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16682       if (gameMode == TwoMachinesPlay) {
16683         DisplayTwoMachinesTitle();
16684       } else {
16685         DisplayTitle("");
16686       }
16687       continue;
16688     }
16689     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16690     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16691     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16692     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16693     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16694     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16695     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16696     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16697     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16698     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16699     if (IntFeature(&p, "done", &val, cps)) {
16700       FeatureDone(cps, val);
16701       continue;
16702     }
16703     /* Added by Tord: */
16704     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16705     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16706     /* End of additions by Tord */
16707
16708     /* [HGM] added features: */
16709     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16710     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16711     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16712     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16713     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16714     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16715     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16716     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16717         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16718         FREE(cps->option[cps->nrOptions].name);
16719         cps->option[cps->nrOptions].name = q; q = NULL;
16720         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16721           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16722             SendToProgram(buf, cps);
16723             continue;
16724         }
16725         if(cps->nrOptions >= MAX_OPTIONS) {
16726             cps->nrOptions--;
16727             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16728             DisplayError(buf, 0);
16729         }
16730         continue;
16731     }
16732     /* End of additions by HGM */
16733
16734     /* unknown feature: complain and skip */
16735     q = p;
16736     while (*q && *q != '=') q++;
16737     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16738     SendToProgram(buf, cps);
16739     p = q;
16740     if (*p == '=') {
16741       p++;
16742       if (*p == '\"') {
16743         p++;
16744         while (*p && *p != '\"') p++;
16745         if (*p == '\"') p++;
16746       } else {
16747         while (*p && *p != ' ') p++;
16748       }
16749     }
16750   }
16751
16752 }
16753
16754 void
16755 PeriodicUpdatesEvent (int newState)
16756 {
16757     if (newState == appData.periodicUpdates)
16758       return;
16759
16760     appData.periodicUpdates=newState;
16761
16762     /* Display type changes, so update it now */
16763 //    DisplayAnalysis();
16764
16765     /* Get the ball rolling again... */
16766     if (newState) {
16767         AnalysisPeriodicEvent(1);
16768         StartAnalysisClock();
16769     }
16770 }
16771
16772 void
16773 PonderNextMoveEvent (int newState)
16774 {
16775     if (newState == appData.ponderNextMove) return;
16776     if (gameMode == EditPosition) EditPositionDone(TRUE);
16777     if (newState) {
16778         SendToProgram("hard\n", &first);
16779         if (gameMode == TwoMachinesPlay) {
16780             SendToProgram("hard\n", &second);
16781         }
16782     } else {
16783         SendToProgram("easy\n", &first);
16784         thinkOutput[0] = NULLCHAR;
16785         if (gameMode == TwoMachinesPlay) {
16786             SendToProgram("easy\n", &second);
16787         }
16788     }
16789     appData.ponderNextMove = newState;
16790 }
16791
16792 void
16793 NewSettingEvent (int option, int *feature, char *command, int value)
16794 {
16795     char buf[MSG_SIZ];
16796
16797     if (gameMode == EditPosition) EditPositionDone(TRUE);
16798     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16799     if(feature == NULL || *feature) SendToProgram(buf, &first);
16800     if (gameMode == TwoMachinesPlay) {
16801         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16802     }
16803 }
16804
16805 void
16806 ShowThinkingEvent ()
16807 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16808 {
16809     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16810     int newState = appData.showThinking
16811         // [HGM] thinking: other features now need thinking output as well
16812         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16813
16814     if (oldState == newState) return;
16815     oldState = newState;
16816     if (gameMode == EditPosition) EditPositionDone(TRUE);
16817     if (oldState) {
16818         SendToProgram("post\n", &first);
16819         if (gameMode == TwoMachinesPlay) {
16820             SendToProgram("post\n", &second);
16821         }
16822     } else {
16823         SendToProgram("nopost\n", &first);
16824         thinkOutput[0] = NULLCHAR;
16825         if (gameMode == TwoMachinesPlay) {
16826             SendToProgram("nopost\n", &second);
16827         }
16828     }
16829 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16830 }
16831
16832 void
16833 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16834 {
16835   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16836   if (pr == NoProc) return;
16837   AskQuestion(title, question, replyPrefix, pr);
16838 }
16839
16840 void
16841 TypeInEvent (char firstChar)
16842 {
16843     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16844         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16845         gameMode == AnalyzeMode || gameMode == EditGame ||
16846         gameMode == EditPosition || gameMode == IcsExamining ||
16847         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16848         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16849                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16850                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16851         gameMode == Training) PopUpMoveDialog(firstChar);
16852 }
16853
16854 void
16855 TypeInDoneEvent (char *move)
16856 {
16857         Board board;
16858         int n, fromX, fromY, toX, toY;
16859         char promoChar;
16860         ChessMove moveType;
16861
16862         // [HGM] FENedit
16863         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16864                 EditPositionPasteFEN(move);
16865                 return;
16866         }
16867         // [HGM] movenum: allow move number to be typed in any mode
16868         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16869           ToNrEvent(2*n-1);
16870           return;
16871         }
16872         // undocumented kludge: allow command-line option to be typed in!
16873         // (potentially fatal, and does not implement the effect of the option.)
16874         // should only be used for options that are values on which future decisions will be made,
16875         // and definitely not on options that would be used during initialization.
16876         if(strstr(move, "!!! -") == move) {
16877             ParseArgsFromString(move+4);
16878             return;
16879         }
16880
16881       if (gameMode != EditGame && currentMove != forwardMostMove &&
16882         gameMode != Training) {
16883         DisplayMoveError(_("Displayed move is not current"));
16884       } else {
16885         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16886           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16887         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16888         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16889           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16890           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16891         } else {
16892           DisplayMoveError(_("Could not parse move"));
16893         }
16894       }
16895 }
16896
16897 void
16898 DisplayMove (int moveNumber)
16899 {
16900     char message[MSG_SIZ];
16901     char res[MSG_SIZ];
16902     char cpThinkOutput[MSG_SIZ];
16903
16904     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16905
16906     if (moveNumber == forwardMostMove - 1 ||
16907         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16908
16909         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16910
16911         if (strchr(cpThinkOutput, '\n')) {
16912             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16913         }
16914     } else {
16915         *cpThinkOutput = NULLCHAR;
16916     }
16917
16918     /* [AS] Hide thinking from human user */
16919     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16920         *cpThinkOutput = NULLCHAR;
16921         if( thinkOutput[0] != NULLCHAR ) {
16922             int i;
16923
16924             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16925                 cpThinkOutput[i] = '.';
16926             }
16927             cpThinkOutput[i] = NULLCHAR;
16928             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16929         }
16930     }
16931
16932     if (moveNumber == forwardMostMove - 1 &&
16933         gameInfo.resultDetails != NULL) {
16934         if (gameInfo.resultDetails[0] == NULLCHAR) {
16935           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16936         } else {
16937           snprintf(res, MSG_SIZ, " {%s} %s",
16938                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16939         }
16940     } else {
16941         res[0] = NULLCHAR;
16942     }
16943
16944     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16945         DisplayMessage(res, cpThinkOutput);
16946     } else {
16947       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16948                 WhiteOnMove(moveNumber) ? " " : ".. ",
16949                 parseList[moveNumber], res);
16950         DisplayMessage(message, cpThinkOutput);
16951     }
16952 }
16953
16954 void
16955 DisplayComment (int moveNumber, char *text)
16956 {
16957     char title[MSG_SIZ];
16958
16959     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16960       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16961     } else {
16962       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16963               WhiteOnMove(moveNumber) ? " " : ".. ",
16964               parseList[moveNumber]);
16965     }
16966     if (text != NULL && (appData.autoDisplayComment || commentUp))
16967         CommentPopUp(title, text);
16968 }
16969
16970 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16971  * might be busy thinking or pondering.  It can be omitted if your
16972  * gnuchess is configured to stop thinking immediately on any user
16973  * input.  However, that gnuchess feature depends on the FIONREAD
16974  * ioctl, which does not work properly on some flavors of Unix.
16975  */
16976 void
16977 Attention (ChessProgramState *cps)
16978 {
16979 #if ATTENTION
16980     if (!cps->useSigint) return;
16981     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16982     switch (gameMode) {
16983       case MachinePlaysWhite:
16984       case MachinePlaysBlack:
16985       case TwoMachinesPlay:
16986       case IcsPlayingWhite:
16987       case IcsPlayingBlack:
16988       case AnalyzeMode:
16989       case AnalyzeFile:
16990         /* Skip if we know it isn't thinking */
16991         if (!cps->maybeThinking) return;
16992         if (appData.debugMode)
16993           fprintf(debugFP, "Interrupting %s\n", cps->which);
16994         InterruptChildProcess(cps->pr);
16995         cps->maybeThinking = FALSE;
16996         break;
16997       default:
16998         break;
16999     }
17000 #endif /*ATTENTION*/
17001 }
17002
17003 int
17004 CheckFlags ()
17005 {
17006     if (whiteTimeRemaining <= 0) {
17007         if (!whiteFlag) {
17008             whiteFlag = TRUE;
17009             if (appData.icsActive) {
17010                 if (appData.autoCallFlag &&
17011                     gameMode == IcsPlayingBlack && !blackFlag) {
17012                   SendToICS(ics_prefix);
17013                   SendToICS("flag\n");
17014                 }
17015             } else {
17016                 if (blackFlag) {
17017                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17018                 } else {
17019                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17020                     if (appData.autoCallFlag) {
17021                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17022                         return TRUE;
17023                     }
17024                 }
17025             }
17026         }
17027     }
17028     if (blackTimeRemaining <= 0) {
17029         if (!blackFlag) {
17030             blackFlag = TRUE;
17031             if (appData.icsActive) {
17032                 if (appData.autoCallFlag &&
17033                     gameMode == IcsPlayingWhite && !whiteFlag) {
17034                   SendToICS(ics_prefix);
17035                   SendToICS("flag\n");
17036                 }
17037             } else {
17038                 if (whiteFlag) {
17039                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17040                 } else {
17041                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17042                     if (appData.autoCallFlag) {
17043                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17044                         return TRUE;
17045                     }
17046                 }
17047             }
17048         }
17049     }
17050     return FALSE;
17051 }
17052
17053 void
17054 CheckTimeControl ()
17055 {
17056     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17057         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17058
17059     /*
17060      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17061      */
17062     if ( !WhiteOnMove(forwardMostMove) ) {
17063         /* White made time control */
17064         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17065         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17066         /* [HGM] time odds: correct new time quota for time odds! */
17067                                             / WhitePlayer()->timeOdds;
17068         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17069     } else {
17070         lastBlack -= blackTimeRemaining;
17071         /* Black made time control */
17072         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17073                                             / WhitePlayer()->other->timeOdds;
17074         lastWhite = whiteTimeRemaining;
17075     }
17076 }
17077
17078 void
17079 DisplayBothClocks ()
17080 {
17081     int wom = gameMode == EditPosition ?
17082       !blackPlaysFirst : WhiteOnMove(currentMove);
17083     DisplayWhiteClock(whiteTimeRemaining, wom);
17084     DisplayBlackClock(blackTimeRemaining, !wom);
17085 }
17086
17087
17088 /* Timekeeping seems to be a portability nightmare.  I think everyone
17089    has ftime(), but I'm really not sure, so I'm including some ifdefs
17090    to use other calls if you don't.  Clocks will be less accurate if
17091    you have neither ftime nor gettimeofday.
17092 */
17093
17094 /* VS 2008 requires the #include outside of the function */
17095 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17096 #include <sys/timeb.h>
17097 #endif
17098
17099 /* Get the current time as a TimeMark */
17100 void
17101 GetTimeMark (TimeMark *tm)
17102 {
17103 #if HAVE_GETTIMEOFDAY
17104
17105     struct timeval timeVal;
17106     struct timezone timeZone;
17107
17108     gettimeofday(&timeVal, &timeZone);
17109     tm->sec = (long) timeVal.tv_sec;
17110     tm->ms = (int) (timeVal.tv_usec / 1000L);
17111
17112 #else /*!HAVE_GETTIMEOFDAY*/
17113 #if HAVE_FTIME
17114
17115 // include <sys/timeb.h> / moved to just above start of function
17116     struct timeb timeB;
17117
17118     ftime(&timeB);
17119     tm->sec = (long) timeB.time;
17120     tm->ms = (int) timeB.millitm;
17121
17122 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17123     tm->sec = (long) time(NULL);
17124     tm->ms = 0;
17125 #endif
17126 #endif
17127 }
17128
17129 /* Return the difference in milliseconds between two
17130    time marks.  We assume the difference will fit in a long!
17131 */
17132 long
17133 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17134 {
17135     return 1000L*(tm2->sec - tm1->sec) +
17136            (long) (tm2->ms - tm1->ms);
17137 }
17138
17139
17140 /*
17141  * Code to manage the game clocks.
17142  *
17143  * In tournament play, black starts the clock and then white makes a move.
17144  * We give the human user a slight advantage if he is playing white---the
17145  * clocks don't run until he makes his first move, so it takes zero time.
17146  * Also, we don't account for network lag, so we could get out of sync
17147  * with GNU Chess's clock -- but then, referees are always right.
17148  */
17149
17150 static TimeMark tickStartTM;
17151 static long intendedTickLength;
17152
17153 long
17154 NextTickLength (long timeRemaining)
17155 {
17156     long nominalTickLength, nextTickLength;
17157
17158     if (timeRemaining > 0L && timeRemaining <= 10000L)
17159       nominalTickLength = 100L;
17160     else
17161       nominalTickLength = 1000L;
17162     nextTickLength = timeRemaining % nominalTickLength;
17163     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17164
17165     return nextTickLength;
17166 }
17167
17168 /* Adjust clock one minute up or down */
17169 void
17170 AdjustClock (Boolean which, int dir)
17171 {
17172     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17173     if(which) blackTimeRemaining += 60000*dir;
17174     else      whiteTimeRemaining += 60000*dir;
17175     DisplayBothClocks();
17176     adjustedClock = TRUE;
17177 }
17178
17179 /* Stop clocks and reset to a fresh time control */
17180 void
17181 ResetClocks ()
17182 {
17183     (void) StopClockTimer();
17184     if (appData.icsActive) {
17185         whiteTimeRemaining = blackTimeRemaining = 0;
17186     } else if (searchTime) {
17187         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17188         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17189     } else { /* [HGM] correct new time quote for time odds */
17190         whiteTC = blackTC = fullTimeControlString;
17191         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17192         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17193     }
17194     if (whiteFlag || blackFlag) {
17195         DisplayTitle("");
17196         whiteFlag = blackFlag = FALSE;
17197     }
17198     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17199     DisplayBothClocks();
17200     adjustedClock = FALSE;
17201 }
17202
17203 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17204
17205 /* Decrement running clock by amount of time that has passed */
17206 void
17207 DecrementClocks ()
17208 {
17209     long timeRemaining;
17210     long lastTickLength, fudge;
17211     TimeMark now;
17212
17213     if (!appData.clockMode) return;
17214     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17215
17216     GetTimeMark(&now);
17217
17218     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17219
17220     /* Fudge if we woke up a little too soon */
17221     fudge = intendedTickLength - lastTickLength;
17222     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17223
17224     if (WhiteOnMove(forwardMostMove)) {
17225         if(whiteNPS >= 0) lastTickLength = 0;
17226         timeRemaining = whiteTimeRemaining -= lastTickLength;
17227         if(timeRemaining < 0 && !appData.icsActive) {
17228             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17229             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17230                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17231                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17232             }
17233         }
17234         DisplayWhiteClock(whiteTimeRemaining - fudge,
17235                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17236     } else {
17237         if(blackNPS >= 0) lastTickLength = 0;
17238         timeRemaining = blackTimeRemaining -= lastTickLength;
17239         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17240             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17241             if(suddenDeath) {
17242                 blackStartMove = forwardMostMove;
17243                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17244             }
17245         }
17246         DisplayBlackClock(blackTimeRemaining - fudge,
17247                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17248     }
17249     if (CheckFlags()) return;
17250
17251     if(twoBoards) { // count down secondary board's clocks as well
17252         activePartnerTime -= lastTickLength;
17253         partnerUp = 1;
17254         if(activePartner == 'W')
17255             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17256         else
17257             DisplayBlackClock(activePartnerTime, TRUE);
17258         partnerUp = 0;
17259     }
17260
17261     tickStartTM = now;
17262     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17263     StartClockTimer(intendedTickLength);
17264
17265     /* if the time remaining has fallen below the alarm threshold, sound the
17266      * alarm. if the alarm has sounded and (due to a takeback or time control
17267      * with increment) the time remaining has increased to a level above the
17268      * threshold, reset the alarm so it can sound again.
17269      */
17270
17271     if (appData.icsActive && appData.icsAlarm) {
17272
17273         /* make sure we are dealing with the user's clock */
17274         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17275                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17276            )) return;
17277
17278         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17279             alarmSounded = FALSE;
17280         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17281             PlayAlarmSound();
17282             alarmSounded = TRUE;
17283         }
17284     }
17285 }
17286
17287
17288 /* A player has just moved, so stop the previously running
17289    clock and (if in clock mode) start the other one.
17290    We redisplay both clocks in case we're in ICS mode, because
17291    ICS gives us an update to both clocks after every move.
17292    Note that this routine is called *after* forwardMostMove
17293    is updated, so the last fractional tick must be subtracted
17294    from the color that is *not* on move now.
17295 */
17296 void
17297 SwitchClocks (int newMoveNr)
17298 {
17299     long lastTickLength;
17300     TimeMark now;
17301     int flagged = FALSE;
17302
17303     GetTimeMark(&now);
17304
17305     if (StopClockTimer() && appData.clockMode) {
17306         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17307         if (!WhiteOnMove(forwardMostMove)) {
17308             if(blackNPS >= 0) lastTickLength = 0;
17309             blackTimeRemaining -= lastTickLength;
17310            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17311 //         if(pvInfoList[forwardMostMove].time == -1)
17312                  pvInfoList[forwardMostMove].time =               // use GUI time
17313                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17314         } else {
17315            if(whiteNPS >= 0) lastTickLength = 0;
17316            whiteTimeRemaining -= lastTickLength;
17317            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17318 //         if(pvInfoList[forwardMostMove].time == -1)
17319                  pvInfoList[forwardMostMove].time =
17320                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17321         }
17322         flagged = CheckFlags();
17323     }
17324     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17325     CheckTimeControl();
17326
17327     if (flagged || !appData.clockMode) return;
17328
17329     switch (gameMode) {
17330       case MachinePlaysBlack:
17331       case MachinePlaysWhite:
17332       case BeginningOfGame:
17333         if (pausing) return;
17334         break;
17335
17336       case EditGame:
17337       case PlayFromGameFile:
17338       case IcsExamining:
17339         return;
17340
17341       default:
17342         break;
17343     }
17344
17345     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17346         if(WhiteOnMove(forwardMostMove))
17347              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17348         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17349     }
17350
17351     tickStartTM = now;
17352     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17353       whiteTimeRemaining : blackTimeRemaining);
17354     StartClockTimer(intendedTickLength);
17355 }
17356
17357
17358 /* Stop both clocks */
17359 void
17360 StopClocks ()
17361 {
17362     long lastTickLength;
17363     TimeMark now;
17364
17365     if (!StopClockTimer()) return;
17366     if (!appData.clockMode) return;
17367
17368     GetTimeMark(&now);
17369
17370     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17371     if (WhiteOnMove(forwardMostMove)) {
17372         if(whiteNPS >= 0) lastTickLength = 0;
17373         whiteTimeRemaining -= lastTickLength;
17374         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17375     } else {
17376         if(blackNPS >= 0) lastTickLength = 0;
17377         blackTimeRemaining -= lastTickLength;
17378         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17379     }
17380     CheckFlags();
17381 }
17382
17383 /* Start clock of player on move.  Time may have been reset, so
17384    if clock is already running, stop and restart it. */
17385 void
17386 StartClocks ()
17387 {
17388     (void) StopClockTimer(); /* in case it was running already */
17389     DisplayBothClocks();
17390     if (CheckFlags()) return;
17391
17392     if (!appData.clockMode) return;
17393     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17394
17395     GetTimeMark(&tickStartTM);
17396     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17397       whiteTimeRemaining : blackTimeRemaining);
17398
17399    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17400     whiteNPS = blackNPS = -1;
17401     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17402        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17403         whiteNPS = first.nps;
17404     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17405        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17406         blackNPS = first.nps;
17407     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17408         whiteNPS = second.nps;
17409     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17410         blackNPS = second.nps;
17411     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17412
17413     StartClockTimer(intendedTickLength);
17414 }
17415
17416 char *
17417 TimeString (long ms)
17418 {
17419     long second, minute, hour, day;
17420     char *sign = "";
17421     static char buf[32];
17422
17423     if (ms > 0 && ms <= 9900) {
17424       /* convert milliseconds to tenths, rounding up */
17425       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17426
17427       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17428       return buf;
17429     }
17430
17431     /* convert milliseconds to seconds, rounding up */
17432     /* use floating point to avoid strangeness of integer division
17433        with negative dividends on many machines */
17434     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17435
17436     if (second < 0) {
17437         sign = "-";
17438         second = -second;
17439     }
17440
17441     day = second / (60 * 60 * 24);
17442     second = second % (60 * 60 * 24);
17443     hour = second / (60 * 60);
17444     second = second % (60 * 60);
17445     minute = second / 60;
17446     second = second % 60;
17447
17448     if (day > 0)
17449       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17450               sign, day, hour, minute, second);
17451     else if (hour > 0)
17452       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17453     else
17454       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17455
17456     return buf;
17457 }
17458
17459
17460 /*
17461  * This is necessary because some C libraries aren't ANSI C compliant yet.
17462  */
17463 char *
17464 StrStr (char *string, char *match)
17465 {
17466     int i, length;
17467
17468     length = strlen(match);
17469
17470     for (i = strlen(string) - length; i >= 0; i--, string++)
17471       if (!strncmp(match, string, length))
17472         return string;
17473
17474     return NULL;
17475 }
17476
17477 char *
17478 StrCaseStr (char *string, char *match)
17479 {
17480     int i, j, length;
17481
17482     length = strlen(match);
17483
17484     for (i = strlen(string) - length; i >= 0; i--, string++) {
17485         for (j = 0; j < length; j++) {
17486             if (ToLower(match[j]) != ToLower(string[j]))
17487               break;
17488         }
17489         if (j == length) return string;
17490     }
17491
17492     return NULL;
17493 }
17494
17495 #ifndef _amigados
17496 int
17497 StrCaseCmp (char *s1, char *s2)
17498 {
17499     char c1, c2;
17500
17501     for (;;) {
17502         c1 = ToLower(*s1++);
17503         c2 = ToLower(*s2++);
17504         if (c1 > c2) return 1;
17505         if (c1 < c2) return -1;
17506         if (c1 == NULLCHAR) return 0;
17507     }
17508 }
17509
17510
17511 int
17512 ToLower (int c)
17513 {
17514     return isupper(c) ? tolower(c) : c;
17515 }
17516
17517
17518 int
17519 ToUpper (int c)
17520 {
17521     return islower(c) ? toupper(c) : c;
17522 }
17523 #endif /* !_amigados    */
17524
17525 char *
17526 StrSave (char *s)
17527 {
17528   char *ret;
17529
17530   if ((ret = (char *) malloc(strlen(s) + 1)))
17531     {
17532       safeStrCpy(ret, s, strlen(s)+1);
17533     }
17534   return ret;
17535 }
17536
17537 char *
17538 StrSavePtr (char *s, char **savePtr)
17539 {
17540     if (*savePtr) {
17541         free(*savePtr);
17542     }
17543     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17544       safeStrCpy(*savePtr, s, strlen(s)+1);
17545     }
17546     return(*savePtr);
17547 }
17548
17549 char *
17550 PGNDate ()
17551 {
17552     time_t clock;
17553     struct tm *tm;
17554     char buf[MSG_SIZ];
17555
17556     clock = time((time_t *)NULL);
17557     tm = localtime(&clock);
17558     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17559             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17560     return StrSave(buf);
17561 }
17562
17563
17564 char *
17565 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17566 {
17567     int i, j, fromX, fromY, toX, toY;
17568     int whiteToPlay;
17569     char buf[MSG_SIZ];
17570     char *p, *q;
17571     int emptycount;
17572     ChessSquare piece;
17573
17574     whiteToPlay = (gameMode == EditPosition) ?
17575       !blackPlaysFirst : (move % 2 == 0);
17576     p = buf;
17577
17578     /* Piece placement data */
17579     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17580         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17581         emptycount = 0;
17582         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17583             if (boards[move][i][j] == EmptySquare) {
17584                 emptycount++;
17585             } else { ChessSquare piece = boards[move][i][j];
17586                 if (emptycount > 0) {
17587                     if(emptycount<10) /* [HGM] can be >= 10 */
17588                         *p++ = '0' + emptycount;
17589                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17590                     emptycount = 0;
17591                 }
17592                 if(PieceToChar(piece) == '+') {
17593                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17594                     *p++ = '+';
17595                     piece = (ChessSquare)(DEMOTED piece);
17596                 }
17597                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17598                 if(p[-1] == '~') {
17599                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17600                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17601                     *p++ = '~';
17602                 }
17603             }
17604         }
17605         if (emptycount > 0) {
17606             if(emptycount<10) /* [HGM] can be >= 10 */
17607                 *p++ = '0' + emptycount;
17608             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17609             emptycount = 0;
17610         }
17611         *p++ = '/';
17612     }
17613     *(p - 1) = ' ';
17614
17615     /* [HGM] print Crazyhouse or Shogi holdings */
17616     if( gameInfo.holdingsWidth ) {
17617         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17618         q = p;
17619         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17620             piece = boards[move][i][BOARD_WIDTH-1];
17621             if( piece != EmptySquare )
17622               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17623                   *p++ = PieceToChar(piece);
17624         }
17625         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17626             piece = boards[move][BOARD_HEIGHT-i-1][0];
17627             if( piece != EmptySquare )
17628               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17629                   *p++ = PieceToChar(piece);
17630         }
17631
17632         if( q == p ) *p++ = '-';
17633         *p++ = ']';
17634         *p++ = ' ';
17635     }
17636
17637     /* Active color */
17638     *p++ = whiteToPlay ? 'w' : 'b';
17639     *p++ = ' ';
17640
17641   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17642     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17643   } else {
17644   if(nrCastlingRights) {
17645      q = p;
17646      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17647        /* [HGM] write directly from rights */
17648            if(boards[move][CASTLING][2] != NoRights &&
17649               boards[move][CASTLING][0] != NoRights   )
17650                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17651            if(boards[move][CASTLING][2] != NoRights &&
17652               boards[move][CASTLING][1] != NoRights   )
17653                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17654            if(boards[move][CASTLING][5] != NoRights &&
17655               boards[move][CASTLING][3] != NoRights   )
17656                 *p++ = boards[move][CASTLING][3] + AAA;
17657            if(boards[move][CASTLING][5] != NoRights &&
17658               boards[move][CASTLING][4] != NoRights   )
17659                 *p++ = boards[move][CASTLING][4] + AAA;
17660      } else {
17661
17662         /* [HGM] write true castling rights */
17663         if( nrCastlingRights == 6 ) {
17664             int q, k=0;
17665             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17666                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17667             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17668                  boards[move][CASTLING][2] != NoRights  );
17669             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17670                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17671                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17672                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17673                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17674             }
17675             if(q) *p++ = 'Q';
17676             k = 0;
17677             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17678                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17679             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17680                  boards[move][CASTLING][5] != NoRights  );
17681             if(gameInfo.variant == VariantSChess) {
17682                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17683                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17684                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17685                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17686             }
17687             if(q) *p++ = 'q';
17688         }
17689      }
17690      if (q == p) *p++ = '-'; /* No castling rights */
17691      *p++ = ' ';
17692   }
17693
17694   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17695      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17696      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17697     /* En passant target square */
17698     if (move > backwardMostMove) {
17699         fromX = moveList[move - 1][0] - AAA;
17700         fromY = moveList[move - 1][1] - ONE;
17701         toX = moveList[move - 1][2] - AAA;
17702         toY = moveList[move - 1][3] - ONE;
17703         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17704             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17705             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17706             fromX == toX) {
17707             /* 2-square pawn move just happened */
17708             *p++ = toX + AAA;
17709             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17710         } else {
17711             *p++ = '-';
17712         }
17713     } else if(move == backwardMostMove) {
17714         // [HGM] perhaps we should always do it like this, and forget the above?
17715         if((signed char)boards[move][EP_STATUS] >= 0) {
17716             *p++ = boards[move][EP_STATUS] + AAA;
17717             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17718         } else {
17719             *p++ = '-';
17720         }
17721     } else {
17722         *p++ = '-';
17723     }
17724     *p++ = ' ';
17725   }
17726   }
17727
17728     if(moveCounts)
17729     {   int i = 0, j=move;
17730
17731         /* [HGM] find reversible plies */
17732         if (appData.debugMode) { int k;
17733             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17734             for(k=backwardMostMove; k<=forwardMostMove; k++)
17735                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17736
17737         }
17738
17739         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17740         if( j == backwardMostMove ) i += initialRulePlies;
17741         sprintf(p, "%d ", i);
17742         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17743
17744         /* Fullmove number */
17745         sprintf(p, "%d", (move / 2) + 1);
17746     } else *--p = NULLCHAR;
17747
17748     return StrSave(buf);
17749 }
17750
17751 Boolean
17752 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17753 {
17754     int i, j, k, w=0;
17755     char *p, c;
17756     int emptycount, virgin[BOARD_FILES];
17757     ChessSquare piece;
17758
17759     p = fen;
17760
17761     /* Piece placement data */
17762     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17763         j = 0;
17764         for (;;) {
17765             if (*p == '/' || *p == ' ' || *p == '[' ) {
17766                 if(j > w) w = j;
17767                 emptycount = gameInfo.boardWidth - j;
17768                 while (emptycount--)
17769                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17770                 if (*p == '/') p++;
17771                 else if(autoSize) { // we stumbled unexpectedly into end of board
17772                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17773                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17774                     }
17775                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17776                 }
17777                 break;
17778 #if(BOARD_FILES >= 10)
17779             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17780                 p++; emptycount=10;
17781                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17782                 while (emptycount--)
17783                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17784 #endif
17785             } else if (*p == '*') {
17786                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17787             } else if (isdigit(*p)) {
17788                 emptycount = *p++ - '0';
17789                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17790                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17791                 while (emptycount--)
17792                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17793             } else if (*p == '+' || isalpha(*p)) {
17794                 if (j >= gameInfo.boardWidth) return FALSE;
17795                 if(*p=='+') {
17796                     piece = CharToPiece(*++p);
17797                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17798                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17799                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17800                 } else piece = CharToPiece(*p++);
17801
17802                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17803                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17804                     piece = (ChessSquare) (PROMOTED piece);
17805                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17806                     p++;
17807                 }
17808                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17809             } else {
17810                 return FALSE;
17811             }
17812         }
17813     }
17814     while (*p == '/' || *p == ' ') p++;
17815
17816     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17817
17818     /* [HGM] by default clear Crazyhouse holdings, if present */
17819     if(gameInfo.holdingsWidth) {
17820        for(i=0; i<BOARD_HEIGHT; i++) {
17821            board[i][0]             = EmptySquare; /* black holdings */
17822            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17823            board[i][1]             = (ChessSquare) 0; /* black counts */
17824            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17825        }
17826     }
17827
17828     /* [HGM] look for Crazyhouse holdings here */
17829     while(*p==' ') p++;
17830     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17831         if(*p == '[') p++;
17832         if(*p == '-' ) p++; /* empty holdings */ else {
17833             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17834             /* if we would allow FEN reading to set board size, we would   */
17835             /* have to add holdings and shift the board read so far here   */
17836             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17837                 p++;
17838                 if((int) piece >= (int) BlackPawn ) {
17839                     i = (int)piece - (int)BlackPawn;
17840                     i = PieceToNumber((ChessSquare)i);
17841                     if( i >= gameInfo.holdingsSize ) return FALSE;
17842                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17843                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17844                 } else {
17845                     i = (int)piece - (int)WhitePawn;
17846                     i = PieceToNumber((ChessSquare)i);
17847                     if( i >= gameInfo.holdingsSize ) return FALSE;
17848                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17849                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17850                 }
17851             }
17852         }
17853         if(*p == ']') p++;
17854     }
17855
17856     while(*p == ' ') p++;
17857
17858     /* Active color */
17859     c = *p++;
17860     if(appData.colorNickNames) {
17861       if( c == appData.colorNickNames[0] ) c = 'w'; else
17862       if( c == appData.colorNickNames[1] ) c = 'b';
17863     }
17864     switch (c) {
17865       case 'w':
17866         *blackPlaysFirst = FALSE;
17867         break;
17868       case 'b':
17869         *blackPlaysFirst = TRUE;
17870         break;
17871       default:
17872         return FALSE;
17873     }
17874
17875     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17876     /* return the extra info in global variiables             */
17877
17878     /* set defaults in case FEN is incomplete */
17879     board[EP_STATUS] = EP_UNKNOWN;
17880     for(i=0; i<nrCastlingRights; i++ ) {
17881         board[CASTLING][i] =
17882             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17883     }   /* assume possible unless obviously impossible */
17884     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17885     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17886     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17887                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17888     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17889     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17890     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17891                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17892     FENrulePlies = 0;
17893
17894     while(*p==' ') p++;
17895     if(nrCastlingRights) {
17896       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17897       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17898           /* castling indicator present, so default becomes no castlings */
17899           for(i=0; i<nrCastlingRights; i++ ) {
17900                  board[CASTLING][i] = NoRights;
17901           }
17902       }
17903       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17904              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17905              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17906              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17907         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17908
17909         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17910             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17911             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17912         }
17913         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17914             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17915         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17916                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17917         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17918                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17919         switch(c) {
17920           case'K':
17921               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17922               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17923               board[CASTLING][2] = whiteKingFile;
17924               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17925               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17926               break;
17927           case'Q':
17928               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17929               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17930               board[CASTLING][2] = whiteKingFile;
17931               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17932               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17933               break;
17934           case'k':
17935               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17936               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17937               board[CASTLING][5] = blackKingFile;
17938               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17939               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17940               break;
17941           case'q':
17942               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17943               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17944               board[CASTLING][5] = blackKingFile;
17945               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17946               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17947           case '-':
17948               break;
17949           default: /* FRC castlings */
17950               if(c >= 'a') { /* black rights */
17951                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17952                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17953                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17954                   if(i == BOARD_RGHT) break;
17955                   board[CASTLING][5] = i;
17956                   c -= AAA;
17957                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17958                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17959                   if(c > i)
17960                       board[CASTLING][3] = c;
17961                   else
17962                       board[CASTLING][4] = c;
17963               } else { /* white rights */
17964                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17965                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17966                     if(board[0][i] == WhiteKing) break;
17967                   if(i == BOARD_RGHT) break;
17968                   board[CASTLING][2] = i;
17969                   c -= AAA - 'a' + 'A';
17970                   if(board[0][c] >= WhiteKing) break;
17971                   if(c > i)
17972                       board[CASTLING][0] = c;
17973                   else
17974                       board[CASTLING][1] = c;
17975               }
17976         }
17977       }
17978       for(i=0; i<nrCastlingRights; i++)
17979         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17980       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17981     if (appData.debugMode) {
17982         fprintf(debugFP, "FEN castling rights:");
17983         for(i=0; i<nrCastlingRights; i++)
17984         fprintf(debugFP, " %d", board[CASTLING][i]);
17985         fprintf(debugFP, "\n");
17986     }
17987
17988       while(*p==' ') p++;
17989     }
17990
17991     /* read e.p. field in games that know e.p. capture */
17992     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17993        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17994        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17995       if(*p=='-') {
17996         p++; board[EP_STATUS] = EP_NONE;
17997       } else {
17998          char c = *p++ - AAA;
17999
18000          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18001          if(*p >= '0' && *p <='9') p++;
18002          board[EP_STATUS] = c;
18003       }
18004     }
18005
18006
18007     if(sscanf(p, "%d", &i) == 1) {
18008         FENrulePlies = i; /* 50-move ply counter */
18009         /* (The move number is still ignored)    */
18010     }
18011
18012     return TRUE;
18013 }
18014
18015 void
18016 EditPositionPasteFEN (char *fen)
18017 {
18018   if (fen != NULL) {
18019     Board initial_position;
18020
18021     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18022       DisplayError(_("Bad FEN position in clipboard"), 0);
18023       return ;
18024     } else {
18025       int savedBlackPlaysFirst = blackPlaysFirst;
18026       EditPositionEvent();
18027       blackPlaysFirst = savedBlackPlaysFirst;
18028       CopyBoard(boards[0], initial_position);
18029       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18030       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18031       DisplayBothClocks();
18032       DrawPosition(FALSE, boards[currentMove]);
18033     }
18034   }
18035 }
18036
18037 static char cseq[12] = "\\   ";
18038
18039 Boolean
18040 set_cont_sequence (char *new_seq)
18041 {
18042     int len;
18043     Boolean ret;
18044
18045     // handle bad attempts to set the sequence
18046         if (!new_seq)
18047                 return 0; // acceptable error - no debug
18048
18049     len = strlen(new_seq);
18050     ret = (len > 0) && (len < sizeof(cseq));
18051     if (ret)
18052       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18053     else if (appData.debugMode)
18054       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18055     return ret;
18056 }
18057
18058 /*
18059     reformat a source message so words don't cross the width boundary.  internal
18060     newlines are not removed.  returns the wrapped size (no null character unless
18061     included in source message).  If dest is NULL, only calculate the size required
18062     for the dest buffer.  lp argument indicats line position upon entry, and it's
18063     passed back upon exit.
18064 */
18065 int
18066 wrap (char *dest, char *src, int count, int width, int *lp)
18067 {
18068     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18069
18070     cseq_len = strlen(cseq);
18071     old_line = line = *lp;
18072     ansi = len = clen = 0;
18073
18074     for (i=0; i < count; i++)
18075     {
18076         if (src[i] == '\033')
18077             ansi = 1;
18078
18079         // if we hit the width, back up
18080         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18081         {
18082             // store i & len in case the word is too long
18083             old_i = i, old_len = len;
18084
18085             // find the end of the last word
18086             while (i && src[i] != ' ' && src[i] != '\n')
18087             {
18088                 i--;
18089                 len--;
18090             }
18091
18092             // word too long?  restore i & len before splitting it
18093             if ((old_i-i+clen) >= width)
18094             {
18095                 i = old_i;
18096                 len = old_len;
18097             }
18098
18099             // extra space?
18100             if (i && src[i-1] == ' ')
18101                 len--;
18102
18103             if (src[i] != ' ' && src[i] != '\n')
18104             {
18105                 i--;
18106                 if (len)
18107                     len--;
18108             }
18109
18110             // now append the newline and continuation sequence
18111             if (dest)
18112                 dest[len] = '\n';
18113             len++;
18114             if (dest)
18115                 strncpy(dest+len, cseq, cseq_len);
18116             len += cseq_len;
18117             line = cseq_len;
18118             clen = cseq_len;
18119             continue;
18120         }
18121
18122         if (dest)
18123             dest[len] = src[i];
18124         len++;
18125         if (!ansi)
18126             line++;
18127         if (src[i] == '\n')
18128             line = 0;
18129         if (src[i] == 'm')
18130             ansi = 0;
18131     }
18132     if (dest && appData.debugMode)
18133     {
18134         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18135             count, width, line, len, *lp);
18136         show_bytes(debugFP, src, count);
18137         fprintf(debugFP, "\ndest: ");
18138         show_bytes(debugFP, dest, len);
18139         fprintf(debugFP, "\n");
18140     }
18141     *lp = dest ? line : old_line;
18142
18143     return len;
18144 }
18145
18146 // [HGM] vari: routines for shelving variations
18147 Boolean modeRestore = FALSE;
18148
18149 void
18150 PushInner (int firstMove, int lastMove)
18151 {
18152         int i, j, nrMoves = lastMove - firstMove;
18153
18154         // push current tail of game on stack
18155         savedResult[storedGames] = gameInfo.result;
18156         savedDetails[storedGames] = gameInfo.resultDetails;
18157         gameInfo.resultDetails = NULL;
18158         savedFirst[storedGames] = firstMove;
18159         savedLast [storedGames] = lastMove;
18160         savedFramePtr[storedGames] = framePtr;
18161         framePtr -= nrMoves; // reserve space for the boards
18162         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18163             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18164             for(j=0; j<MOVE_LEN; j++)
18165                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18166             for(j=0; j<2*MOVE_LEN; j++)
18167                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18168             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18169             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18170             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18171             pvInfoList[firstMove+i-1].depth = 0;
18172             commentList[framePtr+i] = commentList[firstMove+i];
18173             commentList[firstMove+i] = NULL;
18174         }
18175
18176         storedGames++;
18177         forwardMostMove = firstMove; // truncate game so we can start variation
18178 }
18179
18180 void
18181 PushTail (int firstMove, int lastMove)
18182 {
18183         if(appData.icsActive) { // only in local mode
18184                 forwardMostMove = currentMove; // mimic old ICS behavior
18185                 return;
18186         }
18187         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18188
18189         PushInner(firstMove, lastMove);
18190         if(storedGames == 1) GreyRevert(FALSE);
18191         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18192 }
18193
18194 void
18195 PopInner (Boolean annotate)
18196 {
18197         int i, j, nrMoves;
18198         char buf[8000], moveBuf[20];
18199
18200         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18201         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18202         nrMoves = savedLast[storedGames] - currentMove;
18203         if(annotate) {
18204                 int cnt = 10;
18205                 if(!WhiteOnMove(currentMove))
18206                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18207                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18208                 for(i=currentMove; i<forwardMostMove; i++) {
18209                         if(WhiteOnMove(i))
18210                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18211                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18212                         strcat(buf, moveBuf);
18213                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18214                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18215                 }
18216                 strcat(buf, ")");
18217         }
18218         for(i=1; i<=nrMoves; i++) { // copy last variation back
18219             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18220             for(j=0; j<MOVE_LEN; j++)
18221                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18222             for(j=0; j<2*MOVE_LEN; j++)
18223                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18224             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18225             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18226             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18227             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18228             commentList[currentMove+i] = commentList[framePtr+i];
18229             commentList[framePtr+i] = NULL;
18230         }
18231         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18232         framePtr = savedFramePtr[storedGames];
18233         gameInfo.result = savedResult[storedGames];
18234         if(gameInfo.resultDetails != NULL) {
18235             free(gameInfo.resultDetails);
18236       }
18237         gameInfo.resultDetails = savedDetails[storedGames];
18238         forwardMostMove = currentMove + nrMoves;
18239 }
18240
18241 Boolean
18242 PopTail (Boolean annotate)
18243 {
18244         if(appData.icsActive) return FALSE; // only in local mode
18245         if(!storedGames) return FALSE; // sanity
18246         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18247
18248         PopInner(annotate);
18249         if(currentMove < forwardMostMove) ForwardEvent(); else
18250         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18251
18252         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18253         return TRUE;
18254 }
18255
18256 void
18257 CleanupTail ()
18258 {       // remove all shelved variations
18259         int i;
18260         for(i=0; i<storedGames; i++) {
18261             if(savedDetails[i])
18262                 free(savedDetails[i]);
18263             savedDetails[i] = NULL;
18264         }
18265         for(i=framePtr; i<MAX_MOVES; i++) {
18266                 if(commentList[i]) free(commentList[i]);
18267                 commentList[i] = NULL;
18268         }
18269         framePtr = MAX_MOVES-1;
18270         storedGames = 0;
18271 }
18272
18273 void
18274 LoadVariation (int index, char *text)
18275 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18276         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18277         int level = 0, move;
18278
18279         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18280         // first find outermost bracketing variation
18281         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18282             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18283                 if(*p == '{') wait = '}'; else
18284                 if(*p == '[') wait = ']'; else
18285                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18286                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18287             }
18288             if(*p == wait) wait = NULLCHAR; // closing ]} found
18289             p++;
18290         }
18291         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18292         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18293         end[1] = NULLCHAR; // clip off comment beyond variation
18294         ToNrEvent(currentMove-1);
18295         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18296         // kludge: use ParsePV() to append variation to game
18297         move = currentMove;
18298         ParsePV(start, TRUE, TRUE);
18299         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18300         ClearPremoveHighlights();
18301         CommentPopDown();
18302         ToNrEvent(currentMove+1);
18303 }
18304
18305 void
18306 LoadTheme ()
18307 {
18308     char *p, *q, buf[MSG_SIZ];
18309     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18310         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18311         ParseArgsFromString(buf);
18312         ActivateTheme(TRUE); // also redo colors
18313         return;
18314     }
18315     p = nickName;
18316     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18317     {
18318         int len;
18319         q = appData.themeNames;
18320         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18321       if(appData.useBitmaps) {
18322         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18323                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18324                 appData.liteBackTextureMode,
18325                 appData.darkBackTextureMode );
18326       } else {
18327         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18328                 Col2Text(2),   // lightSquareColor
18329                 Col2Text(3) ); // darkSquareColor
18330       }
18331       if(appData.useBorder) {
18332         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18333                 appData.border);
18334       } else {
18335         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18336       }
18337       if(appData.useFont) {
18338         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18339                 appData.renderPiecesWithFont,
18340                 appData.fontToPieceTable,
18341                 Col2Text(9),    // appData.fontBackColorWhite
18342                 Col2Text(10) ); // appData.fontForeColorBlack
18343       } else {
18344         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18345                 appData.pieceDirectory);
18346         if(!appData.pieceDirectory[0])
18347           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18348                 Col2Text(0),   // whitePieceColor
18349                 Col2Text(1) ); // blackPieceColor
18350       }
18351       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18352                 Col2Text(4),   // highlightSquareColor
18353                 Col2Text(5) ); // premoveHighlightColor
18354         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18355         if(insert != q) insert[-1] = NULLCHAR;
18356         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18357         if(q)   free(q);
18358     }
18359     ActivateTheme(FALSE);
18360 }