Allow shuffling indicators in FEN
[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   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
419   return flags;
420 }
421
422 FILE *gameFileFP, *debugFP, *serverFP;
423 char *currentDebugFile; // [HGM] debug split: to remember name
424
425 /*
426     [AS] Note: sometimes, the sscanf() function is used to parse the input
427     into a fixed-size buffer. Because of this, we must be prepared to
428     receive strings as long as the size of the input buffer, which is currently
429     set to 4K for Windows and 8K for the rest.
430     So, we must either allocate sufficiently large buffers here, or
431     reduce the size of the input buffer in the input reading part.
432 */
433
434 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
435 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
436 char thinkOutput1[MSG_SIZ*10];
437
438 ChessProgramState first, second, pairing;
439
440 /* premove variables */
441 int premoveToX = 0;
442 int premoveToY = 0;
443 int premoveFromX = 0;
444 int premoveFromY = 0;
445 int premovePromoChar = 0;
446 int gotPremove = 0;
447 Boolean alarmSounded;
448 /* end premove variables */
449
450 char *ics_prefix = "$";
451 enum ICS_TYPE ics_type = ICS_GENERIC;
452
453 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
454 int pauseExamForwardMostMove = 0;
455 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
456 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
457 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
458 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
459 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
460 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
461 int whiteFlag = FALSE, blackFlag = FALSE;
462 int userOfferedDraw = FALSE;
463 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
464 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
465 int cmailMoveType[CMAIL_MAX_GAMES];
466 long ics_clock_paused = 0;
467 ProcRef icsPR = NoProc, cmailPR = NoProc;
468 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
469 GameMode gameMode = BeginningOfGame;
470 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
471 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
472 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
473 int hiddenThinkOutputState = 0; /* [AS] */
474 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
475 int adjudicateLossPlies = 6;
476 char white_holding[64], black_holding[64];
477 TimeMark lastNodeCountTime;
478 long lastNodeCount=0;
479 int shiftKey, controlKey; // [HGM] set by mouse handler
480
481 int have_sent_ICS_logon = 0;
482 int movesPerSession;
483 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
484 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
485 Boolean adjustedClock;
486 long timeControl_2; /* [AS] Allow separate time controls */
487 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
488 long timeRemaining[2][MAX_MOVES];
489 int matchGame = 0, nextGame = 0, roundNr = 0;
490 Boolean waitingForGame = FALSE, startingEngine = FALSE;
491 TimeMark programStartTime, pauseStart;
492 char ics_handle[MSG_SIZ];
493 int have_set_title = 0;
494
495 /* animateTraining preserves the state of appData.animate
496  * when Training mode is activated. This allows the
497  * response to be animated when appData.animate == TRUE and
498  * appData.animateDragging == TRUE.
499  */
500 Boolean animateTraining;
501
502 GameInfo gameInfo;
503
504 AppData appData;
505
506 Board boards[MAX_MOVES];
507 /* [HGM] Following 7 needed for accurate legality tests: */
508 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
509 signed char  initialRights[BOARD_FILES];
510 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
511 int   initialRulePlies, FENrulePlies;
512 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
513 int loadFlag = 0;
514 Boolean shuffleOpenings;
515 int mute; // mute all sounds
516
517 // [HGM] vari: next 12 to save and restore variations
518 #define MAX_VARIATIONS 10
519 int framePtr = MAX_MOVES-1; // points to free stack entry
520 int storedGames = 0;
521 int savedFirst[MAX_VARIATIONS];
522 int savedLast[MAX_VARIATIONS];
523 int savedFramePtr[MAX_VARIATIONS];
524 char *savedDetails[MAX_VARIATIONS];
525 ChessMove savedResult[MAX_VARIATIONS];
526
527 void PushTail P((int firstMove, int lastMove));
528 Boolean PopTail P((Boolean annotate));
529 void PushInner P((int firstMove, int lastMove));
530 void PopInner P((Boolean annotate));
531 void CleanupTail P((void));
532
533 ChessSquare  FIDEArray[2][BOARD_FILES] = {
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
537         BlackKing, BlackBishop, BlackKnight, BlackRook }
538 };
539
540 ChessSquare twoKingsArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544         BlackKing, BlackKing, BlackKnight, BlackRook }
545 };
546
547 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
549         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
550     { BlackRook, BlackMan, BlackBishop, BlackQueen,
551         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
552 };
553
554 ChessSquare SpartanArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
556         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
558         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
559 };
560
561 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
565         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
566 };
567
568 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
570         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
572         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
573 };
574
575 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
577         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackMan, BlackFerz,
579         BlackKing, BlackMan, BlackKnight, BlackRook }
580 };
581
582 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
584         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackMan, BlackFerz,
586         BlackKing, BlackMan, BlackKnight, BlackRook }
587 };
588
589 ChessSquare  lionArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
591         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
592     { BlackRook, BlackLion, BlackBishop, BlackQueen,
593         BlackKing, BlackBishop, BlackKnight, BlackRook }
594 };
595
596
597 #if (BOARD_FILES>=10)
598 ChessSquare ShogiArray[2][BOARD_FILES] = {
599     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
600         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
601     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
602         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
603 };
604
605 ChessSquare XiangqiArray[2][BOARD_FILES] = {
606     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
607         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
608     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
609         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
610 };
611
612 ChessSquare CapablancaArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
614         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
616         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
617 };
618
619 ChessSquare GreatArray[2][BOARD_FILES] = {
620     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
621         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
622     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
623         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
624 };
625
626 ChessSquare JanusArray[2][BOARD_FILES] = {
627     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
628         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
629     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
630         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
631 };
632
633 ChessSquare GrandArray[2][BOARD_FILES] = {
634     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
635         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
636     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
637         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
638 };
639
640 ChessSquare ChuChessArray[2][BOARD_FILES] = {
641     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
642         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
643     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
644         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
645 };
646
647 #ifdef GOTHIC
648 ChessSquare GothicArray[2][BOARD_FILES] = {
649     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
650         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
651     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
652         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
653 };
654 #else // !GOTHIC
655 #define GothicArray CapablancaArray
656 #endif // !GOTHIC
657
658 #ifdef FALCON
659 ChessSquare FalconArray[2][BOARD_FILES] = {
660     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
661         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
662     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
663         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
664 };
665 #else // !FALCON
666 #define FalconArray CapablancaArray
667 #endif // !FALCON
668
669 #else // !(BOARD_FILES>=10)
670 #define XiangqiPosition FIDEArray
671 #define CapablancaArray FIDEArray
672 #define GothicArray FIDEArray
673 #define GreatArray FIDEArray
674 #endif // !(BOARD_FILES>=10)
675
676 #if (BOARD_FILES>=12)
677 ChessSquare CourierArray[2][BOARD_FILES] = {
678     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
679         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
680     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
681         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
682 };
683 ChessSquare ChuArray[6][BOARD_FILES] = {
684     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
685       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
686     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
687       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
688     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
689       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
690     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
691       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
692     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
693       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
694     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
695       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
696 };
697 #else // !(BOARD_FILES>=12)
698 #define CourierArray CapablancaArray
699 #define ChuArray CapablancaArray
700 #endif // !(BOARD_FILES>=12)
701
702
703 Board initialPosition;
704
705
706 /* Convert str to a rating. Checks for special cases of "----",
707
708    "++++", etc. Also strips ()'s */
709 int
710 string_to_rating (char *str)
711 {
712   while(*str && !isdigit(*str)) ++str;
713   if (!*str)
714     return 0;   /* One of the special "no rating" cases */
715   else
716     return atoi(str);
717 }
718
719 void
720 ClearProgramStats ()
721 {
722     /* Init programStats */
723     programStats.movelist[0] = 0;
724     programStats.depth = 0;
725     programStats.nr_moves = 0;
726     programStats.moves_left = 0;
727     programStats.nodes = 0;
728     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
729     programStats.score = 0;
730     programStats.got_only_move = 0;
731     programStats.got_fail = 0;
732     programStats.line_is_book = 0;
733 }
734
735 void
736 CommonEngineInit ()
737 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
738     if (appData.firstPlaysBlack) {
739         first.twoMachinesColor = "black\n";
740         second.twoMachinesColor = "white\n";
741     } else {
742         first.twoMachinesColor = "white\n";
743         second.twoMachinesColor = "black\n";
744     }
745
746     first.other = &second;
747     second.other = &first;
748
749     { float norm = 1;
750         if(appData.timeOddsMode) {
751             norm = appData.timeOdds[0];
752             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
753         }
754         first.timeOdds  = appData.timeOdds[0]/norm;
755         second.timeOdds = appData.timeOdds[1]/norm;
756     }
757
758     if(programVersion) free(programVersion);
759     if (appData.noChessProgram) {
760         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
761         sprintf(programVersion, "%s", PACKAGE_STRING);
762     } else {
763       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
764       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
765       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
766     }
767 }
768
769 void
770 UnloadEngine (ChessProgramState *cps)
771 {
772         /* Kill off first chess program */
773         if (cps->isr != NULL)
774           RemoveInputSource(cps->isr);
775         cps->isr = NULL;
776
777         if (cps->pr != NoProc) {
778             ExitAnalyzeMode();
779             DoSleep( appData.delayBeforeQuit );
780             SendToProgram("quit\n", cps);
781             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
782         }
783         cps->pr = NoProc;
784         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
785 }
786
787 void
788 ClearOptions (ChessProgramState *cps)
789 {
790     int i;
791     cps->nrOptions = cps->comboCnt = 0;
792     for(i=0; i<MAX_OPTIONS; i++) {
793         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
794         cps->option[i].textValue = 0;
795     }
796 }
797
798 char *engineNames[] = {
799   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
800      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
801 N_("first"),
802   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
803      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
804 N_("second")
805 };
806
807 void
808 InitEngine (ChessProgramState *cps, int n)
809 {   // [HGM] all engine initialiation put in a function that does one engine
810
811     ClearOptions(cps);
812
813     cps->which = engineNames[n];
814     cps->maybeThinking = FALSE;
815     cps->pr = NoProc;
816     cps->isr = NULL;
817     cps->sendTime = 2;
818     cps->sendDrawOffers = 1;
819
820     cps->program = appData.chessProgram[n];
821     cps->host = appData.host[n];
822     cps->dir = appData.directory[n];
823     cps->initString = appData.engInitString[n];
824     cps->computerString = appData.computerString[n];
825     cps->useSigint  = TRUE;
826     cps->useSigterm = TRUE;
827     cps->reuse = appData.reuse[n];
828     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
829     cps->useSetboard = FALSE;
830     cps->useSAN = FALSE;
831     cps->usePing = FALSE;
832     cps->lastPing = 0;
833     cps->lastPong = 0;
834     cps->usePlayother = FALSE;
835     cps->useColors = TRUE;
836     cps->useUsermove = FALSE;
837     cps->sendICS = FALSE;
838     cps->sendName = appData.icsActive;
839     cps->sdKludge = FALSE;
840     cps->stKludge = FALSE;
841     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
842     TidyProgramName(cps->program, cps->host, cps->tidy);
843     cps->matchWins = 0;
844     ASSIGN(cps->variants, appData.variant);
845     cps->analysisSupport = 2; /* detect */
846     cps->analyzing = FALSE;
847     cps->initDone = FALSE;
848     cps->reload = FALSE;
849
850     /* New features added by Tord: */
851     cps->useFEN960 = FALSE;
852     cps->useOOCastle = TRUE;
853     /* End of new features added by Tord. */
854     cps->fenOverride  = appData.fenOverride[n];
855
856     /* [HGM] time odds: set factor for each machine */
857     cps->timeOdds  = appData.timeOdds[n];
858
859     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
860     cps->accumulateTC = appData.accumulateTC[n];
861     cps->maxNrOfSessions = 1;
862
863     /* [HGM] debug */
864     cps->debug = FALSE;
865
866     cps->drawDepth = appData.drawDepth[n];
867     cps->supportsNPS = UNKNOWN;
868     cps->memSize = FALSE;
869     cps->maxCores = FALSE;
870     ASSIGN(cps->egtFormats, "");
871
872     /* [HGM] options */
873     cps->optionSettings  = appData.engOptions[n];
874
875     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
876     cps->isUCI = appData.isUCI[n]; /* [AS] */
877     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
878     cps->highlight = 0;
879
880     if (appData.protocolVersion[n] > PROTOVER
881         || appData.protocolVersion[n] < 1)
882       {
883         char buf[MSG_SIZ];
884         int len;
885
886         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
887                        appData.protocolVersion[n]);
888         if( (len >= MSG_SIZ) && appData.debugMode )
889           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
890
891         DisplayFatalError(buf, 0, 2);
892       }
893     else
894       {
895         cps->protocolVersion = appData.protocolVersion[n];
896       }
897
898     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
899     ParseFeatures(appData.featureDefaults, cps);
900 }
901
902 ChessProgramState *savCps;
903
904 GameMode oldMode;
905
906 void
907 LoadEngine ()
908 {
909     int i;
910     if(WaitForEngine(savCps, LoadEngine)) return;
911     CommonEngineInit(); // recalculate time odds
912     if(gameInfo.variant != StringToVariant(appData.variant)) {
913         // we changed variant when loading the engine; this forces us to reset
914         Reset(TRUE, savCps != &first);
915         oldMode = BeginningOfGame; // to prevent restoring old mode
916     }
917     InitChessProgram(savCps, FALSE);
918     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
919     DisplayMessage("", "");
920     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
921     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
922     ThawUI();
923     SetGNUMode();
924     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
925 }
926
927 void
928 ReplaceEngine (ChessProgramState *cps, int n)
929 {
930     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
931     keepInfo = 1;
932     if(oldMode != BeginningOfGame) EditGameEvent();
933     keepInfo = 0;
934     UnloadEngine(cps);
935     appData.noChessProgram = FALSE;
936     appData.clockMode = TRUE;
937     InitEngine(cps, n);
938     UpdateLogos(TRUE);
939     if(n) return; // only startup first engine immediately; second can wait
940     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
941     LoadEngine();
942 }
943
944 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
945 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
946
947 static char resetOptions[] =
948         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
949         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
950         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
951         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
952
953 void
954 FloatToFront(char **list, char *engineLine)
955 {
956     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
957     int i=0;
958     if(appData.recentEngines <= 0) return;
959     TidyProgramName(engineLine, "localhost", tidy+1);
960     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
961     strncpy(buf+1, *list, MSG_SIZ-50);
962     if(p = strstr(buf, tidy)) { // tidy name appears in list
963         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
964         while(*p++ = *++q); // squeeze out
965     }
966     strcat(tidy, buf+1); // put list behind tidy name
967     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
968     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
969     ASSIGN(*list, tidy+1);
970 }
971
972 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
973
974 void
975 Load (ChessProgramState *cps, int i)
976 {
977     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
978     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
979         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
980         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
981         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
982         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
983         appData.firstProtocolVersion = PROTOVER;
984         ParseArgsFromString(buf);
985         SwapEngines(i);
986         ReplaceEngine(cps, i);
987         FloatToFront(&appData.recentEngineList, engineLine);
988         return;
989     }
990     p = engineName;
991     while(q = strchr(p, SLASH)) p = q+1;
992     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
993     if(engineDir[0] != NULLCHAR) {
994         ASSIGN(appData.directory[i], engineDir); p = engineName;
995     } else if(p != engineName) { // derive directory from engine path, when not given
996         p[-1] = 0;
997         ASSIGN(appData.directory[i], engineName);
998         p[-1] = SLASH;
999         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1000     } else { ASSIGN(appData.directory[i], "."); }
1001     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1002     if(params[0]) {
1003         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1004         snprintf(command, MSG_SIZ, "%s %s", p, params);
1005         p = command;
1006     }
1007     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1008     ASSIGN(appData.chessProgram[i], p);
1009     appData.isUCI[i] = isUCI;
1010     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1011     appData.hasOwnBookUCI[i] = hasBook;
1012     if(!nickName[0]) useNick = FALSE;
1013     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1014     if(addToList) {
1015         int len;
1016         char quote;
1017         q = firstChessProgramNames;
1018         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1019         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1020         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1021                         quote, p, quote, appData.directory[i],
1022                         useNick ? " -fn \"" : "",
1023                         useNick ? nickName : "",
1024                         useNick ? "\"" : "",
1025                         v1 ? " -firstProtocolVersion 1" : "",
1026                         hasBook ? "" : " -fNoOwnBookUCI",
1027                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1028                         storeVariant ? " -variant " : "",
1029                         storeVariant ? VariantName(gameInfo.variant) : "");
1030         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1031         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1032         if(insert != q) insert[-1] = NULLCHAR;
1033         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1034         if(q)   free(q);
1035         FloatToFront(&appData.recentEngineList, buf);
1036     }
1037     ReplaceEngine(cps, i);
1038 }
1039
1040 void
1041 InitTimeControls ()
1042 {
1043     int matched, min, sec;
1044     /*
1045      * Parse timeControl resource
1046      */
1047     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1048                           appData.movesPerSession)) {
1049         char buf[MSG_SIZ];
1050         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1051         DisplayFatalError(buf, 0, 2);
1052     }
1053
1054     /*
1055      * Parse searchTime resource
1056      */
1057     if (*appData.searchTime != NULLCHAR) {
1058         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1059         if (matched == 1) {
1060             searchTime = min * 60;
1061         } else if (matched == 2) {
1062             searchTime = min * 60 + sec;
1063         } else {
1064             char buf[MSG_SIZ];
1065             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1066             DisplayFatalError(buf, 0, 2);
1067         }
1068     }
1069 }
1070
1071 void
1072 InitBackEnd1 ()
1073 {
1074
1075     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1076     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1077
1078     GetTimeMark(&programStartTime);
1079     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1080     appData.seedBase = random() + (random()<<15);
1081     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1082
1083     ClearProgramStats();
1084     programStats.ok_to_send = 1;
1085     programStats.seen_stat = 0;
1086
1087     /*
1088      * Initialize game list
1089      */
1090     ListNew(&gameList);
1091
1092
1093     /*
1094      * Internet chess server status
1095      */
1096     if (appData.icsActive) {
1097         appData.matchMode = FALSE;
1098         appData.matchGames = 0;
1099 #if ZIPPY
1100         appData.noChessProgram = !appData.zippyPlay;
1101 #else
1102         appData.zippyPlay = FALSE;
1103         appData.zippyTalk = FALSE;
1104         appData.noChessProgram = TRUE;
1105 #endif
1106         if (*appData.icsHelper != NULLCHAR) {
1107             appData.useTelnet = TRUE;
1108             appData.telnetProgram = appData.icsHelper;
1109         }
1110     } else {
1111         appData.zippyTalk = appData.zippyPlay = FALSE;
1112     }
1113
1114     /* [AS] Initialize pv info list [HGM] and game state */
1115     {
1116         int i, j;
1117
1118         for( i=0; i<=framePtr; i++ ) {
1119             pvInfoList[i].depth = -1;
1120             boards[i][EP_STATUS] = EP_NONE;
1121             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1122         }
1123     }
1124
1125     InitTimeControls();
1126
1127     /* [AS] Adjudication threshold */
1128     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1129
1130     InitEngine(&first, 0);
1131     InitEngine(&second, 1);
1132     CommonEngineInit();
1133
1134     pairing.which = "pairing"; // pairing engine
1135     pairing.pr = NoProc;
1136     pairing.isr = NULL;
1137     pairing.program = appData.pairingEngine;
1138     pairing.host = "localhost";
1139     pairing.dir = ".";
1140
1141     if (appData.icsActive) {
1142         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1143     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1144         appData.clockMode = FALSE;
1145         first.sendTime = second.sendTime = 0;
1146     }
1147
1148 #if ZIPPY
1149     /* Override some settings from environment variables, for backward
1150        compatibility.  Unfortunately it's not feasible to have the env
1151        vars just set defaults, at least in xboard.  Ugh.
1152     */
1153     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1154       ZippyInit();
1155     }
1156 #endif
1157
1158     if (!appData.icsActive) {
1159       char buf[MSG_SIZ];
1160       int len;
1161
1162       /* Check for variants that are supported only in ICS mode,
1163          or not at all.  Some that are accepted here nevertheless
1164          have bugs; see comments below.
1165       */
1166       VariantClass variant = StringToVariant(appData.variant);
1167       switch (variant) {
1168       case VariantBughouse:     /* need four players and two boards */
1169       case VariantKriegspiel:   /* need to hide pieces and move details */
1170         /* case VariantFischeRandom: (Fabien: moved below) */
1171         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1172         if( (len >= MSG_SIZ) && appData.debugMode )
1173           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1174
1175         DisplayFatalError(buf, 0, 2);
1176         return;
1177
1178       case VariantUnknown:
1179       case VariantLoadable:
1180       case Variant29:
1181       case Variant30:
1182       case Variant31:
1183       case Variant32:
1184       case Variant33:
1185       case Variant34:
1186       case Variant35:
1187       case Variant36:
1188       default:
1189         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1190         if( (len >= MSG_SIZ) && appData.debugMode )
1191           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1192
1193         DisplayFatalError(buf, 0, 2);
1194         return;
1195
1196       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1197       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1198       case VariantGothic:     /* [HGM] should work */
1199       case VariantCapablanca: /* [HGM] should work */
1200       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1201       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1202       case VariantChu:        /* [HGM] experimental */
1203       case VariantKnightmate: /* [HGM] should work */
1204       case VariantCylinder:   /* [HGM] untested */
1205       case VariantFalcon:     /* [HGM] untested */
1206       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1207                                  offboard interposition not understood */
1208       case VariantNormal:     /* definitely works! */
1209       case VariantWildCastle: /* pieces not automatically shuffled */
1210       case VariantNoCastle:   /* pieces not automatically shuffled */
1211       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1212       case VariantLosers:     /* should work except for win condition,
1213                                  and doesn't know captures are mandatory */
1214       case VariantSuicide:    /* should work except for win condition,
1215                                  and doesn't know captures are mandatory */
1216       case VariantGiveaway:   /* should work except for win condition,
1217                                  and doesn't know captures are mandatory */
1218       case VariantTwoKings:   /* should work */
1219       case VariantAtomic:     /* should work except for win condition */
1220       case Variant3Check:     /* should work except for win condition */
1221       case VariantShatranj:   /* should work except for all win conditions */
1222       case VariantMakruk:     /* should work except for draw countdown */
1223       case VariantASEAN :     /* should work except for draw countdown */
1224       case VariantBerolina:   /* might work if TestLegality is off */
1225       case VariantCapaRandom: /* should work */
1226       case VariantJanus:      /* should work */
1227       case VariantSuper:      /* experimental */
1228       case VariantGreat:      /* experimental, requires legality testing to be off */
1229       case VariantSChess:     /* S-Chess, should work */
1230       case VariantGrand:      /* should work */
1231       case VariantSpartan:    /* should work */
1232       case VariantLion:       /* should work */
1233       case VariantChuChess:   /* should work */
1234         break;
1235       }
1236     }
1237
1238 }
1239
1240 int
1241 NextIntegerFromString (char ** str, long * value)
1242 {
1243     int result = -1;
1244     char * s = *str;
1245
1246     while( *s == ' ' || *s == '\t' ) {
1247         s++;
1248     }
1249
1250     *value = 0;
1251
1252     if( *s >= '0' && *s <= '9' ) {
1253         while( *s >= '0' && *s <= '9' ) {
1254             *value = *value * 10 + (*s - '0');
1255             s++;
1256         }
1257
1258         result = 0;
1259     }
1260
1261     *str = s;
1262
1263     return result;
1264 }
1265
1266 int
1267 NextTimeControlFromString (char ** str, long * value)
1268 {
1269     long temp;
1270     int result = NextIntegerFromString( str, &temp );
1271
1272     if( result == 0 ) {
1273         *value = temp * 60; /* Minutes */
1274         if( **str == ':' ) {
1275             (*str)++;
1276             result = NextIntegerFromString( str, &temp );
1277             *value += temp; /* Seconds */
1278         }
1279     }
1280
1281     return result;
1282 }
1283
1284 int
1285 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1286 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1287     int result = -1, type = 0; long temp, temp2;
1288
1289     if(**str != ':') return -1; // old params remain in force!
1290     (*str)++;
1291     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1292     if( NextIntegerFromString( str, &temp ) ) return -1;
1293     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1294
1295     if(**str != '/') {
1296         /* time only: incremental or sudden-death time control */
1297         if(**str == '+') { /* increment follows; read it */
1298             (*str)++;
1299             if(**str == '!') type = *(*str)++; // Bronstein TC
1300             if(result = NextIntegerFromString( str, &temp2)) return -1;
1301             *inc = temp2 * 1000;
1302             if(**str == '.') { // read fraction of increment
1303                 char *start = ++(*str);
1304                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1305                 temp2 *= 1000;
1306                 while(start++ < *str) temp2 /= 10;
1307                 *inc += temp2;
1308             }
1309         } else *inc = 0;
1310         *moves = 0; *tc = temp * 1000; *incType = type;
1311         return 0;
1312     }
1313
1314     (*str)++; /* classical time control */
1315     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1316
1317     if(result == 0) {
1318         *moves = temp;
1319         *tc    = temp2 * 1000;
1320         *inc   = 0;
1321         *incType = type;
1322     }
1323     return result;
1324 }
1325
1326 int
1327 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1328 {   /* [HGM] get time to add from the multi-session time-control string */
1329     int incType, moves=1; /* kludge to force reading of first session */
1330     long time, increment;
1331     char *s = tcString;
1332
1333     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1334     do {
1335         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1336         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1337         if(movenr == -1) return time;    /* last move before new session     */
1338         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1339         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1340         if(!moves) return increment;     /* current session is incremental   */
1341         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1342     } while(movenr >= -1);               /* try again for next session       */
1343
1344     return 0; // no new time quota on this move
1345 }
1346
1347 int
1348 ParseTimeControl (char *tc, float ti, int mps)
1349 {
1350   long tc1;
1351   long tc2;
1352   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1353   int min, sec=0;
1354
1355   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1356   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1357       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1358   if(ti > 0) {
1359
1360     if(mps)
1361       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1362     else
1363       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1364   } else {
1365     if(mps)
1366       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1367     else
1368       snprintf(buf, MSG_SIZ, ":%s", mytc);
1369   }
1370   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1371
1372   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1373     return FALSE;
1374   }
1375
1376   if( *tc == '/' ) {
1377     /* Parse second time control */
1378     tc++;
1379
1380     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1381       return FALSE;
1382     }
1383
1384     if( tc2 == 0 ) {
1385       return FALSE;
1386     }
1387
1388     timeControl_2 = tc2 * 1000;
1389   }
1390   else {
1391     timeControl_2 = 0;
1392   }
1393
1394   if( tc1 == 0 ) {
1395     return FALSE;
1396   }
1397
1398   timeControl = tc1 * 1000;
1399
1400   if (ti >= 0) {
1401     timeIncrement = ti * 1000;  /* convert to ms */
1402     movesPerSession = 0;
1403   } else {
1404     timeIncrement = 0;
1405     movesPerSession = mps;
1406   }
1407   return TRUE;
1408 }
1409
1410 void
1411 InitBackEnd2 ()
1412 {
1413     if (appData.debugMode) {
1414 #    ifdef __GIT_VERSION
1415       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1416 #    else
1417       fprintf(debugFP, "Version: %s\n", programVersion);
1418 #    endif
1419     }
1420     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1421
1422     set_cont_sequence(appData.wrapContSeq);
1423     if (appData.matchGames > 0) {
1424         appData.matchMode = TRUE;
1425     } else if (appData.matchMode) {
1426         appData.matchGames = 1;
1427     }
1428     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1429         appData.matchGames = appData.sameColorGames;
1430     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1431         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1432         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1433     }
1434     Reset(TRUE, FALSE);
1435     if (appData.noChessProgram || first.protocolVersion == 1) {
1436       InitBackEnd3();
1437     } else {
1438       /* kludge: allow timeout for initial "feature" commands */
1439       FreezeUI();
1440       DisplayMessage("", _("Starting chess program"));
1441       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1442     }
1443 }
1444
1445 int
1446 CalculateIndex (int index, int gameNr)
1447 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1448     int res;
1449     if(index > 0) return index; // fixed nmber
1450     if(index == 0) return 1;
1451     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1452     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1453     return res;
1454 }
1455
1456 int
1457 LoadGameOrPosition (int gameNr)
1458 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1459     if (*appData.loadGameFile != NULLCHAR) {
1460         if (!LoadGameFromFile(appData.loadGameFile,
1461                 CalculateIndex(appData.loadGameIndex, gameNr),
1462                               appData.loadGameFile, FALSE)) {
1463             DisplayFatalError(_("Bad game file"), 0, 1);
1464             return 0;
1465         }
1466     } else if (*appData.loadPositionFile != NULLCHAR) {
1467         if (!LoadPositionFromFile(appData.loadPositionFile,
1468                 CalculateIndex(appData.loadPositionIndex, gameNr),
1469                                   appData.loadPositionFile)) {
1470             DisplayFatalError(_("Bad position file"), 0, 1);
1471             return 0;
1472         }
1473     }
1474     return 1;
1475 }
1476
1477 void
1478 ReserveGame (int gameNr, char resChar)
1479 {
1480     FILE *tf = fopen(appData.tourneyFile, "r+");
1481     char *p, *q, c, buf[MSG_SIZ];
1482     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1483     safeStrCpy(buf, lastMsg, MSG_SIZ);
1484     DisplayMessage(_("Pick new game"), "");
1485     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1486     ParseArgsFromFile(tf);
1487     p = q = appData.results;
1488     if(appData.debugMode) {
1489       char *r = appData.participants;
1490       fprintf(debugFP, "results = '%s'\n", p);
1491       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1492       fprintf(debugFP, "\n");
1493     }
1494     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1495     nextGame = q - p;
1496     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1497     safeStrCpy(q, p, strlen(p) + 2);
1498     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1499     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1500     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1501         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1502         q[nextGame] = '*';
1503     }
1504     fseek(tf, -(strlen(p)+4), SEEK_END);
1505     c = fgetc(tf);
1506     if(c != '"') // depending on DOS or Unix line endings we can be one off
1507          fseek(tf, -(strlen(p)+2), SEEK_END);
1508     else fseek(tf, -(strlen(p)+3), SEEK_END);
1509     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1510     DisplayMessage(buf, "");
1511     free(p); appData.results = q;
1512     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1513        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1514       int round = appData.defaultMatchGames * appData.tourneyType;
1515       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1516          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1517         UnloadEngine(&first);  // next game belongs to other pairing;
1518         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1519     }
1520     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1521 }
1522
1523 void
1524 MatchEvent (int mode)
1525 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1526         int dummy;
1527         if(matchMode) { // already in match mode: switch it off
1528             abortMatch = TRUE;
1529             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1530             return;
1531         }
1532 //      if(gameMode != BeginningOfGame) {
1533 //          DisplayError(_("You can only start a match from the initial position."), 0);
1534 //          return;
1535 //      }
1536         abortMatch = FALSE;
1537         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1538         /* Set up machine vs. machine match */
1539         nextGame = 0;
1540         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1541         if(appData.tourneyFile[0]) {
1542             ReserveGame(-1, 0);
1543             if(nextGame > appData.matchGames) {
1544                 char buf[MSG_SIZ];
1545                 if(strchr(appData.results, '*') == NULL) {
1546                     FILE *f;
1547                     appData.tourneyCycles++;
1548                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1549                         fclose(f);
1550                         NextTourneyGame(-1, &dummy);
1551                         ReserveGame(-1, 0);
1552                         if(nextGame <= appData.matchGames) {
1553                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1554                             matchMode = mode;
1555                             ScheduleDelayedEvent(NextMatchGame, 10000);
1556                             return;
1557                         }
1558                     }
1559                 }
1560                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1561                 DisplayError(buf, 0);
1562                 appData.tourneyFile[0] = 0;
1563                 return;
1564             }
1565         } else
1566         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1567             DisplayFatalError(_("Can't have a match with no chess programs"),
1568                               0, 2);
1569             return;
1570         }
1571         matchMode = mode;
1572         matchGame = roundNr = 1;
1573         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1574         NextMatchGame();
1575 }
1576
1577 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1578
1579 void
1580 InitBackEnd3 P((void))
1581 {
1582     GameMode initialMode;
1583     char buf[MSG_SIZ];
1584     int err, len;
1585
1586     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1587        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1588         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1589        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1590        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1591         char c, *q = first.variants, *p = strchr(q, ',');
1592         if(p) *p = NULLCHAR;
1593         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1594             int w, h, s;
1595             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1596                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1597             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1598             Reset(TRUE, FALSE);         // and re-initialize
1599         }
1600         if(p) *p = ',';
1601     }
1602
1603     InitChessProgram(&first, startedFromSetupPosition);
1604
1605     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1606         free(programVersion);
1607         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1608         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1609         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1610     }
1611
1612     if (appData.icsActive) {
1613 #ifdef WIN32
1614         /* [DM] Make a console window if needed [HGM] merged ifs */
1615         ConsoleCreate();
1616 #endif
1617         err = establish();
1618         if (err != 0)
1619           {
1620             if (*appData.icsCommPort != NULLCHAR)
1621               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1622                              appData.icsCommPort);
1623             else
1624               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1625                         appData.icsHost, appData.icsPort);
1626
1627             if( (len >= MSG_SIZ) && appData.debugMode )
1628               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1629
1630             DisplayFatalError(buf, err, 1);
1631             return;
1632         }
1633         SetICSMode();
1634         telnetISR =
1635           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1636         fromUserISR =
1637           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1638         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1639             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1640     } else if (appData.noChessProgram) {
1641         SetNCPMode();
1642     } else {
1643         SetGNUMode();
1644     }
1645
1646     if (*appData.cmailGameName != NULLCHAR) {
1647         SetCmailMode();
1648         OpenLoopback(&cmailPR);
1649         cmailISR =
1650           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1651     }
1652
1653     ThawUI();
1654     DisplayMessage("", "");
1655     if (StrCaseCmp(appData.initialMode, "") == 0) {
1656       initialMode = BeginningOfGame;
1657       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1658         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1659         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1660         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1661         ModeHighlight();
1662       }
1663     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1664       initialMode = TwoMachinesPlay;
1665     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1666       initialMode = AnalyzeFile;
1667     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1668       initialMode = AnalyzeMode;
1669     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1670       initialMode = MachinePlaysWhite;
1671     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1672       initialMode = MachinePlaysBlack;
1673     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1674       initialMode = EditGame;
1675     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1676       initialMode = EditPosition;
1677     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1678       initialMode = Training;
1679     } else {
1680       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1681       if( (len >= MSG_SIZ) && appData.debugMode )
1682         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1683
1684       DisplayFatalError(buf, 0, 2);
1685       return;
1686     }
1687
1688     if (appData.matchMode) {
1689         if(appData.tourneyFile[0]) { // start tourney from command line
1690             FILE *f;
1691             if(f = fopen(appData.tourneyFile, "r")) {
1692                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1693                 fclose(f);
1694                 appData.clockMode = TRUE;
1695                 SetGNUMode();
1696             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1697         }
1698         MatchEvent(TRUE);
1699     } else if (*appData.cmailGameName != NULLCHAR) {
1700         /* Set up cmail mode */
1701         ReloadCmailMsgEvent(TRUE);
1702     } else {
1703         /* Set up other modes */
1704         if (initialMode == AnalyzeFile) {
1705           if (*appData.loadGameFile == NULLCHAR) {
1706             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1707             return;
1708           }
1709         }
1710         if (*appData.loadGameFile != NULLCHAR) {
1711             (void) LoadGameFromFile(appData.loadGameFile,
1712                                     appData.loadGameIndex,
1713                                     appData.loadGameFile, TRUE);
1714         } else if (*appData.loadPositionFile != NULLCHAR) {
1715             (void) LoadPositionFromFile(appData.loadPositionFile,
1716                                         appData.loadPositionIndex,
1717                                         appData.loadPositionFile);
1718             /* [HGM] try to make self-starting even after FEN load */
1719             /* to allow automatic setup of fairy variants with wtm */
1720             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1721                 gameMode = BeginningOfGame;
1722                 setboardSpoiledMachineBlack = 1;
1723             }
1724             /* [HGM] loadPos: make that every new game uses the setup */
1725             /* from file as long as we do not switch variant          */
1726             if(!blackPlaysFirst) {
1727                 startedFromPositionFile = TRUE;
1728                 CopyBoard(filePosition, boards[0]);
1729             }
1730         }
1731         if (initialMode == AnalyzeMode) {
1732           if (appData.noChessProgram) {
1733             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1734             return;
1735           }
1736           if (appData.icsActive) {
1737             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1738             return;
1739           }
1740           AnalyzeModeEvent();
1741         } else if (initialMode == AnalyzeFile) {
1742           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1743           ShowThinkingEvent();
1744           AnalyzeFileEvent();
1745           AnalysisPeriodicEvent(1);
1746         } else if (initialMode == MachinePlaysWhite) {
1747           if (appData.noChessProgram) {
1748             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1749                               0, 2);
1750             return;
1751           }
1752           if (appData.icsActive) {
1753             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1754                               0, 2);
1755             return;
1756           }
1757           MachineWhiteEvent();
1758         } else if (initialMode == MachinePlaysBlack) {
1759           if (appData.noChessProgram) {
1760             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1761                               0, 2);
1762             return;
1763           }
1764           if (appData.icsActive) {
1765             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1766                               0, 2);
1767             return;
1768           }
1769           MachineBlackEvent();
1770         } else if (initialMode == TwoMachinesPlay) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           TwoMachinesEvent();
1782         } else if (initialMode == EditGame) {
1783           EditGameEvent();
1784         } else if (initialMode == EditPosition) {
1785           EditPositionEvent();
1786         } else if (initialMode == Training) {
1787           if (*appData.loadGameFile == NULLCHAR) {
1788             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1789             return;
1790           }
1791           TrainingEvent();
1792         }
1793     }
1794 }
1795
1796 void
1797 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1798 {
1799     DisplayBook(current+1);
1800
1801     MoveHistorySet( movelist, first, last, current, pvInfoList );
1802
1803     EvalGraphSet( first, last, current, pvInfoList );
1804
1805     MakeEngineOutputTitle();
1806 }
1807
1808 /*
1809  * Establish will establish a contact to a remote host.port.
1810  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1811  *  used to talk to the host.
1812  * Returns 0 if okay, error code if not.
1813  */
1814 int
1815 establish ()
1816 {
1817     char buf[MSG_SIZ];
1818
1819     if (*appData.icsCommPort != NULLCHAR) {
1820         /* Talk to the host through a serial comm port */
1821         return OpenCommPort(appData.icsCommPort, &icsPR);
1822
1823     } else if (*appData.gateway != NULLCHAR) {
1824         if (*appData.remoteShell == NULLCHAR) {
1825             /* Use the rcmd protocol to run telnet program on a gateway host */
1826             snprintf(buf, sizeof(buf), "%s %s %s",
1827                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1828             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1829
1830         } else {
1831             /* Use the rsh program to run telnet program on a gateway host */
1832             if (*appData.remoteUser == NULLCHAR) {
1833                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1834                         appData.gateway, appData.telnetProgram,
1835                         appData.icsHost, appData.icsPort);
1836             } else {
1837                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1838                         appData.remoteShell, appData.gateway,
1839                         appData.remoteUser, appData.telnetProgram,
1840                         appData.icsHost, appData.icsPort);
1841             }
1842             return StartChildProcess(buf, "", &icsPR);
1843
1844         }
1845     } else if (appData.useTelnet) {
1846         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1847
1848     } else {
1849         /* TCP socket interface differs somewhat between
1850            Unix and NT; handle details in the front end.
1851            */
1852         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1853     }
1854 }
1855
1856 void
1857 EscapeExpand (char *p, char *q)
1858 {       // [HGM] initstring: routine to shape up string arguments
1859         while(*p++ = *q++) if(p[-1] == '\\')
1860             switch(*q++) {
1861                 case 'n': p[-1] = '\n'; break;
1862                 case 'r': p[-1] = '\r'; break;
1863                 case 't': p[-1] = '\t'; break;
1864                 case '\\': p[-1] = '\\'; break;
1865                 case 0: *p = 0; return;
1866                 default: p[-1] = q[-1]; break;
1867             }
1868 }
1869
1870 void
1871 show_bytes (FILE *fp, char *buf, int count)
1872 {
1873     while (count--) {
1874         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1875             fprintf(fp, "\\%03o", *buf & 0xff);
1876         } else {
1877             putc(*buf, fp);
1878         }
1879         buf++;
1880     }
1881     fflush(fp);
1882 }
1883
1884 /* Returns an errno value */
1885 int
1886 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1887 {
1888     char buf[8192], *p, *q, *buflim;
1889     int left, newcount, outcount;
1890
1891     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1892         *appData.gateway != NULLCHAR) {
1893         if (appData.debugMode) {
1894             fprintf(debugFP, ">ICS: ");
1895             show_bytes(debugFP, message, count);
1896             fprintf(debugFP, "\n");
1897         }
1898         return OutputToProcess(pr, message, count, outError);
1899     }
1900
1901     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1902     p = message;
1903     q = buf;
1904     left = count;
1905     newcount = 0;
1906     while (left) {
1907         if (q >= buflim) {
1908             if (appData.debugMode) {
1909                 fprintf(debugFP, ">ICS: ");
1910                 show_bytes(debugFP, buf, newcount);
1911                 fprintf(debugFP, "\n");
1912             }
1913             outcount = OutputToProcess(pr, buf, newcount, outError);
1914             if (outcount < newcount) return -1; /* to be sure */
1915             q = buf;
1916             newcount = 0;
1917         }
1918         if (*p == '\n') {
1919             *q++ = '\r';
1920             newcount++;
1921         } else if (((unsigned char) *p) == TN_IAC) {
1922             *q++ = (char) TN_IAC;
1923             newcount ++;
1924         }
1925         *q++ = *p++;
1926         newcount++;
1927         left--;
1928     }
1929     if (appData.debugMode) {
1930         fprintf(debugFP, ">ICS: ");
1931         show_bytes(debugFP, buf, newcount);
1932         fprintf(debugFP, "\n");
1933     }
1934     outcount = OutputToProcess(pr, buf, newcount, outError);
1935     if (outcount < newcount) return -1; /* to be sure */
1936     return count;
1937 }
1938
1939 void
1940 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1941 {
1942     int outError, outCount;
1943     static int gotEof = 0;
1944     static FILE *ini;
1945
1946     /* Pass data read from player on to ICS */
1947     if (count > 0) {
1948         gotEof = 0;
1949         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1950         if (outCount < count) {
1951             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1952         }
1953         if(have_sent_ICS_logon == 2) {
1954           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1955             fprintf(ini, "%s", message);
1956             have_sent_ICS_logon = 3;
1957           } else
1958             have_sent_ICS_logon = 1;
1959         } else if(have_sent_ICS_logon == 3) {
1960             fprintf(ini, "%s", message);
1961             fclose(ini);
1962           have_sent_ICS_logon = 1;
1963         }
1964     } else if (count < 0) {
1965         RemoveInputSource(isr);
1966         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1967     } else if (gotEof++ > 0) {
1968         RemoveInputSource(isr);
1969         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1970     }
1971 }
1972
1973 void
1974 KeepAlive ()
1975 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1976     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1977     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1978     SendToICS("date\n");
1979     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1980 }
1981
1982 /* added routine for printf style output to ics */
1983 void
1984 ics_printf (char *format, ...)
1985 {
1986     char buffer[MSG_SIZ];
1987     va_list args;
1988
1989     va_start(args, format);
1990     vsnprintf(buffer, sizeof(buffer), format, args);
1991     buffer[sizeof(buffer)-1] = '\0';
1992     SendToICS(buffer);
1993     va_end(args);
1994 }
1995
1996 void
1997 SendToICS (char *s)
1998 {
1999     int count, outCount, outError;
2000
2001     if (icsPR == NoProc) return;
2002
2003     count = strlen(s);
2004     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2005     if (outCount < count) {
2006         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2007     }
2008 }
2009
2010 /* This is used for sending logon scripts to the ICS. Sending
2011    without a delay causes problems when using timestamp on ICC
2012    (at least on my machine). */
2013 void
2014 SendToICSDelayed (char *s, long msdelay)
2015 {
2016     int count, outCount, outError;
2017
2018     if (icsPR == NoProc) return;
2019
2020     count = strlen(s);
2021     if (appData.debugMode) {
2022         fprintf(debugFP, ">ICS: ");
2023         show_bytes(debugFP, s, count);
2024         fprintf(debugFP, "\n");
2025     }
2026     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2027                                       msdelay);
2028     if (outCount < count) {
2029         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2030     }
2031 }
2032
2033
2034 /* Remove all highlighting escape sequences in s
2035    Also deletes any suffix starting with '('
2036    */
2037 char *
2038 StripHighlightAndTitle (char *s)
2039 {
2040     static char retbuf[MSG_SIZ];
2041     char *p = retbuf;
2042
2043     while (*s != NULLCHAR) {
2044         while (*s == '\033') {
2045             while (*s != NULLCHAR && !isalpha(*s)) s++;
2046             if (*s != NULLCHAR) s++;
2047         }
2048         while (*s != NULLCHAR && *s != '\033') {
2049             if (*s == '(' || *s == '[') {
2050                 *p = NULLCHAR;
2051                 return retbuf;
2052             }
2053             *p++ = *s++;
2054         }
2055     }
2056     *p = NULLCHAR;
2057     return retbuf;
2058 }
2059
2060 /* Remove all highlighting escape sequences in s */
2061 char *
2062 StripHighlight (char *s)
2063 {
2064     static char retbuf[MSG_SIZ];
2065     char *p = retbuf;
2066
2067     while (*s != NULLCHAR) {
2068         while (*s == '\033') {
2069             while (*s != NULLCHAR && !isalpha(*s)) s++;
2070             if (*s != NULLCHAR) s++;
2071         }
2072         while (*s != NULLCHAR && *s != '\033') {
2073             *p++ = *s++;
2074         }
2075     }
2076     *p = NULLCHAR;
2077     return retbuf;
2078 }
2079
2080 char engineVariant[MSG_SIZ];
2081 char *variantNames[] = VARIANT_NAMES;
2082 char *
2083 VariantName (VariantClass v)
2084 {
2085     if(v == VariantUnknown || *engineVariant) return engineVariant;
2086     return variantNames[v];
2087 }
2088
2089
2090 /* Identify a variant from the strings the chess servers use or the
2091    PGN Variant tag names we use. */
2092 VariantClass
2093 StringToVariant (char *e)
2094 {
2095     char *p;
2096     int wnum = -1;
2097     VariantClass v = VariantNormal;
2098     int i, found = FALSE;
2099     char buf[MSG_SIZ];
2100     int len;
2101
2102     if (!e) return v;
2103
2104     /* [HGM] skip over optional board-size prefixes */
2105     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2106         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2107         while( *e++ != '_');
2108     }
2109
2110     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2111         v = VariantNormal;
2112         found = TRUE;
2113     } else
2114     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2115       if (p = StrCaseStr(e, variantNames[i])) {
2116         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2117         v = (VariantClass) i;
2118         found = TRUE;
2119         break;
2120       }
2121     }
2122
2123     if (!found) {
2124       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2125           || StrCaseStr(e, "wild/fr")
2126           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2127         v = VariantFischeRandom;
2128       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2129                  (i = 1, p = StrCaseStr(e, "w"))) {
2130         p += i;
2131         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2132         if (isdigit(*p)) {
2133           wnum = atoi(p);
2134         } else {
2135           wnum = -1;
2136         }
2137         switch (wnum) {
2138         case 0: /* FICS only, actually */
2139         case 1:
2140           /* Castling legal even if K starts on d-file */
2141           v = VariantWildCastle;
2142           break;
2143         case 2:
2144         case 3:
2145         case 4:
2146           /* Castling illegal even if K & R happen to start in
2147              normal positions. */
2148           v = VariantNoCastle;
2149           break;
2150         case 5:
2151         case 7:
2152         case 8:
2153         case 10:
2154         case 11:
2155         case 12:
2156         case 13:
2157         case 14:
2158         case 15:
2159         case 18:
2160         case 19:
2161           /* Castling legal iff K & R start in normal positions */
2162           v = VariantNormal;
2163           break;
2164         case 6:
2165         case 20:
2166         case 21:
2167           /* Special wilds for position setup; unclear what to do here */
2168           v = VariantLoadable;
2169           break;
2170         case 9:
2171           /* Bizarre ICC game */
2172           v = VariantTwoKings;
2173           break;
2174         case 16:
2175           v = VariantKriegspiel;
2176           break;
2177         case 17:
2178           v = VariantLosers;
2179           break;
2180         case 22:
2181           v = VariantFischeRandom;
2182           break;
2183         case 23:
2184           v = VariantCrazyhouse;
2185           break;
2186         case 24:
2187           v = VariantBughouse;
2188           break;
2189         case 25:
2190           v = Variant3Check;
2191           break;
2192         case 26:
2193           /* Not quite the same as FICS suicide! */
2194           v = VariantGiveaway;
2195           break;
2196         case 27:
2197           v = VariantAtomic;
2198           break;
2199         case 28:
2200           v = VariantShatranj;
2201           break;
2202
2203         /* Temporary names for future ICC types.  The name *will* change in
2204            the next xboard/WinBoard release after ICC defines it. */
2205         case 29:
2206           v = Variant29;
2207           break;
2208         case 30:
2209           v = Variant30;
2210           break;
2211         case 31:
2212           v = Variant31;
2213           break;
2214         case 32:
2215           v = Variant32;
2216           break;
2217         case 33:
2218           v = Variant33;
2219           break;
2220         case 34:
2221           v = Variant34;
2222           break;
2223         case 35:
2224           v = Variant35;
2225           break;
2226         case 36:
2227           v = Variant36;
2228           break;
2229         case 37:
2230           v = VariantShogi;
2231           break;
2232         case 38:
2233           v = VariantXiangqi;
2234           break;
2235         case 39:
2236           v = VariantCourier;
2237           break;
2238         case 40:
2239           v = VariantGothic;
2240           break;
2241         case 41:
2242           v = VariantCapablanca;
2243           break;
2244         case 42:
2245           v = VariantKnightmate;
2246           break;
2247         case 43:
2248           v = VariantFairy;
2249           break;
2250         case 44:
2251           v = VariantCylinder;
2252           break;
2253         case 45:
2254           v = VariantFalcon;
2255           break;
2256         case 46:
2257           v = VariantCapaRandom;
2258           break;
2259         case 47:
2260           v = VariantBerolina;
2261           break;
2262         case 48:
2263           v = VariantJanus;
2264           break;
2265         case 49:
2266           v = VariantSuper;
2267           break;
2268         case 50:
2269           v = VariantGreat;
2270           break;
2271         case -1:
2272           /* Found "wild" or "w" in the string but no number;
2273              must assume it's normal chess. */
2274           v = VariantNormal;
2275           break;
2276         default:
2277           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2278           if( (len >= MSG_SIZ) && appData.debugMode )
2279             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2280
2281           DisplayError(buf, 0);
2282           v = VariantUnknown;
2283           break;
2284         }
2285       }
2286     }
2287     if (appData.debugMode) {
2288       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2289               e, wnum, VariantName(v));
2290     }
2291     return v;
2292 }
2293
2294 static int leftover_start = 0, leftover_len = 0;
2295 char star_match[STAR_MATCH_N][MSG_SIZ];
2296
2297 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2298    advance *index beyond it, and set leftover_start to the new value of
2299    *index; else return FALSE.  If pattern contains the character '*', it
2300    matches any sequence of characters not containing '\r', '\n', or the
2301    character following the '*' (if any), and the matched sequence(s) are
2302    copied into star_match.
2303    */
2304 int
2305 looking_at ( char *buf, int *index, char *pattern)
2306 {
2307     char *bufp = &buf[*index], *patternp = pattern;
2308     int star_count = 0;
2309     char *matchp = star_match[0];
2310
2311     for (;;) {
2312         if (*patternp == NULLCHAR) {
2313             *index = leftover_start = bufp - buf;
2314             *matchp = NULLCHAR;
2315             return TRUE;
2316         }
2317         if (*bufp == NULLCHAR) return FALSE;
2318         if (*patternp == '*') {
2319             if (*bufp == *(patternp + 1)) {
2320                 *matchp = NULLCHAR;
2321                 matchp = star_match[++star_count];
2322                 patternp += 2;
2323                 bufp++;
2324                 continue;
2325             } else if (*bufp == '\n' || *bufp == '\r') {
2326                 patternp++;
2327                 if (*patternp == NULLCHAR)
2328                   continue;
2329                 else
2330                   return FALSE;
2331             } else {
2332                 *matchp++ = *bufp++;
2333                 continue;
2334             }
2335         }
2336         if (*patternp != *bufp) return FALSE;
2337         patternp++;
2338         bufp++;
2339     }
2340 }
2341
2342 void
2343 SendToPlayer (char *data, int length)
2344 {
2345     int error, outCount;
2346     outCount = OutputToProcess(NoProc, data, length, &error);
2347     if (outCount < length) {
2348         DisplayFatalError(_("Error writing to display"), error, 1);
2349     }
2350 }
2351
2352 void
2353 PackHolding (char packed[], char *holding)
2354 {
2355     char *p = holding;
2356     char *q = packed;
2357     int runlength = 0;
2358     int curr = 9999;
2359     do {
2360         if (*p == curr) {
2361             runlength++;
2362         } else {
2363             switch (runlength) {
2364               case 0:
2365                 break;
2366               case 1:
2367                 *q++ = curr;
2368                 break;
2369               case 2:
2370                 *q++ = curr;
2371                 *q++ = curr;
2372                 break;
2373               default:
2374                 sprintf(q, "%d", runlength);
2375                 while (*q) q++;
2376                 *q++ = curr;
2377                 break;
2378             }
2379             runlength = 1;
2380             curr = *p;
2381         }
2382     } while (*p++);
2383     *q = NULLCHAR;
2384 }
2385
2386 /* Telnet protocol requests from the front end */
2387 void
2388 TelnetRequest (unsigned char ddww, unsigned char option)
2389 {
2390     unsigned char msg[3];
2391     int outCount, outError;
2392
2393     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2394
2395     if (appData.debugMode) {
2396         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2397         switch (ddww) {
2398           case TN_DO:
2399             ddwwStr = "DO";
2400             break;
2401           case TN_DONT:
2402             ddwwStr = "DONT";
2403             break;
2404           case TN_WILL:
2405             ddwwStr = "WILL";
2406             break;
2407           case TN_WONT:
2408             ddwwStr = "WONT";
2409             break;
2410           default:
2411             ddwwStr = buf1;
2412             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2413             break;
2414         }
2415         switch (option) {
2416           case TN_ECHO:
2417             optionStr = "ECHO";
2418             break;
2419           default:
2420             optionStr = buf2;
2421             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2422             break;
2423         }
2424         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2425     }
2426     msg[0] = TN_IAC;
2427     msg[1] = ddww;
2428     msg[2] = option;
2429     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2430     if (outCount < 3) {
2431         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2432     }
2433 }
2434
2435 void
2436 DoEcho ()
2437 {
2438     if (!appData.icsActive) return;
2439     TelnetRequest(TN_DO, TN_ECHO);
2440 }
2441
2442 void
2443 DontEcho ()
2444 {
2445     if (!appData.icsActive) return;
2446     TelnetRequest(TN_DONT, TN_ECHO);
2447 }
2448
2449 void
2450 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2451 {
2452     /* put the holdings sent to us by the server on the board holdings area */
2453     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2454     char p;
2455     ChessSquare piece;
2456
2457     if(gameInfo.holdingsWidth < 2)  return;
2458     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2459         return; // prevent overwriting by pre-board holdings
2460
2461     if( (int)lowestPiece >= BlackPawn ) {
2462         holdingsColumn = 0;
2463         countsColumn = 1;
2464         holdingsStartRow = BOARD_HEIGHT-1;
2465         direction = -1;
2466     } else {
2467         holdingsColumn = BOARD_WIDTH-1;
2468         countsColumn = BOARD_WIDTH-2;
2469         holdingsStartRow = 0;
2470         direction = 1;
2471     }
2472
2473     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2474         board[i][holdingsColumn] = EmptySquare;
2475         board[i][countsColumn]   = (ChessSquare) 0;
2476     }
2477     while( (p=*holdings++) != NULLCHAR ) {
2478         piece = CharToPiece( ToUpper(p) );
2479         if(piece == EmptySquare) continue;
2480         /*j = (int) piece - (int) WhitePawn;*/
2481         j = PieceToNumber(piece);
2482         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2483         if(j < 0) continue;               /* should not happen */
2484         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2485         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2486         board[holdingsStartRow+j*direction][countsColumn]++;
2487     }
2488 }
2489
2490
2491 void
2492 VariantSwitch (Board board, VariantClass newVariant)
2493 {
2494    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2495    static Board oldBoard;
2496
2497    startedFromPositionFile = FALSE;
2498    if(gameInfo.variant == newVariant) return;
2499
2500    /* [HGM] This routine is called each time an assignment is made to
2501     * gameInfo.variant during a game, to make sure the board sizes
2502     * are set to match the new variant. If that means adding or deleting
2503     * holdings, we shift the playing board accordingly
2504     * This kludge is needed because in ICS observe mode, we get boards
2505     * of an ongoing game without knowing the variant, and learn about the
2506     * latter only later. This can be because of the move list we requested,
2507     * in which case the game history is refilled from the beginning anyway,
2508     * but also when receiving holdings of a crazyhouse game. In the latter
2509     * case we want to add those holdings to the already received position.
2510     */
2511
2512
2513    if (appData.debugMode) {
2514      fprintf(debugFP, "Switch board from %s to %s\n",
2515              VariantName(gameInfo.variant), VariantName(newVariant));
2516      setbuf(debugFP, NULL);
2517    }
2518    shuffleOpenings = 0;       /* [HGM] shuffle */
2519    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2520    switch(newVariant)
2521      {
2522      case VariantShogi:
2523        newWidth = 9;  newHeight = 9;
2524        gameInfo.holdingsSize = 7;
2525      case VariantBughouse:
2526      case VariantCrazyhouse:
2527        newHoldingsWidth = 2; break;
2528      case VariantGreat:
2529        newWidth = 10;
2530      case VariantSuper:
2531        newHoldingsWidth = 2;
2532        gameInfo.holdingsSize = 8;
2533        break;
2534      case VariantGothic:
2535      case VariantCapablanca:
2536      case VariantCapaRandom:
2537        newWidth = 10;
2538      default:
2539        newHoldingsWidth = gameInfo.holdingsSize = 0;
2540      };
2541
2542    if(newWidth  != gameInfo.boardWidth  ||
2543       newHeight != gameInfo.boardHeight ||
2544       newHoldingsWidth != gameInfo.holdingsWidth ) {
2545
2546      /* shift position to new playing area, if needed */
2547      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2548        for(i=0; i<BOARD_HEIGHT; i++)
2549          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2550            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2551              board[i][j];
2552        for(i=0; i<newHeight; i++) {
2553          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2554          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2555        }
2556      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2557        for(i=0; i<BOARD_HEIGHT; i++)
2558          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2559            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2560              board[i][j];
2561      }
2562      board[HOLDINGS_SET] = 0;
2563      gameInfo.boardWidth  = newWidth;
2564      gameInfo.boardHeight = newHeight;
2565      gameInfo.holdingsWidth = newHoldingsWidth;
2566      gameInfo.variant = newVariant;
2567      InitDrawingSizes(-2, 0);
2568    } else gameInfo.variant = newVariant;
2569    CopyBoard(oldBoard, board);   // remember correctly formatted board
2570      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2571    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2572 }
2573
2574 static int loggedOn = FALSE;
2575
2576 /*-- Game start info cache: --*/
2577 int gs_gamenum;
2578 char gs_kind[MSG_SIZ];
2579 static char player1Name[128] = "";
2580 static char player2Name[128] = "";
2581 static char cont_seq[] = "\n\\   ";
2582 static int player1Rating = -1;
2583 static int player2Rating = -1;
2584 /*----------------------------*/
2585
2586 ColorClass curColor = ColorNormal;
2587 int suppressKibitz = 0;
2588
2589 // [HGM] seekgraph
2590 Boolean soughtPending = FALSE;
2591 Boolean seekGraphUp;
2592 #define MAX_SEEK_ADS 200
2593 #define SQUARE 0x80
2594 char *seekAdList[MAX_SEEK_ADS];
2595 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2596 float tcList[MAX_SEEK_ADS];
2597 char colorList[MAX_SEEK_ADS];
2598 int nrOfSeekAds = 0;
2599 int minRating = 1010, maxRating = 2800;
2600 int hMargin = 10, vMargin = 20, h, w;
2601 extern int squareSize, lineGap;
2602
2603 void
2604 PlotSeekAd (int i)
2605 {
2606         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2607         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2608         if(r < minRating+100 && r >=0 ) r = minRating+100;
2609         if(r > maxRating) r = maxRating;
2610         if(tc < 1.f) tc = 1.f;
2611         if(tc > 95.f) tc = 95.f;
2612         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2613         y = ((double)r - minRating)/(maxRating - minRating)
2614             * (h-vMargin-squareSize/8-1) + vMargin;
2615         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2616         if(strstr(seekAdList[i], " u ")) color = 1;
2617         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2618            !strstr(seekAdList[i], "bullet") &&
2619            !strstr(seekAdList[i], "blitz") &&
2620            !strstr(seekAdList[i], "standard") ) color = 2;
2621         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2622         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2623 }
2624
2625 void
2626 PlotSingleSeekAd (int i)
2627 {
2628         PlotSeekAd(i);
2629 }
2630
2631 void
2632 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2633 {
2634         char buf[MSG_SIZ], *ext = "";
2635         VariantClass v = StringToVariant(type);
2636         if(strstr(type, "wild")) {
2637             ext = type + 4; // append wild number
2638             if(v == VariantFischeRandom) type = "chess960"; else
2639             if(v == VariantLoadable) type = "setup"; else
2640             type = VariantName(v);
2641         }
2642         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2643         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2644             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2645             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2646             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2647             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2648             seekNrList[nrOfSeekAds] = nr;
2649             zList[nrOfSeekAds] = 0;
2650             seekAdList[nrOfSeekAds++] = StrSave(buf);
2651             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2652         }
2653 }
2654
2655 void
2656 EraseSeekDot (int i)
2657 {
2658     int x = xList[i], y = yList[i], d=squareSize/4, k;
2659     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2660     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2661     // now replot every dot that overlapped
2662     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2663         int xx = xList[k], yy = yList[k];
2664         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2665             DrawSeekDot(xx, yy, colorList[k]);
2666     }
2667 }
2668
2669 void
2670 RemoveSeekAd (int nr)
2671 {
2672         int i;
2673         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2674             EraseSeekDot(i);
2675             if(seekAdList[i]) free(seekAdList[i]);
2676             seekAdList[i] = seekAdList[--nrOfSeekAds];
2677             seekNrList[i] = seekNrList[nrOfSeekAds];
2678             ratingList[i] = ratingList[nrOfSeekAds];
2679             colorList[i]  = colorList[nrOfSeekAds];
2680             tcList[i] = tcList[nrOfSeekAds];
2681             xList[i]  = xList[nrOfSeekAds];
2682             yList[i]  = yList[nrOfSeekAds];
2683             zList[i]  = zList[nrOfSeekAds];
2684             seekAdList[nrOfSeekAds] = NULL;
2685             break;
2686         }
2687 }
2688
2689 Boolean
2690 MatchSoughtLine (char *line)
2691 {
2692     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2693     int nr, base, inc, u=0; char dummy;
2694
2695     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2696        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2697        (u=1) &&
2698        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2699         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2700         // match: compact and save the line
2701         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2702         return TRUE;
2703     }
2704     return FALSE;
2705 }
2706
2707 int
2708 DrawSeekGraph ()
2709 {
2710     int i;
2711     if(!seekGraphUp) return FALSE;
2712     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2713     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2714
2715     DrawSeekBackground(0, 0, w, h);
2716     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2717     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2718     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2719         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2720         yy = h-1-yy;
2721         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2722         if(i%500 == 0) {
2723             char buf[MSG_SIZ];
2724             snprintf(buf, MSG_SIZ, "%d", i);
2725             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2726         }
2727     }
2728     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2729     for(i=1; i<100; i+=(i<10?1:5)) {
2730         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2731         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2732         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2733             char buf[MSG_SIZ];
2734             snprintf(buf, MSG_SIZ, "%d", i);
2735             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2736         }
2737     }
2738     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2739     return TRUE;
2740 }
2741
2742 int
2743 SeekGraphClick (ClickType click, int x, int y, int moving)
2744 {
2745     static int lastDown = 0, displayed = 0, lastSecond;
2746     if(y < 0) return FALSE;
2747     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2748         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2749         if(!seekGraphUp) return FALSE;
2750         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2751         DrawPosition(TRUE, NULL);
2752         return TRUE;
2753     }
2754     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2755         if(click == Release || moving) return FALSE;
2756         nrOfSeekAds = 0;
2757         soughtPending = TRUE;
2758         SendToICS(ics_prefix);
2759         SendToICS("sought\n"); // should this be "sought all"?
2760     } else { // issue challenge based on clicked ad
2761         int dist = 10000; int i, closest = 0, second = 0;
2762         for(i=0; i<nrOfSeekAds; i++) {
2763             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2764             if(d < dist) { dist = d; closest = i; }
2765             second += (d - zList[i] < 120); // count in-range ads
2766             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2767         }
2768         if(dist < 120) {
2769             char buf[MSG_SIZ];
2770             second = (second > 1);
2771             if(displayed != closest || second != lastSecond) {
2772                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2773                 lastSecond = second; displayed = closest;
2774             }
2775             if(click == Press) {
2776                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2777                 lastDown = closest;
2778                 return TRUE;
2779             } // on press 'hit', only show info
2780             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2781             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2782             SendToICS(ics_prefix);
2783             SendToICS(buf);
2784             return TRUE; // let incoming board of started game pop down the graph
2785         } else if(click == Release) { // release 'miss' is ignored
2786             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2787             if(moving == 2) { // right up-click
2788                 nrOfSeekAds = 0; // refresh graph
2789                 soughtPending = TRUE;
2790                 SendToICS(ics_prefix);
2791                 SendToICS("sought\n"); // should this be "sought all"?
2792             }
2793             return TRUE;
2794         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2795         // press miss or release hit 'pop down' seek graph
2796         seekGraphUp = FALSE;
2797         DrawPosition(TRUE, NULL);
2798     }
2799     return TRUE;
2800 }
2801
2802 void
2803 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2804 {
2805 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2806 #define STARTED_NONE 0
2807 #define STARTED_MOVES 1
2808 #define STARTED_BOARD 2
2809 #define STARTED_OBSERVE 3
2810 #define STARTED_HOLDINGS 4
2811 #define STARTED_CHATTER 5
2812 #define STARTED_COMMENT 6
2813 #define STARTED_MOVES_NOHIDE 7
2814
2815     static int started = STARTED_NONE;
2816     static char parse[20000];
2817     static int parse_pos = 0;
2818     static char buf[BUF_SIZE + 1];
2819     static int firstTime = TRUE, intfSet = FALSE;
2820     static ColorClass prevColor = ColorNormal;
2821     static int savingComment = FALSE;
2822     static int cmatch = 0; // continuation sequence match
2823     char *bp;
2824     char str[MSG_SIZ];
2825     int i, oldi;
2826     int buf_len;
2827     int next_out;
2828     int tkind;
2829     int backup;    /* [DM] For zippy color lines */
2830     char *p;
2831     char talker[MSG_SIZ]; // [HGM] chat
2832     int channel;
2833
2834     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2835
2836     if (appData.debugMode) {
2837       if (!error) {
2838         fprintf(debugFP, "<ICS: ");
2839         show_bytes(debugFP, data, count);
2840         fprintf(debugFP, "\n");
2841       }
2842     }
2843
2844     if (appData.debugMode) { int f = forwardMostMove;
2845         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2846                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2847                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2848     }
2849     if (count > 0) {
2850         /* If last read ended with a partial line that we couldn't parse,
2851            prepend it to the new read and try again. */
2852         if (leftover_len > 0) {
2853             for (i=0; i<leftover_len; i++)
2854               buf[i] = buf[leftover_start + i];
2855         }
2856
2857     /* copy new characters into the buffer */
2858     bp = buf + leftover_len;
2859     buf_len=leftover_len;
2860     for (i=0; i<count; i++)
2861     {
2862         // ignore these
2863         if (data[i] == '\r')
2864             continue;
2865
2866         // join lines split by ICS?
2867         if (!appData.noJoin)
2868         {
2869             /*
2870                 Joining just consists of finding matches against the
2871                 continuation sequence, and discarding that sequence
2872                 if found instead of copying it.  So, until a match
2873                 fails, there's nothing to do since it might be the
2874                 complete sequence, and thus, something we don't want
2875                 copied.
2876             */
2877             if (data[i] == cont_seq[cmatch])
2878             {
2879                 cmatch++;
2880                 if (cmatch == strlen(cont_seq))
2881                 {
2882                     cmatch = 0; // complete match.  just reset the counter
2883
2884                     /*
2885                         it's possible for the ICS to not include the space
2886                         at the end of the last word, making our [correct]
2887                         join operation fuse two separate words.  the server
2888                         does this when the space occurs at the width setting.
2889                     */
2890                     if (!buf_len || buf[buf_len-1] != ' ')
2891                     {
2892                         *bp++ = ' ';
2893                         buf_len++;
2894                     }
2895                 }
2896                 continue;
2897             }
2898             else if (cmatch)
2899             {
2900                 /*
2901                     match failed, so we have to copy what matched before
2902                     falling through and copying this character.  In reality,
2903                     this will only ever be just the newline character, but
2904                     it doesn't hurt to be precise.
2905                 */
2906                 strncpy(bp, cont_seq, cmatch);
2907                 bp += cmatch;
2908                 buf_len += cmatch;
2909                 cmatch = 0;
2910             }
2911         }
2912
2913         // copy this char
2914         *bp++ = data[i];
2915         buf_len++;
2916     }
2917
2918         buf[buf_len] = NULLCHAR;
2919 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2920         next_out = 0;
2921         leftover_start = 0;
2922
2923         i = 0;
2924         while (i < buf_len) {
2925             /* Deal with part of the TELNET option negotiation
2926                protocol.  We refuse to do anything beyond the
2927                defaults, except that we allow the WILL ECHO option,
2928                which ICS uses to turn off password echoing when we are
2929                directly connected to it.  We reject this option
2930                if localLineEditing mode is on (always on in xboard)
2931                and we are talking to port 23, which might be a real
2932                telnet server that will try to keep WILL ECHO on permanently.
2933              */
2934             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2935                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2936                 unsigned char option;
2937                 oldi = i;
2938                 switch ((unsigned char) buf[++i]) {
2939                   case TN_WILL:
2940                     if (appData.debugMode)
2941                       fprintf(debugFP, "\n<WILL ");
2942                     switch (option = (unsigned char) buf[++i]) {
2943                       case TN_ECHO:
2944                         if (appData.debugMode)
2945                           fprintf(debugFP, "ECHO ");
2946                         /* Reply only if this is a change, according
2947                            to the protocol rules. */
2948                         if (remoteEchoOption) break;
2949                         if (appData.localLineEditing &&
2950                             atoi(appData.icsPort) == TN_PORT) {
2951                             TelnetRequest(TN_DONT, TN_ECHO);
2952                         } else {
2953                             EchoOff();
2954                             TelnetRequest(TN_DO, TN_ECHO);
2955                             remoteEchoOption = TRUE;
2956                         }
2957                         break;
2958                       default:
2959                         if (appData.debugMode)
2960                           fprintf(debugFP, "%d ", option);
2961                         /* Whatever this is, we don't want it. */
2962                         TelnetRequest(TN_DONT, option);
2963                         break;
2964                     }
2965                     break;
2966                   case TN_WONT:
2967                     if (appData.debugMode)
2968                       fprintf(debugFP, "\n<WONT ");
2969                     switch (option = (unsigned char) buf[++i]) {
2970                       case TN_ECHO:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "ECHO ");
2973                         /* Reply only if this is a change, according
2974                            to the protocol rules. */
2975                         if (!remoteEchoOption) break;
2976                         EchoOn();
2977                         TelnetRequest(TN_DONT, TN_ECHO);
2978                         remoteEchoOption = FALSE;
2979                         break;
2980                       default:
2981                         if (appData.debugMode)
2982                           fprintf(debugFP, "%d ", (unsigned char) option);
2983                         /* Whatever this is, it must already be turned
2984                            off, because we never agree to turn on
2985                            anything non-default, so according to the
2986                            protocol rules, we don't reply. */
2987                         break;
2988                     }
2989                     break;
2990                   case TN_DO:
2991                     if (appData.debugMode)
2992                       fprintf(debugFP, "\n<DO ");
2993                     switch (option = (unsigned char) buf[++i]) {
2994                       default:
2995                         /* Whatever this is, we refuse to do it. */
2996                         if (appData.debugMode)
2997                           fprintf(debugFP, "%d ", option);
2998                         TelnetRequest(TN_WONT, option);
2999                         break;
3000                     }
3001                     break;
3002                   case TN_DONT:
3003                     if (appData.debugMode)
3004                       fprintf(debugFP, "\n<DONT ");
3005                     switch (option = (unsigned char) buf[++i]) {
3006                       default:
3007                         if (appData.debugMode)
3008                           fprintf(debugFP, "%d ", option);
3009                         /* Whatever this is, we are already not doing
3010                            it, because we never agree to do anything
3011                            non-default, so according to the protocol
3012                            rules, we don't reply. */
3013                         break;
3014                     }
3015                     break;
3016                   case TN_IAC:
3017                     if (appData.debugMode)
3018                       fprintf(debugFP, "\n<IAC ");
3019                     /* Doubled IAC; pass it through */
3020                     i--;
3021                     break;
3022                   default:
3023                     if (appData.debugMode)
3024                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3025                     /* Drop all other telnet commands on the floor */
3026                     break;
3027                 }
3028                 if (oldi > next_out)
3029                   SendToPlayer(&buf[next_out], oldi - next_out);
3030                 if (++i > next_out)
3031                   next_out = i;
3032                 continue;
3033             }
3034
3035             /* OK, this at least will *usually* work */
3036             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3037                 loggedOn = TRUE;
3038             }
3039
3040             if (loggedOn && !intfSet) {
3041                 if (ics_type == ICS_ICC) {
3042                   snprintf(str, MSG_SIZ,
3043                           "/set-quietly interface %s\n/set-quietly style 12\n",
3044                           programVersion);
3045                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3046                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3047                 } else if (ics_type == ICS_CHESSNET) {
3048                   snprintf(str, MSG_SIZ, "/style 12\n");
3049                 } else {
3050                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3051                   strcat(str, programVersion);
3052                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3053                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3054                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3055 #ifdef WIN32
3056                   strcat(str, "$iset nohighlight 1\n");
3057 #endif
3058                   strcat(str, "$iset lock 1\n$style 12\n");
3059                 }
3060                 SendToICS(str);
3061                 NotifyFrontendLogin();
3062                 intfSet = TRUE;
3063             }
3064
3065             if (started == STARTED_COMMENT) {
3066                 /* Accumulate characters in comment */
3067                 parse[parse_pos++] = buf[i];
3068                 if (buf[i] == '\n') {
3069                     parse[parse_pos] = NULLCHAR;
3070                     if(chattingPartner>=0) {
3071                         char mess[MSG_SIZ];
3072                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3073                         OutputChatMessage(chattingPartner, mess);
3074                         chattingPartner = -1;
3075                         next_out = i+1; // [HGM] suppress printing in ICS window
3076                     } else
3077                     if(!suppressKibitz) // [HGM] kibitz
3078                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3079                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3080                         int nrDigit = 0, nrAlph = 0, j;
3081                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3082                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3083                         parse[parse_pos] = NULLCHAR;
3084                         // try to be smart: if it does not look like search info, it should go to
3085                         // ICS interaction window after all, not to engine-output window.
3086                         for(j=0; j<parse_pos; j++) { // count letters and digits
3087                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3088                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3089                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3090                         }
3091                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3092                             int depth=0; float score;
3093                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3094                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3095                                 pvInfoList[forwardMostMove-1].depth = depth;
3096                                 pvInfoList[forwardMostMove-1].score = 100*score;
3097                             }
3098                             OutputKibitz(suppressKibitz, parse);
3099                         } else {
3100                             char tmp[MSG_SIZ];
3101                             if(gameMode == IcsObserving) // restore original ICS messages
3102                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3103                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3104                             else
3105                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3106                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3107                             SendToPlayer(tmp, strlen(tmp));
3108                         }
3109                         next_out = i+1; // [HGM] suppress printing in ICS window
3110                     }
3111                     started = STARTED_NONE;
3112                 } else {
3113                     /* Don't match patterns against characters in comment */
3114                     i++;
3115                     continue;
3116                 }
3117             }
3118             if (started == STARTED_CHATTER) {
3119                 if (buf[i] != '\n') {
3120                     /* Don't match patterns against characters in chatter */
3121                     i++;
3122                     continue;
3123                 }
3124                 started = STARTED_NONE;
3125                 if(suppressKibitz) next_out = i+1;
3126             }
3127
3128             /* Kludge to deal with rcmd protocol */
3129             if (firstTime && looking_at(buf, &i, "\001*")) {
3130                 DisplayFatalError(&buf[1], 0, 1);
3131                 continue;
3132             } else {
3133                 firstTime = FALSE;
3134             }
3135
3136             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3137                 ics_type = ICS_ICC;
3138                 ics_prefix = "/";
3139                 if (appData.debugMode)
3140                   fprintf(debugFP, "ics_type %d\n", ics_type);
3141                 continue;
3142             }
3143             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3144                 ics_type = ICS_FICS;
3145                 ics_prefix = "$";
3146                 if (appData.debugMode)
3147                   fprintf(debugFP, "ics_type %d\n", ics_type);
3148                 continue;
3149             }
3150             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3151                 ics_type = ICS_CHESSNET;
3152                 ics_prefix = "/";
3153                 if (appData.debugMode)
3154                   fprintf(debugFP, "ics_type %d\n", ics_type);
3155                 continue;
3156             }
3157
3158             if (!loggedOn &&
3159                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3160                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3161                  looking_at(buf, &i, "will be \"*\""))) {
3162               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3163               continue;
3164             }
3165
3166             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3167               char buf[MSG_SIZ];
3168               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3169               DisplayIcsInteractionTitle(buf);
3170               have_set_title = TRUE;
3171             }
3172
3173             /* skip finger notes */
3174             if (started == STARTED_NONE &&
3175                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3176                  (buf[i] == '1' && buf[i+1] == '0')) &&
3177                 buf[i+2] == ':' && buf[i+3] == ' ') {
3178               started = STARTED_CHATTER;
3179               i += 3;
3180               continue;
3181             }
3182
3183             oldi = i;
3184             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3185             if(appData.seekGraph) {
3186                 if(soughtPending && MatchSoughtLine(buf+i)) {
3187                     i = strstr(buf+i, "rated") - buf;
3188                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3189                     next_out = leftover_start = i;
3190                     started = STARTED_CHATTER;
3191                     suppressKibitz = TRUE;
3192                     continue;
3193                 }
3194                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3195                         && looking_at(buf, &i, "* ads displayed")) {
3196                     soughtPending = FALSE;
3197                     seekGraphUp = TRUE;
3198                     DrawSeekGraph();
3199                     continue;
3200                 }
3201                 if(appData.autoRefresh) {
3202                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3203                         int s = (ics_type == ICS_ICC); // ICC format differs
3204                         if(seekGraphUp)
3205                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3206                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3207                         looking_at(buf, &i, "*% "); // eat prompt
3208                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3209                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3210                         next_out = i; // suppress
3211                         continue;
3212                     }
3213                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3214                         char *p = star_match[0];
3215                         while(*p) {
3216                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3217                             while(*p && *p++ != ' '); // next
3218                         }
3219                         looking_at(buf, &i, "*% "); // eat prompt
3220                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                         next_out = i;
3222                         continue;
3223                     }
3224                 }
3225             }
3226
3227             /* skip formula vars */
3228             if (started == STARTED_NONE &&
3229                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3230               started = STARTED_CHATTER;
3231               i += 3;
3232               continue;
3233             }
3234
3235             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3236             if (appData.autoKibitz && started == STARTED_NONE &&
3237                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3238                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3239                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3240                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3241                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3242                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3243                         suppressKibitz = TRUE;
3244                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3245                         next_out = i;
3246                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3247                                 && (gameMode == IcsPlayingWhite)) ||
3248                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3249                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3250                             started = STARTED_CHATTER; // own kibitz we simply discard
3251                         else {
3252                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3253                             parse_pos = 0; parse[0] = NULLCHAR;
3254                             savingComment = TRUE;
3255                             suppressKibitz = gameMode != IcsObserving ? 2 :
3256                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3257                         }
3258                         continue;
3259                 } else
3260                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3261                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3262                          && atoi(star_match[0])) {
3263                     // suppress the acknowledgements of our own autoKibitz
3264                     char *p;
3265                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3266                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3267                     SendToPlayer(star_match[0], strlen(star_match[0]));
3268                     if(looking_at(buf, &i, "*% ")) // eat prompt
3269                         suppressKibitz = FALSE;
3270                     next_out = i;
3271                     continue;
3272                 }
3273             } // [HGM] kibitz: end of patch
3274
3275             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3276
3277             // [HGM] chat: intercept tells by users for which we have an open chat window
3278             channel = -1;
3279             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3280                                            looking_at(buf, &i, "* whispers:") ||
3281                                            looking_at(buf, &i, "* kibitzes:") ||
3282                                            looking_at(buf, &i, "* shouts:") ||
3283                                            looking_at(buf, &i, "* c-shouts:") ||
3284                                            looking_at(buf, &i, "--> * ") ||
3285                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3286                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3287                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3288                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3289                 int p;
3290                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3291                 chattingPartner = -1;
3292
3293                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3294                 for(p=0; p<MAX_CHAT; p++) {
3295                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3296                     talker[0] = '['; strcat(talker, "] ");
3297                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3298                     chattingPartner = p; break;
3299                     }
3300                 } else
3301                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3302                 for(p=0; p<MAX_CHAT; p++) {
3303                     if(!strcmp("kibitzes", chatPartner[p])) {
3304                         talker[0] = '['; strcat(talker, "] ");
3305                         chattingPartner = p; break;
3306                     }
3307                 } else
3308                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3309                 for(p=0; p<MAX_CHAT; p++) {
3310                     if(!strcmp("whispers", chatPartner[p])) {
3311                         talker[0] = '['; strcat(talker, "] ");
3312                         chattingPartner = p; break;
3313                     }
3314                 } else
3315                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3316                   if(buf[i-8] == '-' && buf[i-3] == 't')
3317                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3318                     if(!strcmp("c-shouts", chatPartner[p])) {
3319                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3320                         chattingPartner = p; break;
3321                     }
3322                   }
3323                   if(chattingPartner < 0)
3324                   for(p=0; p<MAX_CHAT; p++) {
3325                     if(!strcmp("shouts", chatPartner[p])) {
3326                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3327                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3328                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3329                         chattingPartner = p; break;
3330                     }
3331                   }
3332                 }
3333                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3334                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3335                     talker[0] = 0; Colorize(ColorTell, FALSE);
3336                     chattingPartner = p; break;
3337                 }
3338                 if(chattingPartner<0) i = oldi; else {
3339                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3340                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3341                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3342                     started = STARTED_COMMENT;
3343                     parse_pos = 0; parse[0] = NULLCHAR;
3344                     savingComment = 3 + chattingPartner; // counts as TRUE
3345                     suppressKibitz = TRUE;
3346                     continue;
3347                 }
3348             } // [HGM] chat: end of patch
3349
3350           backup = i;
3351             if (appData.zippyTalk || appData.zippyPlay) {
3352                 /* [DM] Backup address for color zippy lines */
3353 #if ZIPPY
3354                if (loggedOn == TRUE)
3355                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3356                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3357 #endif
3358             } // [DM] 'else { ' deleted
3359                 if (
3360                     /* Regular tells and says */
3361                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3362                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3363                     looking_at(buf, &i, "* says: ") ||
3364                     /* Don't color "message" or "messages" output */
3365                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3366                     looking_at(buf, &i, "*. * at *:*: ") ||
3367                     looking_at(buf, &i, "--* (*:*): ") ||
3368                     /* Message notifications (same color as tells) */
3369                     looking_at(buf, &i, "* has left a message ") ||
3370                     looking_at(buf, &i, "* just sent you a message:\n") ||
3371                     /* Whispers and kibitzes */
3372                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3373                     looking_at(buf, &i, "* kibitzes: ") ||
3374                     /* Channel tells */
3375                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3376
3377                   if (tkind == 1 && strchr(star_match[0], ':')) {
3378                       /* Avoid "tells you:" spoofs in channels */
3379                      tkind = 3;
3380                   }
3381                   if (star_match[0][0] == NULLCHAR ||
3382                       strchr(star_match[0], ' ') ||
3383                       (tkind == 3 && strchr(star_match[1], ' '))) {
3384                     /* Reject bogus matches */
3385                     i = oldi;
3386                   } else {
3387                     if (appData.colorize) {
3388                       if (oldi > next_out) {
3389                         SendToPlayer(&buf[next_out], oldi - next_out);
3390                         next_out = oldi;
3391                       }
3392                       switch (tkind) {
3393                       case 1:
3394                         Colorize(ColorTell, FALSE);
3395                         curColor = ColorTell;
3396                         break;
3397                       case 2:
3398                         Colorize(ColorKibitz, FALSE);
3399                         curColor = ColorKibitz;
3400                         break;
3401                       case 3:
3402                         p = strrchr(star_match[1], '(');
3403                         if (p == NULL) {
3404                           p = star_match[1];
3405                         } else {
3406                           p++;
3407                         }
3408                         if (atoi(p) == 1) {
3409                           Colorize(ColorChannel1, FALSE);
3410                           curColor = ColorChannel1;
3411                         } else {
3412                           Colorize(ColorChannel, FALSE);
3413                           curColor = ColorChannel;
3414                         }
3415                         break;
3416                       case 5:
3417                         curColor = ColorNormal;
3418                         break;
3419                       }
3420                     }
3421                     if (started == STARTED_NONE && appData.autoComment &&
3422                         (gameMode == IcsObserving ||
3423                          gameMode == IcsPlayingWhite ||
3424                          gameMode == IcsPlayingBlack)) {
3425                       parse_pos = i - oldi;
3426                       memcpy(parse, &buf[oldi], parse_pos);
3427                       parse[parse_pos] = NULLCHAR;
3428                       started = STARTED_COMMENT;
3429                       savingComment = TRUE;
3430                     } else {
3431                       started = STARTED_CHATTER;
3432                       savingComment = FALSE;
3433                     }
3434                     loggedOn = TRUE;
3435                     continue;
3436                   }
3437                 }
3438
3439                 if (looking_at(buf, &i, "* s-shouts: ") ||
3440                     looking_at(buf, &i, "* c-shouts: ")) {
3441                     if (appData.colorize) {
3442                         if (oldi > next_out) {
3443                             SendToPlayer(&buf[next_out], oldi - next_out);
3444                             next_out = oldi;
3445                         }
3446                         Colorize(ColorSShout, FALSE);
3447                         curColor = ColorSShout;
3448                     }
3449                     loggedOn = TRUE;
3450                     started = STARTED_CHATTER;
3451                     continue;
3452                 }
3453
3454                 if (looking_at(buf, &i, "--->")) {
3455                     loggedOn = TRUE;
3456                     continue;
3457                 }
3458
3459                 if (looking_at(buf, &i, "* shouts: ") ||
3460                     looking_at(buf, &i, "--> ")) {
3461                     if (appData.colorize) {
3462                         if (oldi > next_out) {
3463                             SendToPlayer(&buf[next_out], oldi - next_out);
3464                             next_out = oldi;
3465                         }
3466                         Colorize(ColorShout, FALSE);
3467                         curColor = ColorShout;
3468                     }
3469                     loggedOn = TRUE;
3470                     started = STARTED_CHATTER;
3471                     continue;
3472                 }
3473
3474                 if (looking_at( buf, &i, "Challenge:")) {
3475                     if (appData.colorize) {
3476                         if (oldi > next_out) {
3477                             SendToPlayer(&buf[next_out], oldi - next_out);
3478                             next_out = oldi;
3479                         }
3480                         Colorize(ColorChallenge, FALSE);
3481                         curColor = ColorChallenge;
3482                     }
3483                     loggedOn = TRUE;
3484                     continue;
3485                 }
3486
3487                 if (looking_at(buf, &i, "* offers you") ||
3488                     looking_at(buf, &i, "* offers to be") ||
3489                     looking_at(buf, &i, "* would like to") ||
3490                     looking_at(buf, &i, "* requests to") ||
3491                     looking_at(buf, &i, "Your opponent offers") ||
3492                     looking_at(buf, &i, "Your opponent requests")) {
3493
3494                     if (appData.colorize) {
3495                         if (oldi > next_out) {
3496                             SendToPlayer(&buf[next_out], oldi - next_out);
3497                             next_out = oldi;
3498                         }
3499                         Colorize(ColorRequest, FALSE);
3500                         curColor = ColorRequest;
3501                     }
3502                     continue;
3503                 }
3504
3505                 if (looking_at(buf, &i, "* (*) seeking")) {
3506                     if (appData.colorize) {
3507                         if (oldi > next_out) {
3508                             SendToPlayer(&buf[next_out], oldi - next_out);
3509                             next_out = oldi;
3510                         }
3511                         Colorize(ColorSeek, FALSE);
3512                         curColor = ColorSeek;
3513                     }
3514                     continue;
3515             }
3516
3517           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3518
3519             if (looking_at(buf, &i, "\\   ")) {
3520                 if (prevColor != ColorNormal) {
3521                     if (oldi > next_out) {
3522                         SendToPlayer(&buf[next_out], oldi - next_out);
3523                         next_out = oldi;
3524                     }
3525                     Colorize(prevColor, TRUE);
3526                     curColor = prevColor;
3527                 }
3528                 if (savingComment) {
3529                     parse_pos = i - oldi;
3530                     memcpy(parse, &buf[oldi], parse_pos);
3531                     parse[parse_pos] = NULLCHAR;
3532                     started = STARTED_COMMENT;
3533                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3534                         chattingPartner = savingComment - 3; // kludge to remember the box
3535                 } else {
3536                     started = STARTED_CHATTER;
3537                 }
3538                 continue;
3539             }
3540
3541             if (looking_at(buf, &i, "Black Strength :") ||
3542                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3543                 looking_at(buf, &i, "<10>") ||
3544                 looking_at(buf, &i, "#@#")) {
3545                 /* Wrong board style */
3546                 loggedOn = TRUE;
3547                 SendToICS(ics_prefix);
3548                 SendToICS("set style 12\n");
3549                 SendToICS(ics_prefix);
3550                 SendToICS("refresh\n");
3551                 continue;
3552             }
3553
3554             if (looking_at(buf, &i, "login:")) {
3555               if (!have_sent_ICS_logon) {
3556                 if(ICSInitScript())
3557                   have_sent_ICS_logon = 1;
3558                 else // no init script was found
3559                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3560               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3561                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3562               }
3563                 continue;
3564             }
3565
3566             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3567                 (looking_at(buf, &i, "\n<12> ") ||
3568                  looking_at(buf, &i, "<12> "))) {
3569                 loggedOn = TRUE;
3570                 if (oldi > next_out) {
3571                     SendToPlayer(&buf[next_out], oldi - next_out);
3572                 }
3573                 next_out = i;
3574                 started = STARTED_BOARD;
3575                 parse_pos = 0;
3576                 continue;
3577             }
3578
3579             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3580                 looking_at(buf, &i, "<b1> ")) {
3581                 if (oldi > next_out) {
3582                     SendToPlayer(&buf[next_out], oldi - next_out);
3583                 }
3584                 next_out = i;
3585                 started = STARTED_HOLDINGS;
3586                 parse_pos = 0;
3587                 continue;
3588             }
3589
3590             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3591                 loggedOn = TRUE;
3592                 /* Header for a move list -- first line */
3593
3594                 switch (ics_getting_history) {
3595                   case H_FALSE:
3596                     switch (gameMode) {
3597                       case IcsIdle:
3598                       case BeginningOfGame:
3599                         /* User typed "moves" or "oldmoves" while we
3600                            were idle.  Pretend we asked for these
3601                            moves and soak them up so user can step
3602                            through them and/or save them.
3603                            */
3604                         Reset(FALSE, TRUE);
3605                         gameMode = IcsObserving;
3606                         ModeHighlight();
3607                         ics_gamenum = -1;
3608                         ics_getting_history = H_GOT_UNREQ_HEADER;
3609                         break;
3610                       case EditGame: /*?*/
3611                       case EditPosition: /*?*/
3612                         /* Should above feature work in these modes too? */
3613                         /* For now it doesn't */
3614                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3615                         break;
3616                       default:
3617                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3618                         break;
3619                     }
3620                     break;
3621                   case H_REQUESTED:
3622                     /* Is this the right one? */
3623                     if (gameInfo.white && gameInfo.black &&
3624                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3625                         strcmp(gameInfo.black, star_match[2]) == 0) {
3626                         /* All is well */
3627                         ics_getting_history = H_GOT_REQ_HEADER;
3628                     }
3629                     break;
3630                   case H_GOT_REQ_HEADER:
3631                   case H_GOT_UNREQ_HEADER:
3632                   case H_GOT_UNWANTED_HEADER:
3633                   case H_GETTING_MOVES:
3634                     /* Should not happen */
3635                     DisplayError(_("Error gathering move list: two headers"), 0);
3636                     ics_getting_history = H_FALSE;
3637                     break;
3638                 }
3639
3640                 /* Save player ratings into gameInfo if needed */
3641                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3642                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3643                     (gameInfo.whiteRating == -1 ||
3644                      gameInfo.blackRating == -1)) {
3645
3646                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3647                     gameInfo.blackRating = string_to_rating(star_match[3]);
3648                     if (appData.debugMode)
3649                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3650                               gameInfo.whiteRating, gameInfo.blackRating);
3651                 }
3652                 continue;
3653             }
3654
3655             if (looking_at(buf, &i,
3656               "* * match, initial time: * minute*, increment: * second")) {
3657                 /* Header for a move list -- second line */
3658                 /* Initial board will follow if this is a wild game */
3659                 if (gameInfo.event != NULL) free(gameInfo.event);
3660                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3661                 gameInfo.event = StrSave(str);
3662                 /* [HGM] we switched variant. Translate boards if needed. */
3663                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3664                 continue;
3665             }
3666
3667             if (looking_at(buf, &i, "Move  ")) {
3668                 /* Beginning of a move list */
3669                 switch (ics_getting_history) {
3670                   case H_FALSE:
3671                     /* Normally should not happen */
3672                     /* Maybe user hit reset while we were parsing */
3673                     break;
3674                   case H_REQUESTED:
3675                     /* Happens if we are ignoring a move list that is not
3676                      * the one we just requested.  Common if the user
3677                      * tries to observe two games without turning off
3678                      * getMoveList */
3679                     break;
3680                   case H_GETTING_MOVES:
3681                     /* Should not happen */
3682                     DisplayError(_("Error gathering move list: nested"), 0);
3683                     ics_getting_history = H_FALSE;
3684                     break;
3685                   case H_GOT_REQ_HEADER:
3686                     ics_getting_history = H_GETTING_MOVES;
3687                     started = STARTED_MOVES;
3688                     parse_pos = 0;
3689                     if (oldi > next_out) {
3690                         SendToPlayer(&buf[next_out], oldi - next_out);
3691                     }
3692                     break;
3693                   case H_GOT_UNREQ_HEADER:
3694                     ics_getting_history = H_GETTING_MOVES;
3695                     started = STARTED_MOVES_NOHIDE;
3696                     parse_pos = 0;
3697                     break;
3698                   case H_GOT_UNWANTED_HEADER:
3699                     ics_getting_history = H_FALSE;
3700                     break;
3701                 }
3702                 continue;
3703             }
3704
3705             if (looking_at(buf, &i, "% ") ||
3706                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3707                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3708                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3709                     soughtPending = FALSE;
3710                     seekGraphUp = TRUE;
3711                     DrawSeekGraph();
3712                 }
3713                 if(suppressKibitz) next_out = i;
3714                 savingComment = FALSE;
3715                 suppressKibitz = 0;
3716                 switch (started) {
3717                   case STARTED_MOVES:
3718                   case STARTED_MOVES_NOHIDE:
3719                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3720                     parse[parse_pos + i - oldi] = NULLCHAR;
3721                     ParseGameHistory(parse);
3722 #if ZIPPY
3723                     if (appData.zippyPlay && first.initDone) {
3724                         FeedMovesToProgram(&first, forwardMostMove);
3725                         if (gameMode == IcsPlayingWhite) {
3726                             if (WhiteOnMove(forwardMostMove)) {
3727                                 if (first.sendTime) {
3728                                   if (first.useColors) {
3729                                     SendToProgram("black\n", &first);
3730                                   }
3731                                   SendTimeRemaining(&first, TRUE);
3732                                 }
3733                                 if (first.useColors) {
3734                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3735                                 }
3736                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3737                                 first.maybeThinking = TRUE;
3738                             } else {
3739                                 if (first.usePlayother) {
3740                                   if (first.sendTime) {
3741                                     SendTimeRemaining(&first, TRUE);
3742                                   }
3743                                   SendToProgram("playother\n", &first);
3744                                   firstMove = FALSE;
3745                                 } else {
3746                                   firstMove = TRUE;
3747                                 }
3748                             }
3749                         } else if (gameMode == IcsPlayingBlack) {
3750                             if (!WhiteOnMove(forwardMostMove)) {
3751                                 if (first.sendTime) {
3752                                   if (first.useColors) {
3753                                     SendToProgram("white\n", &first);
3754                                   }
3755                                   SendTimeRemaining(&first, FALSE);
3756                                 }
3757                                 if (first.useColors) {
3758                                   SendToProgram("black\n", &first);
3759                                 }
3760                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3761                                 first.maybeThinking = TRUE;
3762                             } else {
3763                                 if (first.usePlayother) {
3764                                   if (first.sendTime) {
3765                                     SendTimeRemaining(&first, FALSE);
3766                                   }
3767                                   SendToProgram("playother\n", &first);
3768                                   firstMove = FALSE;
3769                                 } else {
3770                                   firstMove = TRUE;
3771                                 }
3772                             }
3773                         }
3774                     }
3775 #endif
3776                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3777                         /* Moves came from oldmoves or moves command
3778                            while we weren't doing anything else.
3779                            */
3780                         currentMove = forwardMostMove;
3781                         ClearHighlights();/*!!could figure this out*/
3782                         flipView = appData.flipView;
3783                         DrawPosition(TRUE, boards[currentMove]);
3784                         DisplayBothClocks();
3785                         snprintf(str, MSG_SIZ, "%s %s %s",
3786                                 gameInfo.white, _("vs."),  gameInfo.black);
3787                         DisplayTitle(str);
3788                         gameMode = IcsIdle;
3789                     } else {
3790                         /* Moves were history of an active game */
3791                         if (gameInfo.resultDetails != NULL) {
3792                             free(gameInfo.resultDetails);
3793                             gameInfo.resultDetails = NULL;
3794                         }
3795                     }
3796                     HistorySet(parseList, backwardMostMove,
3797                                forwardMostMove, currentMove-1);
3798                     DisplayMove(currentMove - 1);
3799                     if (started == STARTED_MOVES) next_out = i;
3800                     started = STARTED_NONE;
3801                     ics_getting_history = H_FALSE;
3802                     break;
3803
3804                   case STARTED_OBSERVE:
3805                     started = STARTED_NONE;
3806                     SendToICS(ics_prefix);
3807                     SendToICS("refresh\n");
3808                     break;
3809
3810                   default:
3811                     break;
3812                 }
3813                 if(bookHit) { // [HGM] book: simulate book reply
3814                     static char bookMove[MSG_SIZ]; // a bit generous?
3815
3816                     programStats.nodes = programStats.depth = programStats.time =
3817                     programStats.score = programStats.got_only_move = 0;
3818                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3819
3820                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3821                     strcat(bookMove, bookHit);
3822                     HandleMachineMove(bookMove, &first);
3823                 }
3824                 continue;
3825             }
3826
3827             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3828                  started == STARTED_HOLDINGS ||
3829                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3830                 /* Accumulate characters in move list or board */
3831                 parse[parse_pos++] = buf[i];
3832             }
3833
3834             /* Start of game messages.  Mostly we detect start of game
3835                when the first board image arrives.  On some versions
3836                of the ICS, though, we need to do a "refresh" after starting
3837                to observe in order to get the current board right away. */
3838             if (looking_at(buf, &i, "Adding game * to observation list")) {
3839                 started = STARTED_OBSERVE;
3840                 continue;
3841             }
3842
3843             /* Handle auto-observe */
3844             if (appData.autoObserve &&
3845                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3846                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3847                 char *player;
3848                 /* Choose the player that was highlighted, if any. */
3849                 if (star_match[0][0] == '\033' ||
3850                     star_match[1][0] != '\033') {
3851                     player = star_match[0];
3852                 } else {
3853                     player = star_match[2];
3854                 }
3855                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3856                         ics_prefix, StripHighlightAndTitle(player));
3857                 SendToICS(str);
3858
3859                 /* Save ratings from notify string */
3860                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3861                 player1Rating = string_to_rating(star_match[1]);
3862                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3863                 player2Rating = string_to_rating(star_match[3]);
3864
3865                 if (appData.debugMode)
3866                   fprintf(debugFP,
3867                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3868                           player1Name, player1Rating,
3869                           player2Name, player2Rating);
3870
3871                 continue;
3872             }
3873
3874             /* Deal with automatic examine mode after a game,
3875                and with IcsObserving -> IcsExamining transition */
3876             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3877                 looking_at(buf, &i, "has made you an examiner of game *")) {
3878
3879                 int gamenum = atoi(star_match[0]);
3880                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3881                     gamenum == ics_gamenum) {
3882                     /* We were already playing or observing this game;
3883                        no need to refetch history */
3884                     gameMode = IcsExamining;
3885                     if (pausing) {
3886                         pauseExamForwardMostMove = forwardMostMove;
3887                     } else if (currentMove < forwardMostMove) {
3888                         ForwardInner(forwardMostMove);
3889                     }
3890                 } else {
3891                     /* I don't think this case really can happen */
3892                     SendToICS(ics_prefix);
3893                     SendToICS("refresh\n");
3894                 }
3895                 continue;
3896             }
3897
3898             /* Error messages */
3899 //          if (ics_user_moved) {
3900             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3901                 if (looking_at(buf, &i, "Illegal move") ||
3902                     looking_at(buf, &i, "Not a legal move") ||
3903                     looking_at(buf, &i, "Your king is in check") ||
3904                     looking_at(buf, &i, "It isn't your turn") ||
3905                     looking_at(buf, &i, "It is not your move")) {
3906                     /* Illegal move */
3907                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3908                         currentMove = forwardMostMove-1;
3909                         DisplayMove(currentMove - 1); /* before DMError */
3910                         DrawPosition(FALSE, boards[currentMove]);
3911                         SwitchClocks(forwardMostMove-1); // [HGM] race
3912                         DisplayBothClocks();
3913                     }
3914                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3915                     ics_user_moved = 0;
3916                     continue;
3917                 }
3918             }
3919
3920             if (looking_at(buf, &i, "still have time") ||
3921                 looking_at(buf, &i, "not out of time") ||
3922                 looking_at(buf, &i, "either player is out of time") ||
3923                 looking_at(buf, &i, "has timeseal; checking")) {
3924                 /* We must have called his flag a little too soon */
3925                 whiteFlag = blackFlag = FALSE;
3926                 continue;
3927             }
3928
3929             if (looking_at(buf, &i, "added * seconds to") ||
3930                 looking_at(buf, &i, "seconds were added to")) {
3931                 /* Update the clocks */
3932                 SendToICS(ics_prefix);
3933                 SendToICS("refresh\n");
3934                 continue;
3935             }
3936
3937             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3938                 ics_clock_paused = TRUE;
3939                 StopClocks();
3940                 continue;
3941             }
3942
3943             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3944                 ics_clock_paused = FALSE;
3945                 StartClocks();
3946                 continue;
3947             }
3948
3949             /* Grab player ratings from the Creating: message.
3950                Note we have to check for the special case when
3951                the ICS inserts things like [white] or [black]. */
3952             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3953                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3954                 /* star_matches:
3955                    0    player 1 name (not necessarily white)
3956                    1    player 1 rating
3957                    2    empty, white, or black (IGNORED)
3958                    3    player 2 name (not necessarily black)
3959                    4    player 2 rating
3960
3961                    The names/ratings are sorted out when the game
3962                    actually starts (below).
3963                 */
3964                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3965                 player1Rating = string_to_rating(star_match[1]);
3966                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3967                 player2Rating = string_to_rating(star_match[4]);
3968
3969                 if (appData.debugMode)
3970                   fprintf(debugFP,
3971                           "Ratings from 'Creating:' %s %d, %s %d\n",
3972                           player1Name, player1Rating,
3973                           player2Name, player2Rating);
3974
3975                 continue;
3976             }
3977
3978             /* Improved generic start/end-of-game messages */
3979             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3980                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3981                 /* If tkind == 0: */
3982                 /* star_match[0] is the game number */
3983                 /*           [1] is the white player's name */
3984                 /*           [2] is the black player's name */
3985                 /* For end-of-game: */
3986                 /*           [3] is the reason for the game end */
3987                 /*           [4] is a PGN end game-token, preceded by " " */
3988                 /* For start-of-game: */
3989                 /*           [3] begins with "Creating" or "Continuing" */
3990                 /*           [4] is " *" or empty (don't care). */
3991                 int gamenum = atoi(star_match[0]);
3992                 char *whitename, *blackname, *why, *endtoken;
3993                 ChessMove endtype = EndOfFile;
3994
3995                 if (tkind == 0) {
3996                   whitename = star_match[1];
3997                   blackname = star_match[2];
3998                   why = star_match[3];
3999                   endtoken = star_match[4];
4000                 } else {
4001                   whitename = star_match[1];
4002                   blackname = star_match[3];
4003                   why = star_match[5];
4004                   endtoken = star_match[6];
4005                 }
4006
4007                 /* Game start messages */
4008                 if (strncmp(why, "Creating ", 9) == 0 ||
4009                     strncmp(why, "Continuing ", 11) == 0) {
4010                     gs_gamenum = gamenum;
4011                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4012                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4013                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4014 #if ZIPPY
4015                     if (appData.zippyPlay) {
4016                         ZippyGameStart(whitename, blackname);
4017                     }
4018 #endif /*ZIPPY*/
4019                     partnerBoardValid = FALSE; // [HGM] bughouse
4020                     continue;
4021                 }
4022
4023                 /* Game end messages */
4024                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4025                     ics_gamenum != gamenum) {
4026                     continue;
4027                 }
4028                 while (endtoken[0] == ' ') endtoken++;
4029                 switch (endtoken[0]) {
4030                   case '*':
4031                   default:
4032                     endtype = GameUnfinished;
4033                     break;
4034                   case '0':
4035                     endtype = BlackWins;
4036                     break;
4037                   case '1':
4038                     if (endtoken[1] == '/')
4039                       endtype = GameIsDrawn;
4040                     else
4041                       endtype = WhiteWins;
4042                     break;
4043                 }
4044                 GameEnds(endtype, why, GE_ICS);
4045 #if ZIPPY
4046                 if (appData.zippyPlay && first.initDone) {
4047                     ZippyGameEnd(endtype, why);
4048                     if (first.pr == NoProc) {
4049                       /* Start the next process early so that we'll
4050                          be ready for the next challenge */
4051                       StartChessProgram(&first);
4052                     }
4053                     /* Send "new" early, in case this command takes
4054                        a long time to finish, so that we'll be ready
4055                        for the next challenge. */
4056                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4057                     Reset(TRUE, TRUE);
4058                 }
4059 #endif /*ZIPPY*/
4060                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4061                 continue;
4062             }
4063
4064             if (looking_at(buf, &i, "Removing game * from observation") ||
4065                 looking_at(buf, &i, "no longer observing game *") ||
4066                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4067                 if (gameMode == IcsObserving &&
4068                     atoi(star_match[0]) == ics_gamenum)
4069                   {
4070                       /* icsEngineAnalyze */
4071                       if (appData.icsEngineAnalyze) {
4072                             ExitAnalyzeMode();
4073                             ModeHighlight();
4074                       }
4075                       StopClocks();
4076                       gameMode = IcsIdle;
4077                       ics_gamenum = -1;
4078                       ics_user_moved = FALSE;
4079                   }
4080                 continue;
4081             }
4082
4083             if (looking_at(buf, &i, "no longer examining game *")) {
4084                 if (gameMode == IcsExamining &&
4085                     atoi(star_match[0]) == ics_gamenum)
4086                   {
4087                       gameMode = IcsIdle;
4088                       ics_gamenum = -1;
4089                       ics_user_moved = FALSE;
4090                   }
4091                 continue;
4092             }
4093
4094             /* Advance leftover_start past any newlines we find,
4095                so only partial lines can get reparsed */
4096             if (looking_at(buf, &i, "\n")) {
4097                 prevColor = curColor;
4098                 if (curColor != ColorNormal) {
4099                     if (oldi > next_out) {
4100                         SendToPlayer(&buf[next_out], oldi - next_out);
4101                         next_out = oldi;
4102                     }
4103                     Colorize(ColorNormal, FALSE);
4104                     curColor = ColorNormal;
4105                 }
4106                 if (started == STARTED_BOARD) {
4107                     started = STARTED_NONE;
4108                     parse[parse_pos] = NULLCHAR;
4109                     ParseBoard12(parse);
4110                     ics_user_moved = 0;
4111
4112                     /* Send premove here */
4113                     if (appData.premove) {
4114                       char str[MSG_SIZ];
4115                       if (currentMove == 0 &&
4116                           gameMode == IcsPlayingWhite &&
4117                           appData.premoveWhite) {
4118                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4119                         if (appData.debugMode)
4120                           fprintf(debugFP, "Sending premove:\n");
4121                         SendToICS(str);
4122                       } else if (currentMove == 1 &&
4123                                  gameMode == IcsPlayingBlack &&
4124                                  appData.premoveBlack) {
4125                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4126                         if (appData.debugMode)
4127                           fprintf(debugFP, "Sending premove:\n");
4128                         SendToICS(str);
4129                       } else if (gotPremove) {
4130                         gotPremove = 0;
4131                         ClearPremoveHighlights();
4132                         if (appData.debugMode)
4133                           fprintf(debugFP, "Sending premove:\n");
4134                           UserMoveEvent(premoveFromX, premoveFromY,
4135                                         premoveToX, premoveToY,
4136                                         premovePromoChar);
4137                       }
4138                     }
4139
4140                     /* Usually suppress following prompt */
4141                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4142                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4143                         if (looking_at(buf, &i, "*% ")) {
4144                             savingComment = FALSE;
4145                             suppressKibitz = 0;
4146                         }
4147                     }
4148                     next_out = i;
4149                 } else if (started == STARTED_HOLDINGS) {
4150                     int gamenum;
4151                     char new_piece[MSG_SIZ];
4152                     started = STARTED_NONE;
4153                     parse[parse_pos] = NULLCHAR;
4154                     if (appData.debugMode)
4155                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4156                                                         parse, currentMove);
4157                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4158                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4159                         if (gameInfo.variant == VariantNormal) {
4160                           /* [HGM] We seem to switch variant during a game!
4161                            * Presumably no holdings were displayed, so we have
4162                            * to move the position two files to the right to
4163                            * create room for them!
4164                            */
4165                           VariantClass newVariant;
4166                           switch(gameInfo.boardWidth) { // base guess on board width
4167                                 case 9:  newVariant = VariantShogi; break;
4168                                 case 10: newVariant = VariantGreat; break;
4169                                 default: newVariant = VariantCrazyhouse; break;
4170                           }
4171                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4172                           /* Get a move list just to see the header, which
4173                              will tell us whether this is really bug or zh */
4174                           if (ics_getting_history == H_FALSE) {
4175                             ics_getting_history = H_REQUESTED;
4176                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4177                             SendToICS(str);
4178                           }
4179                         }
4180                         new_piece[0] = NULLCHAR;
4181                         sscanf(parse, "game %d white [%s black [%s <- %s",
4182                                &gamenum, white_holding, black_holding,
4183                                new_piece);
4184                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4185                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4186                         /* [HGM] copy holdings to board holdings area */
4187                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4188                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4189                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4190 #if ZIPPY
4191                         if (appData.zippyPlay && first.initDone) {
4192                             ZippyHoldings(white_holding, black_holding,
4193                                           new_piece);
4194                         }
4195 #endif /*ZIPPY*/
4196                         if (tinyLayout || smallLayout) {
4197                             char wh[16], bh[16];
4198                             PackHolding(wh, white_holding);
4199                             PackHolding(bh, black_holding);
4200                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4201                                     gameInfo.white, gameInfo.black);
4202                         } else {
4203                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4204                                     gameInfo.white, white_holding, _("vs."),
4205                                     gameInfo.black, black_holding);
4206                         }
4207                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4208                         DrawPosition(FALSE, boards[currentMove]);
4209                         DisplayTitle(str);
4210                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4211                         sscanf(parse, "game %d white [%s black [%s <- %s",
4212                                &gamenum, white_holding, black_holding,
4213                                new_piece);
4214                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4215                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4216                         /* [HGM] copy holdings to partner-board holdings area */
4217                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4218                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4219                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4220                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4221                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4222                       }
4223                     }
4224                     /* Suppress following prompt */
4225                     if (looking_at(buf, &i, "*% ")) {
4226                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4227                         savingComment = FALSE;
4228                         suppressKibitz = 0;
4229                     }
4230                     next_out = i;
4231                 }
4232                 continue;
4233             }
4234
4235             i++;                /* skip unparsed character and loop back */
4236         }
4237
4238         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4239 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4240 //          SendToPlayer(&buf[next_out], i - next_out);
4241             started != STARTED_HOLDINGS && leftover_start > next_out) {
4242             SendToPlayer(&buf[next_out], leftover_start - next_out);
4243             next_out = i;
4244         }
4245
4246         leftover_len = buf_len - leftover_start;
4247         /* if buffer ends with something we couldn't parse,
4248            reparse it after appending the next read */
4249
4250     } else if (count == 0) {
4251         RemoveInputSource(isr);
4252         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4253     } else {
4254         DisplayFatalError(_("Error reading from ICS"), error, 1);
4255     }
4256 }
4257
4258
4259 /* Board style 12 looks like this:
4260
4261    <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
4262
4263  * The "<12> " is stripped before it gets to this routine.  The two
4264  * trailing 0's (flip state and clock ticking) are later addition, and
4265  * some chess servers may not have them, or may have only the first.
4266  * Additional trailing fields may be added in the future.
4267  */
4268
4269 #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"
4270
4271 #define RELATION_OBSERVING_PLAYED    0
4272 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4273 #define RELATION_PLAYING_MYMOVE      1
4274 #define RELATION_PLAYING_NOTMYMOVE  -1
4275 #define RELATION_EXAMINING           2
4276 #define RELATION_ISOLATED_BOARD     -3
4277 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4278
4279 void
4280 ParseBoard12 (char *string)
4281 {
4282 #if ZIPPY
4283     int i, takeback;
4284     char *bookHit = NULL; // [HGM] book
4285 #endif
4286     GameMode newGameMode;
4287     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4288     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4289     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4290     char to_play, board_chars[200];
4291     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4292     char black[32], white[32];
4293     Board board;
4294     int prevMove = currentMove;
4295     int ticking = 2;
4296     ChessMove moveType;
4297     int fromX, fromY, toX, toY;
4298     char promoChar;
4299     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4300     Boolean weird = FALSE, reqFlag = FALSE;
4301
4302     fromX = fromY = toX = toY = -1;
4303
4304     newGame = FALSE;
4305
4306     if (appData.debugMode)
4307       fprintf(debugFP, "Parsing board: %s\n", string);
4308
4309     move_str[0] = NULLCHAR;
4310     elapsed_time[0] = NULLCHAR;
4311     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4312         int  i = 0, j;
4313         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4314             if(string[i] == ' ') { ranks++; files = 0; }
4315             else files++;
4316             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4317             i++;
4318         }
4319         for(j = 0; j <i; j++) board_chars[j] = string[j];
4320         board_chars[i] = '\0';
4321         string += i + 1;
4322     }
4323     n = sscanf(string, PATTERN, &to_play, &double_push,
4324                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4325                &gamenum, white, black, &relation, &basetime, &increment,
4326                &white_stren, &black_stren, &white_time, &black_time,
4327                &moveNum, str, elapsed_time, move_str, &ics_flip,
4328                &ticking);
4329
4330     if (n < 21) {
4331         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4332         DisplayError(str, 0);
4333         return;
4334     }
4335
4336     /* Convert the move number to internal form */
4337     moveNum = (moveNum - 1) * 2;
4338     if (to_play == 'B') moveNum++;
4339     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4340       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4341                         0, 1);
4342       return;
4343     }
4344
4345     switch (relation) {
4346       case RELATION_OBSERVING_PLAYED:
4347       case RELATION_OBSERVING_STATIC:
4348         if (gamenum == -1) {
4349             /* Old ICC buglet */
4350             relation = RELATION_OBSERVING_STATIC;
4351         }
4352         newGameMode = IcsObserving;
4353         break;
4354       case RELATION_PLAYING_MYMOVE:
4355       case RELATION_PLAYING_NOTMYMOVE:
4356         newGameMode =
4357           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4358             IcsPlayingWhite : IcsPlayingBlack;
4359         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4360         break;
4361       case RELATION_EXAMINING:
4362         newGameMode = IcsExamining;
4363         break;
4364       case RELATION_ISOLATED_BOARD:
4365       default:
4366         /* Just display this board.  If user was doing something else,
4367            we will forget about it until the next board comes. */
4368         newGameMode = IcsIdle;
4369         break;
4370       case RELATION_STARTING_POSITION:
4371         newGameMode = gameMode;
4372         break;
4373     }
4374
4375     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4376         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4377          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4378       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4379       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4380       static int lastBgGame = -1;
4381       char *toSqr;
4382       for (k = 0; k < ranks; k++) {
4383         for (j = 0; j < files; j++)
4384           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4385         if(gameInfo.holdingsWidth > 1) {
4386              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4387              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4388         }
4389       }
4390       CopyBoard(partnerBoard, board);
4391       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4392         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4393         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4394       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4395       if(toSqr = strchr(str, '-')) {
4396         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4397         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4398       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4399       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4400       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4401       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4402       if(twoBoards) {
4403           DisplayWhiteClock(white_time*fac, to_play == 'W');
4404           DisplayBlackClock(black_time*fac, to_play != 'W');
4405           activePartner = to_play;
4406           if(gamenum != lastBgGame) {
4407               char buf[MSG_SIZ];
4408               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4409               DisplayTitle(buf);
4410           }
4411           lastBgGame = gamenum;
4412           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4413                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4414       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4415                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4416       if(!twoBoards) DisplayMessage(partnerStatus, "");
4417         partnerBoardValid = TRUE;
4418       return;
4419     }
4420
4421     if(appData.dualBoard && appData.bgObserve) {
4422         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4423             SendToICS(ics_prefix), SendToICS("pobserve\n");
4424         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4425             char buf[MSG_SIZ];
4426             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4427             SendToICS(buf);
4428         }
4429     }
4430
4431     /* Modify behavior for initial board display on move listing
4432        of wild games.
4433        */
4434     switch (ics_getting_history) {
4435       case H_FALSE:
4436       case H_REQUESTED:
4437         break;
4438       case H_GOT_REQ_HEADER:
4439       case H_GOT_UNREQ_HEADER:
4440         /* This is the initial position of the current game */
4441         gamenum = ics_gamenum;
4442         moveNum = 0;            /* old ICS bug workaround */
4443         if (to_play == 'B') {
4444           startedFromSetupPosition = TRUE;
4445           blackPlaysFirst = TRUE;
4446           moveNum = 1;
4447           if (forwardMostMove == 0) forwardMostMove = 1;
4448           if (backwardMostMove == 0) backwardMostMove = 1;
4449           if (currentMove == 0) currentMove = 1;
4450         }
4451         newGameMode = gameMode;
4452         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4453         break;
4454       case H_GOT_UNWANTED_HEADER:
4455         /* This is an initial board that we don't want */
4456         return;
4457       case H_GETTING_MOVES:
4458         /* Should not happen */
4459         DisplayError(_("Error gathering move list: extra board"), 0);
4460         ics_getting_history = H_FALSE;
4461         return;
4462     }
4463
4464    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4465                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4466                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4467      /* [HGM] We seem to have switched variant unexpectedly
4468       * Try to guess new variant from board size
4469       */
4470           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4471           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4472           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4473           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4474           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4475           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4476           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4477           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4478           /* Get a move list just to see the header, which
4479              will tell us whether this is really bug or zh */
4480           if (ics_getting_history == H_FALSE) {
4481             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4482             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4483             SendToICS(str);
4484           }
4485     }
4486
4487     /* Take action if this is the first board of a new game, or of a
4488        different game than is currently being displayed.  */
4489     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4490         relation == RELATION_ISOLATED_BOARD) {
4491
4492         /* Forget the old game and get the history (if any) of the new one */
4493         if (gameMode != BeginningOfGame) {
4494           Reset(TRUE, TRUE);
4495         }
4496         newGame = TRUE;
4497         if (appData.autoRaiseBoard) BoardToTop();
4498         prevMove = -3;
4499         if (gamenum == -1) {
4500             newGameMode = IcsIdle;
4501         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4502                    appData.getMoveList && !reqFlag) {
4503             /* Need to get game history */
4504             ics_getting_history = H_REQUESTED;
4505             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4506             SendToICS(str);
4507         }
4508
4509         /* Initially flip the board to have black on the bottom if playing
4510            black or if the ICS flip flag is set, but let the user change
4511            it with the Flip View button. */
4512         flipView = appData.autoFlipView ?
4513           (newGameMode == IcsPlayingBlack) || ics_flip :
4514           appData.flipView;
4515
4516         /* Done with values from previous mode; copy in new ones */
4517         gameMode = newGameMode;
4518         ModeHighlight();
4519         ics_gamenum = gamenum;
4520         if (gamenum == gs_gamenum) {
4521             int klen = strlen(gs_kind);
4522             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4523             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4524             gameInfo.event = StrSave(str);
4525         } else {
4526             gameInfo.event = StrSave("ICS game");
4527         }
4528         gameInfo.site = StrSave(appData.icsHost);
4529         gameInfo.date = PGNDate();
4530         gameInfo.round = StrSave("-");
4531         gameInfo.white = StrSave(white);
4532         gameInfo.black = StrSave(black);
4533         timeControl = basetime * 60 * 1000;
4534         timeControl_2 = 0;
4535         timeIncrement = increment * 1000;
4536         movesPerSession = 0;
4537         gameInfo.timeControl = TimeControlTagValue();
4538         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4539   if (appData.debugMode) {
4540     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4541     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4542     setbuf(debugFP, NULL);
4543   }
4544
4545         gameInfo.outOfBook = NULL;
4546
4547         /* Do we have the ratings? */
4548         if (strcmp(player1Name, white) == 0 &&
4549             strcmp(player2Name, black) == 0) {
4550             if (appData.debugMode)
4551               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4552                       player1Rating, player2Rating);
4553             gameInfo.whiteRating = player1Rating;
4554             gameInfo.blackRating = player2Rating;
4555         } else if (strcmp(player2Name, white) == 0 &&
4556                    strcmp(player1Name, black) == 0) {
4557             if (appData.debugMode)
4558               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4559                       player2Rating, player1Rating);
4560             gameInfo.whiteRating = player2Rating;
4561             gameInfo.blackRating = player1Rating;
4562         }
4563         player1Name[0] = player2Name[0] = NULLCHAR;
4564
4565         /* Silence shouts if requested */
4566         if (appData.quietPlay &&
4567             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4568             SendToICS(ics_prefix);
4569             SendToICS("set shout 0\n");
4570         }
4571     }
4572
4573     /* Deal with midgame name changes */
4574     if (!newGame) {
4575         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4576             if (gameInfo.white) free(gameInfo.white);
4577             gameInfo.white = StrSave(white);
4578         }
4579         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4580             if (gameInfo.black) free(gameInfo.black);
4581             gameInfo.black = StrSave(black);
4582         }
4583     }
4584
4585     /* Throw away game result if anything actually changes in examine mode */
4586     if (gameMode == IcsExamining && !newGame) {
4587         gameInfo.result = GameUnfinished;
4588         if (gameInfo.resultDetails != NULL) {
4589             free(gameInfo.resultDetails);
4590             gameInfo.resultDetails = NULL;
4591         }
4592     }
4593
4594     /* In pausing && IcsExamining mode, we ignore boards coming
4595        in if they are in a different variation than we are. */
4596     if (pauseExamInvalid) return;
4597     if (pausing && gameMode == IcsExamining) {
4598         if (moveNum <= pauseExamForwardMostMove) {
4599             pauseExamInvalid = TRUE;
4600             forwardMostMove = pauseExamForwardMostMove;
4601             return;
4602         }
4603     }
4604
4605   if (appData.debugMode) {
4606     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4607   }
4608     /* Parse the board */
4609     for (k = 0; k < ranks; k++) {
4610       for (j = 0; j < files; j++)
4611         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4612       if(gameInfo.holdingsWidth > 1) {
4613            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4614            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4615       }
4616     }
4617     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4618       board[5][BOARD_RGHT+1] = WhiteAngel;
4619       board[6][BOARD_RGHT+1] = WhiteMarshall;
4620       board[1][0] = BlackMarshall;
4621       board[2][0] = BlackAngel;
4622       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4623     }
4624     CopyBoard(boards[moveNum], board);
4625     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4626     if (moveNum == 0) {
4627         startedFromSetupPosition =
4628           !CompareBoards(board, initialPosition);
4629         if(startedFromSetupPosition)
4630             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4631     }
4632
4633     /* [HGM] Set castling rights. Take the outermost Rooks,
4634        to make it also work for FRC opening positions. Note that board12
4635        is really defective for later FRC positions, as it has no way to
4636        indicate which Rook can castle if they are on the same side of King.
4637        For the initial position we grant rights to the outermost Rooks,
4638        and remember thos rights, and we then copy them on positions
4639        later in an FRC game. This means WB might not recognize castlings with
4640        Rooks that have moved back to their original position as illegal,
4641        but in ICS mode that is not its job anyway.
4642     */
4643     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4644     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4645
4646         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4647             if(board[0][i] == WhiteRook) j = i;
4648         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4649         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4650             if(board[0][i] == WhiteRook) j = i;
4651         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4652         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4653             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4654         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4655         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4656             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4657         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4658
4659         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4660         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4661         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4662             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4663         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4664             if(board[BOARD_HEIGHT-1][k] == bKing)
4665                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4666         if(gameInfo.variant == VariantTwoKings) {
4667             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4668             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4669             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4670         }
4671     } else { int r;
4672         r = boards[moveNum][CASTLING][0] = initialRights[0];
4673         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4674         r = boards[moveNum][CASTLING][1] = initialRights[1];
4675         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4676         r = boards[moveNum][CASTLING][3] = initialRights[3];
4677         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4678         r = boards[moveNum][CASTLING][4] = initialRights[4];
4679         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4680         /* wildcastle kludge: always assume King has rights */
4681         r = boards[moveNum][CASTLING][2] = initialRights[2];
4682         r = boards[moveNum][CASTLING][5] = initialRights[5];
4683     }
4684     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4685     boards[moveNum][EP_STATUS] = EP_NONE;
4686     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4687     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4688     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4689
4690
4691     if (ics_getting_history == H_GOT_REQ_HEADER ||
4692         ics_getting_history == H_GOT_UNREQ_HEADER) {
4693         /* This was an initial position from a move list, not
4694            the current position */
4695         return;
4696     }
4697
4698     /* Update currentMove and known move number limits */
4699     newMove = newGame || moveNum > forwardMostMove;
4700
4701     if (newGame) {
4702         forwardMostMove = backwardMostMove = currentMove = moveNum;
4703         if (gameMode == IcsExamining && moveNum == 0) {
4704           /* Workaround for ICS limitation: we are not told the wild
4705              type when starting to examine a game.  But if we ask for
4706              the move list, the move list header will tell us */
4707             ics_getting_history = H_REQUESTED;
4708             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4709             SendToICS(str);
4710         }
4711     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4712                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4713 #if ZIPPY
4714         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4715         /* [HGM] applied this also to an engine that is silently watching        */
4716         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4717             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4718             gameInfo.variant == currentlyInitializedVariant) {
4719           takeback = forwardMostMove - moveNum;
4720           for (i = 0; i < takeback; i++) {
4721             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4722             SendToProgram("undo\n", &first);
4723           }
4724         }
4725 #endif
4726
4727         forwardMostMove = moveNum;
4728         if (!pausing || currentMove > forwardMostMove)
4729           currentMove = forwardMostMove;
4730     } else {
4731         /* New part of history that is not contiguous with old part */
4732         if (pausing && gameMode == IcsExamining) {
4733             pauseExamInvalid = TRUE;
4734             forwardMostMove = pauseExamForwardMostMove;
4735             return;
4736         }
4737         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4738 #if ZIPPY
4739             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4740                 // [HGM] when we will receive the move list we now request, it will be
4741                 // fed to the engine from the first move on. So if the engine is not
4742                 // in the initial position now, bring it there.
4743                 InitChessProgram(&first, 0);
4744             }
4745 #endif
4746             ics_getting_history = H_REQUESTED;
4747             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4748             SendToICS(str);
4749         }
4750         forwardMostMove = backwardMostMove = currentMove = moveNum;
4751     }
4752
4753     /* Update the clocks */
4754     if (strchr(elapsed_time, '.')) {
4755       /* Time is in ms */
4756       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4757       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4758     } else {
4759       /* Time is in seconds */
4760       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4761       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4762     }
4763
4764
4765 #if ZIPPY
4766     if (appData.zippyPlay && newGame &&
4767         gameMode != IcsObserving && gameMode != IcsIdle &&
4768         gameMode != IcsExamining)
4769       ZippyFirstBoard(moveNum, basetime, increment);
4770 #endif
4771
4772     /* Put the move on the move list, first converting
4773        to canonical algebraic form. */
4774     if (moveNum > 0) {
4775   if (appData.debugMode) {
4776     int f = forwardMostMove;
4777     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4778             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4779             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4780     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4781     fprintf(debugFP, "moveNum = %d\n", moveNum);
4782     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4783     setbuf(debugFP, NULL);
4784   }
4785         if (moveNum <= backwardMostMove) {
4786             /* We don't know what the board looked like before
4787                this move.  Punt. */
4788           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4789             strcat(parseList[moveNum - 1], " ");
4790             strcat(parseList[moveNum - 1], elapsed_time);
4791             moveList[moveNum - 1][0] = NULLCHAR;
4792         } else if (strcmp(move_str, "none") == 0) {
4793             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4794             /* Again, we don't know what the board looked like;
4795                this is really the start of the game. */
4796             parseList[moveNum - 1][0] = NULLCHAR;
4797             moveList[moveNum - 1][0] = NULLCHAR;
4798             backwardMostMove = moveNum;
4799             startedFromSetupPosition = TRUE;
4800             fromX = fromY = toX = toY = -1;
4801         } else {
4802           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4803           //                 So we parse the long-algebraic move string in stead of the SAN move
4804           int valid; char buf[MSG_SIZ], *prom;
4805
4806           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4807                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4808           // str looks something like "Q/a1-a2"; kill the slash
4809           if(str[1] == '/')
4810             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4811           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4812           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4813                 strcat(buf, prom); // long move lacks promo specification!
4814           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4815                 if(appData.debugMode)
4816                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4817                 safeStrCpy(move_str, buf, MSG_SIZ);
4818           }
4819           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4820                                 &fromX, &fromY, &toX, &toY, &promoChar)
4821                || ParseOneMove(buf, moveNum - 1, &moveType,
4822                                 &fromX, &fromY, &toX, &toY, &promoChar);
4823           // end of long SAN patch
4824           if (valid) {
4825             (void) CoordsToAlgebraic(boards[moveNum - 1],
4826                                      PosFlags(moveNum - 1),
4827                                      fromY, fromX, toY, toX, promoChar,
4828                                      parseList[moveNum-1]);
4829             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4830               case MT_NONE:
4831               case MT_STALEMATE:
4832               default:
4833                 break;
4834               case MT_CHECK:
4835                 if(!IS_SHOGI(gameInfo.variant))
4836                     strcat(parseList[moveNum - 1], "+");
4837                 break;
4838               case MT_CHECKMATE:
4839               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4840                 strcat(parseList[moveNum - 1], "#");
4841                 break;
4842             }
4843             strcat(parseList[moveNum - 1], " ");
4844             strcat(parseList[moveNum - 1], elapsed_time);
4845             /* currentMoveString is set as a side-effect of ParseOneMove */
4846             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4847             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4848             strcat(moveList[moveNum - 1], "\n");
4849
4850             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4851                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4852               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4853                 ChessSquare old, new = boards[moveNum][k][j];
4854                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4855                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4856                   if(old == new) continue;
4857                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4858                   else if(new == WhiteWazir || new == BlackWazir) {
4859                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4860                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4861                       else boards[moveNum][k][j] = old; // preserve type of Gold
4862                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4863                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4864               }
4865           } else {
4866             /* Move from ICS was illegal!?  Punt. */
4867             if (appData.debugMode) {
4868               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4869               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4870             }
4871             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4872             strcat(parseList[moveNum - 1], " ");
4873             strcat(parseList[moveNum - 1], elapsed_time);
4874             moveList[moveNum - 1][0] = NULLCHAR;
4875             fromX = fromY = toX = toY = -1;
4876           }
4877         }
4878   if (appData.debugMode) {
4879     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4880     setbuf(debugFP, NULL);
4881   }
4882
4883 #if ZIPPY
4884         /* Send move to chess program (BEFORE animating it). */
4885         if (appData.zippyPlay && !newGame && newMove &&
4886            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4887
4888             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4889                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4890                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4891                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4892                             move_str);
4893                     DisplayError(str, 0);
4894                 } else {
4895                     if (first.sendTime) {
4896                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4897                     }
4898                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4899                     if (firstMove && !bookHit) {
4900                         firstMove = FALSE;
4901                         if (first.useColors) {
4902                           SendToProgram(gameMode == IcsPlayingWhite ?
4903                                         "white\ngo\n" :
4904                                         "black\ngo\n", &first);
4905                         } else {
4906                           SendToProgram("go\n", &first);
4907                         }
4908                         first.maybeThinking = TRUE;
4909                     }
4910                 }
4911             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4912               if (moveList[moveNum - 1][0] == NULLCHAR) {
4913                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4914                 DisplayError(str, 0);
4915               } else {
4916                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4917                 SendMoveToProgram(moveNum - 1, &first);
4918               }
4919             }
4920         }
4921 #endif
4922     }
4923
4924     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4925         /* If move comes from a remote source, animate it.  If it
4926            isn't remote, it will have already been animated. */
4927         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4928             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4929         }
4930         if (!pausing && appData.highlightLastMove) {
4931             SetHighlights(fromX, fromY, toX, toY);
4932         }
4933     }
4934
4935     /* Start the clocks */
4936     whiteFlag = blackFlag = FALSE;
4937     appData.clockMode = !(basetime == 0 && increment == 0);
4938     if (ticking == 0) {
4939       ics_clock_paused = TRUE;
4940       StopClocks();
4941     } else if (ticking == 1) {
4942       ics_clock_paused = FALSE;
4943     }
4944     if (gameMode == IcsIdle ||
4945         relation == RELATION_OBSERVING_STATIC ||
4946         relation == RELATION_EXAMINING ||
4947         ics_clock_paused)
4948       DisplayBothClocks();
4949     else
4950       StartClocks();
4951
4952     /* Display opponents and material strengths */
4953     if (gameInfo.variant != VariantBughouse &&
4954         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4955         if (tinyLayout || smallLayout) {
4956             if(gameInfo.variant == VariantNormal)
4957               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4958                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4959                     basetime, increment);
4960             else
4961               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4962                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4963                     basetime, increment, (int) gameInfo.variant);
4964         } else {
4965             if(gameInfo.variant == VariantNormal)
4966               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4967                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4968                     basetime, increment);
4969             else
4970               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4971                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4972                     basetime, increment, VariantName(gameInfo.variant));
4973         }
4974         DisplayTitle(str);
4975   if (appData.debugMode) {
4976     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4977   }
4978     }
4979
4980
4981     /* Display the board */
4982     if (!pausing && !appData.noGUI) {
4983
4984       if (appData.premove)
4985           if (!gotPremove ||
4986              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4987              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4988               ClearPremoveHighlights();
4989
4990       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4991         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4992       DrawPosition(j, boards[currentMove]);
4993
4994       DisplayMove(moveNum - 1);
4995       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4996             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4997               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4998         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4999       }
5000     }
5001
5002     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5003 #if ZIPPY
5004     if(bookHit) { // [HGM] book: simulate book reply
5005         static char bookMove[MSG_SIZ]; // a bit generous?
5006
5007         programStats.nodes = programStats.depth = programStats.time =
5008         programStats.score = programStats.got_only_move = 0;
5009         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5010
5011         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5012         strcat(bookMove, bookHit);
5013         HandleMachineMove(bookMove, &first);
5014     }
5015 #endif
5016 }
5017
5018 void
5019 GetMoveListEvent ()
5020 {
5021     char buf[MSG_SIZ];
5022     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5023         ics_getting_history = H_REQUESTED;
5024         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5025         SendToICS(buf);
5026     }
5027 }
5028
5029 void
5030 SendToBoth (char *msg)
5031 {   // to make it easy to keep two engines in step in dual analysis
5032     SendToProgram(msg, &first);
5033     if(second.analyzing) SendToProgram(msg, &second);
5034 }
5035
5036 void
5037 AnalysisPeriodicEvent (int force)
5038 {
5039     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5040          && !force) || !appData.periodicUpdates)
5041       return;
5042
5043     /* Send . command to Crafty to collect stats */
5044     SendToBoth(".\n");
5045
5046     /* Don't send another until we get a response (this makes
5047        us stop sending to old Crafty's which don't understand
5048        the "." command (sending illegal cmds resets node count & time,
5049        which looks bad)) */
5050     programStats.ok_to_send = 0;
5051 }
5052
5053 void
5054 ics_update_width (int new_width)
5055 {
5056         ics_printf("set width %d\n", new_width);
5057 }
5058
5059 void
5060 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5061 {
5062     char buf[MSG_SIZ];
5063
5064     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5065         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5066             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5067             SendToProgram(buf, cps);
5068             return;
5069         }
5070         // null move in variant where engine does not understand it (for analysis purposes)
5071         SendBoard(cps, moveNum + 1); // send position after move in stead.
5072         return;
5073     }
5074     if (cps->useUsermove) {
5075       SendToProgram("usermove ", cps);
5076     }
5077     if (cps->useSAN) {
5078       char *space;
5079       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5080         int len = space - parseList[moveNum];
5081         memcpy(buf, parseList[moveNum], len);
5082         buf[len++] = '\n';
5083         buf[len] = NULLCHAR;
5084       } else {
5085         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5086       }
5087       SendToProgram(buf, cps);
5088     } else {
5089       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5090         AlphaRank(moveList[moveNum], 4);
5091         SendToProgram(moveList[moveNum], cps);
5092         AlphaRank(moveList[moveNum], 4); // and back
5093       } else
5094       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5095        * the engine. It would be nice to have a better way to identify castle
5096        * moves here. */
5097       if(appData.fischerCastling && 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 = appData.fischerCastling = 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       appData.fischerCastling = TRUE;
6016     default:
6017       break;
6018     case VariantShatranj:
6019       pieces = ShatranjArray;
6020       nrCastlingRights = 0;
6021       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6022       break;
6023     case VariantMakruk:
6024       pieces = makrukArray;
6025       nrCastlingRights = 0;
6026       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6027       break;
6028     case VariantASEAN:
6029       pieces = aseanArray;
6030       nrCastlingRights = 0;
6031       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6032       break;
6033     case VariantTwoKings:
6034       pieces = twoKingsArray;
6035       break;
6036     case VariantGrand:
6037       pieces = GrandArray;
6038       nrCastlingRights = 0;
6039       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6040       gameInfo.boardWidth = 10;
6041       gameInfo.boardHeight = 10;
6042       gameInfo.holdingsSize = 7;
6043       break;
6044     case VariantCapaRandom:
6045       shuffleOpenings = TRUE;
6046       appData.fischerCastling = TRUE;
6047     case VariantCapablanca:
6048       pieces = CapablancaArray;
6049       gameInfo.boardWidth = 10;
6050       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6051       break;
6052     case VariantGothic:
6053       pieces = GothicArray;
6054       gameInfo.boardWidth = 10;
6055       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6056       break;
6057     case VariantSChess:
6058       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6059       gameInfo.holdingsSize = 7;
6060       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6061       break;
6062     case VariantJanus:
6063       pieces = JanusArray;
6064       gameInfo.boardWidth = 10;
6065       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6066       nrCastlingRights = 6;
6067         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6068         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6069         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6070         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6071         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6072         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6073       break;
6074     case VariantFalcon:
6075       pieces = FalconArray;
6076       gameInfo.boardWidth = 10;
6077       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6078       break;
6079     case VariantXiangqi:
6080       pieces = XiangqiArray;
6081       gameInfo.boardWidth  = 9;
6082       gameInfo.boardHeight = 10;
6083       nrCastlingRights = 0;
6084       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6085       break;
6086     case VariantShogi:
6087       pieces = ShogiArray;
6088       gameInfo.boardWidth  = 9;
6089       gameInfo.boardHeight = 9;
6090       gameInfo.holdingsSize = 7;
6091       nrCastlingRights = 0;
6092       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6093       break;
6094     case VariantChu:
6095       pieces = ChuArray; pieceRows = 3;
6096       gameInfo.boardWidth  = 12;
6097       gameInfo.boardHeight = 12;
6098       nrCastlingRights = 0;
6099       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6100                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6101       break;
6102     case VariantCourier:
6103       pieces = CourierArray;
6104       gameInfo.boardWidth  = 12;
6105       nrCastlingRights = 0;
6106       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6107       break;
6108     case VariantKnightmate:
6109       pieces = KnightmateArray;
6110       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6111       break;
6112     case VariantSpartan:
6113       pieces = SpartanArray;
6114       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6115       break;
6116     case VariantLion:
6117       pieces = lionArray;
6118       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6119       break;
6120     case VariantChuChess:
6121       pieces = ChuChessArray;
6122       gameInfo.boardWidth = 10;
6123       gameInfo.boardHeight = 10;
6124       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6125       break;
6126     case VariantFairy:
6127       pieces = fairyArray;
6128       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6129       break;
6130     case VariantGreat:
6131       pieces = GreatArray;
6132       gameInfo.boardWidth = 10;
6133       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6134       gameInfo.holdingsSize = 8;
6135       break;
6136     case VariantSuper:
6137       pieces = FIDEArray;
6138       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6139       gameInfo.holdingsSize = 8;
6140       startedFromSetupPosition = TRUE;
6141       break;
6142     case VariantCrazyhouse:
6143     case VariantBughouse:
6144       pieces = FIDEArray;
6145       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6146       gameInfo.holdingsSize = 5;
6147       break;
6148     case VariantWildCastle:
6149       pieces = FIDEArray;
6150       /* !!?shuffle with kings guaranteed to be on d or e file */
6151       shuffleOpenings = 1;
6152       break;
6153     case VariantNoCastle:
6154       pieces = FIDEArray;
6155       nrCastlingRights = 0;
6156       /* !!?unconstrained back-rank shuffle */
6157       shuffleOpenings = 1;
6158       break;
6159     }
6160
6161     overrule = 0;
6162     if(appData.NrFiles >= 0) {
6163         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6164         gameInfo.boardWidth = appData.NrFiles;
6165     }
6166     if(appData.NrRanks >= 0) {
6167         gameInfo.boardHeight = appData.NrRanks;
6168     }
6169     if(appData.holdingsSize >= 0) {
6170         i = appData.holdingsSize;
6171         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6172         gameInfo.holdingsSize = i;
6173     }
6174     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6175     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6176         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6177
6178     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6179     if(pawnRow < 1) pawnRow = 1;
6180     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6181        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6182     if(gameInfo.variant == VariantChu) pawnRow = 3;
6183
6184     /* User pieceToChar list overrules defaults */
6185     if(appData.pieceToCharTable != NULL)
6186         SetCharTable(pieceToChar, appData.pieceToCharTable);
6187
6188     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6189
6190         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6191             s = (ChessSquare) 0; /* account holding counts in guard band */
6192         for( i=0; i<BOARD_HEIGHT; i++ )
6193             initialPosition[i][j] = s;
6194
6195         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6196         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6197         initialPosition[pawnRow][j] = WhitePawn;
6198         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6199         if(gameInfo.variant == VariantXiangqi) {
6200             if(j&1) {
6201                 initialPosition[pawnRow][j] =
6202                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6203                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6204                    initialPosition[2][j] = WhiteCannon;
6205                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6206                 }
6207             }
6208         }
6209         if(gameInfo.variant == VariantChu) {
6210              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6211                initialPosition[pawnRow+1][j] = WhiteCobra,
6212                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6213              for(i=1; i<pieceRows; i++) {
6214                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6215                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6216              }
6217         }
6218         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6219             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6220                initialPosition[0][j] = WhiteRook;
6221                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6222             }
6223         }
6224         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6225     }
6226     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6227     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6228
6229             j=BOARD_LEFT+1;
6230             initialPosition[1][j] = WhiteBishop;
6231             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6232             j=BOARD_RGHT-2;
6233             initialPosition[1][j] = WhiteRook;
6234             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6235     }
6236
6237     if( nrCastlingRights == -1) {
6238         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6239         /*       This sets default castling rights from none to normal corners   */
6240         /* Variants with other castling rights must set them themselves above    */
6241         nrCastlingRights = 6;
6242
6243         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6244         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6245         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6246         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6247         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6248         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6249      }
6250
6251      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6252      if(gameInfo.variant == VariantGreat) { // promotion commoners
6253         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6254         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6255         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6256         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6257      }
6258      if( gameInfo.variant == VariantSChess ) {
6259       initialPosition[1][0] = BlackMarshall;
6260       initialPosition[2][0] = BlackAngel;
6261       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6262       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6263       initialPosition[1][1] = initialPosition[2][1] =
6264       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6265      }
6266   if (appData.debugMode) {
6267     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6268   }
6269     if(shuffleOpenings) {
6270         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6271         startedFromSetupPosition = TRUE;
6272     }
6273     if(startedFromPositionFile) {
6274       /* [HGM] loadPos: use PositionFile for every new game */
6275       CopyBoard(initialPosition, filePosition);
6276       for(i=0; i<nrCastlingRights; i++)
6277           initialRights[i] = filePosition[CASTLING][i];
6278       startedFromSetupPosition = TRUE;
6279     }
6280
6281     CopyBoard(boards[0], initialPosition);
6282
6283     if(oldx != gameInfo.boardWidth ||
6284        oldy != gameInfo.boardHeight ||
6285        oldv != gameInfo.variant ||
6286        oldh != gameInfo.holdingsWidth
6287                                          )
6288             InitDrawingSizes(-2 ,0);
6289
6290     oldv = gameInfo.variant;
6291     if (redraw)
6292       DrawPosition(TRUE, boards[currentMove]);
6293 }
6294
6295 void
6296 SendBoard (ChessProgramState *cps, int moveNum)
6297 {
6298     char message[MSG_SIZ];
6299
6300     if (cps->useSetboard) {
6301       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6302       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6303       SendToProgram(message, cps);
6304       free(fen);
6305
6306     } else {
6307       ChessSquare *bp;
6308       int i, j, left=0, right=BOARD_WIDTH;
6309       /* Kludge to set black to move, avoiding the troublesome and now
6310        * deprecated "black" command.
6311        */
6312       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6313         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6314
6315       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6316
6317       SendToProgram("edit\n", cps);
6318       SendToProgram("#\n", cps);
6319       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6320         bp = &boards[moveNum][i][left];
6321         for (j = left; j < right; j++, bp++) {
6322           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6323           if ((int) *bp < (int) BlackPawn) {
6324             if(j == BOARD_RGHT+1)
6325                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6326             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6327             if(message[0] == '+' || message[0] == '~') {
6328               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6329                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6330                         AAA + j, ONE + i);
6331             }
6332             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6333                 message[1] = BOARD_RGHT   - 1 - j + '1';
6334                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6335             }
6336             SendToProgram(message, cps);
6337           }
6338         }
6339       }
6340
6341       SendToProgram("c\n", cps);
6342       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6343         bp = &boards[moveNum][i][left];
6344         for (j = left; j < right; j++, bp++) {
6345           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6346           if (((int) *bp != (int) EmptySquare)
6347               && ((int) *bp >= (int) BlackPawn)) {
6348             if(j == BOARD_LEFT-2)
6349                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6350             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6351                     AAA + j, ONE + i);
6352             if(message[0] == '+' || message[0] == '~') {
6353               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6354                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6355                         AAA + j, ONE + i);
6356             }
6357             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6358                 message[1] = BOARD_RGHT   - 1 - j + '1';
6359                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6360             }
6361             SendToProgram(message, cps);
6362           }
6363         }
6364       }
6365
6366       SendToProgram(".\n", cps);
6367     }
6368     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6369 }
6370
6371 char exclusionHeader[MSG_SIZ];
6372 int exCnt, excludePtr;
6373 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6374 static Exclusion excluTab[200];
6375 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6376
6377 static void
6378 WriteMap (int s)
6379 {
6380     int j;
6381     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6382     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6383 }
6384
6385 static void
6386 ClearMap ()
6387 {
6388     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6389     excludePtr = 24; exCnt = 0;
6390     WriteMap(0);
6391 }
6392
6393 static void
6394 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6395 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6396     char buf[2*MOVE_LEN], *p;
6397     Exclusion *e = excluTab;
6398     int i;
6399     for(i=0; i<exCnt; i++)
6400         if(e[i].ff == fromX && e[i].fr == fromY &&
6401            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6402     if(i == exCnt) { // was not in exclude list; add it
6403         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6404         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6405             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6406             return; // abort
6407         }
6408         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6409         excludePtr++; e[i].mark = excludePtr++;
6410         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6411         exCnt++;
6412     }
6413     exclusionHeader[e[i].mark] = state;
6414 }
6415
6416 static int
6417 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6418 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6419     char buf[MSG_SIZ];
6420     int j, k;
6421     ChessMove moveType;
6422     if((signed char)promoChar == -1) { // kludge to indicate best move
6423         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6424             return 1; // if unparsable, abort
6425     }
6426     // update exclusion map (resolving toggle by consulting existing state)
6427     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6428     j = k%8; k >>= 3;
6429     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6430     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6431          excludeMap[k] |=   1<<j;
6432     else excludeMap[k] &= ~(1<<j);
6433     // update header
6434     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6435     // inform engine
6436     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6437     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6438     SendToBoth(buf);
6439     return (state == '+');
6440 }
6441
6442 static void
6443 ExcludeClick (int index)
6444 {
6445     int i, j;
6446     Exclusion *e = excluTab;
6447     if(index < 25) { // none, best or tail clicked
6448         if(index < 13) { // none: include all
6449             WriteMap(0); // clear map
6450             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6451             SendToBoth("include all\n"); // and inform engine
6452         } else if(index > 18) { // tail
6453             if(exclusionHeader[19] == '-') { // tail was excluded
6454                 SendToBoth("include all\n");
6455                 WriteMap(0); // clear map completely
6456                 // now re-exclude selected moves
6457                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6458                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6459             } else { // tail was included or in mixed state
6460                 SendToBoth("exclude all\n");
6461                 WriteMap(0xFF); // fill map completely
6462                 // now re-include selected moves
6463                 j = 0; // count them
6464                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6465                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6466                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6467             }
6468         } else { // best
6469             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6470         }
6471     } else {
6472         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6473             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6474             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6475             break;
6476         }
6477     }
6478 }
6479
6480 ChessSquare
6481 DefaultPromoChoice (int white)
6482 {
6483     ChessSquare result;
6484     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6485        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6486         result = WhiteFerz; // no choice
6487     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6488         result= WhiteKing; // in Suicide Q is the last thing we want
6489     else if(gameInfo.variant == VariantSpartan)
6490         result = white ? WhiteQueen : WhiteAngel;
6491     else result = WhiteQueen;
6492     if(!white) result = WHITE_TO_BLACK result;
6493     return result;
6494 }
6495
6496 static int autoQueen; // [HGM] oneclick
6497
6498 int
6499 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6500 {
6501     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6502     /* [HGM] add Shogi promotions */
6503     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6504     ChessSquare piece, partner;
6505     ChessMove moveType;
6506     Boolean premove;
6507
6508     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6509     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6510
6511     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6512       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6513         return FALSE;
6514
6515     piece = boards[currentMove][fromY][fromX];
6516     if(gameInfo.variant == VariantChu) {
6517         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6518         promotionZoneSize = BOARD_HEIGHT/3;
6519         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6520     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6521         promotionZoneSize = BOARD_HEIGHT/3;
6522         highestPromotingPiece = (int)WhiteAlfil;
6523     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6524         promotionZoneSize = 3;
6525     }
6526
6527     // Treat Lance as Pawn when it is not representing Amazon or Lance
6528     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6529         if(piece == WhiteLance) piece = WhitePawn; else
6530         if(piece == BlackLance) piece = BlackPawn;
6531     }
6532
6533     // next weed out all moves that do not touch the promotion zone at all
6534     if((int)piece >= BlackPawn) {
6535         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6536              return FALSE;
6537         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6538         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6539     } else {
6540         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6541            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6542         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6543              return FALSE;
6544     }
6545
6546     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6547
6548     // weed out mandatory Shogi promotions
6549     if(gameInfo.variant == VariantShogi) {
6550         if(piece >= BlackPawn) {
6551             if(toY == 0 && piece == BlackPawn ||
6552                toY == 0 && piece == BlackQueen ||
6553                toY <= 1 && piece == BlackKnight) {
6554                 *promoChoice = '+';
6555                 return FALSE;
6556             }
6557         } else {
6558             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6559                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6560                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6561                 *promoChoice = '+';
6562                 return FALSE;
6563             }
6564         }
6565     }
6566
6567     // weed out obviously illegal Pawn moves
6568     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6569         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6570         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6571         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6572         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6573         // note we are not allowed to test for valid (non-)capture, due to premove
6574     }
6575
6576     // we either have a choice what to promote to, or (in Shogi) whether to promote
6577     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6578        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6579         ChessSquare p=BlackFerz;  // no choice
6580         while(p < EmptySquare) {  //but make sure we use piece that exists
6581             *promoChoice = PieceToChar(p++);
6582             if(*promoChoice != '.') break;
6583         }
6584         return FALSE;
6585     }
6586     // no sense asking what we must promote to if it is going to explode...
6587     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6588         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6589         return FALSE;
6590     }
6591     // give caller the default choice even if we will not make it
6592     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6593     partner = piece; // pieces can promote if the pieceToCharTable says so
6594     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6595     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6596     if(        sweepSelect && gameInfo.variant != VariantGreat
6597                            && gameInfo.variant != VariantGrand
6598                            && gameInfo.variant != VariantSuper) return FALSE;
6599     if(autoQueen) return FALSE; // predetermined
6600
6601     // suppress promotion popup on illegal moves that are not premoves
6602     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6603               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6604     if(appData.testLegality && !premove) {
6605         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6606                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6607         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6608         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6609             return FALSE;
6610     }
6611
6612     return TRUE;
6613 }
6614
6615 int
6616 InPalace (int row, int column)
6617 {   /* [HGM] for Xiangqi */
6618     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6619          column < (BOARD_WIDTH + 4)/2 &&
6620          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6621     return FALSE;
6622 }
6623
6624 int
6625 PieceForSquare (int x, int y)
6626 {
6627   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6628      return -1;
6629   else
6630      return boards[currentMove][y][x];
6631 }
6632
6633 int
6634 OKToStartUserMove (int x, int y)
6635 {
6636     ChessSquare from_piece;
6637     int white_piece;
6638
6639     if (matchMode) return FALSE;
6640     if (gameMode == EditPosition) return TRUE;
6641
6642     if (x >= 0 && y >= 0)
6643       from_piece = boards[currentMove][y][x];
6644     else
6645       from_piece = EmptySquare;
6646
6647     if (from_piece == EmptySquare) return FALSE;
6648
6649     white_piece = (int)from_piece >= (int)WhitePawn &&
6650       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6651
6652     switch (gameMode) {
6653       case AnalyzeFile:
6654       case TwoMachinesPlay:
6655       case EndOfGame:
6656         return FALSE;
6657
6658       case IcsObserving:
6659       case IcsIdle:
6660         return FALSE;
6661
6662       case MachinePlaysWhite:
6663       case IcsPlayingBlack:
6664         if (appData.zippyPlay) return FALSE;
6665         if (white_piece) {
6666             DisplayMoveError(_("You are playing Black"));
6667             return FALSE;
6668         }
6669         break;
6670
6671       case MachinePlaysBlack:
6672       case IcsPlayingWhite:
6673         if (appData.zippyPlay) return FALSE;
6674         if (!white_piece) {
6675             DisplayMoveError(_("You are playing White"));
6676             return FALSE;
6677         }
6678         break;
6679
6680       case PlayFromGameFile:
6681             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6682       case EditGame:
6683         if (!white_piece && WhiteOnMove(currentMove)) {
6684             DisplayMoveError(_("It is White's turn"));
6685             return FALSE;
6686         }
6687         if (white_piece && !WhiteOnMove(currentMove)) {
6688             DisplayMoveError(_("It is Black's turn"));
6689             return FALSE;
6690         }
6691         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6692             /* Editing correspondence game history */
6693             /* Could disallow this or prompt for confirmation */
6694             cmailOldMove = -1;
6695         }
6696         break;
6697
6698       case BeginningOfGame:
6699         if (appData.icsActive) return FALSE;
6700         if (!appData.noChessProgram) {
6701             if (!white_piece) {
6702                 DisplayMoveError(_("You are playing White"));
6703                 return FALSE;
6704             }
6705         }
6706         break;
6707
6708       case Training:
6709         if (!white_piece && WhiteOnMove(currentMove)) {
6710             DisplayMoveError(_("It is White's turn"));
6711             return FALSE;
6712         }
6713         if (white_piece && !WhiteOnMove(currentMove)) {
6714             DisplayMoveError(_("It is Black's turn"));
6715             return FALSE;
6716         }
6717         break;
6718
6719       default:
6720       case IcsExamining:
6721         break;
6722     }
6723     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6724         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6725         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6726         && gameMode != AnalyzeFile && gameMode != Training) {
6727         DisplayMoveError(_("Displayed position is not current"));
6728         return FALSE;
6729     }
6730     return TRUE;
6731 }
6732
6733 Boolean
6734 OnlyMove (int *x, int *y, Boolean captures)
6735 {
6736     DisambiguateClosure cl;
6737     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6738     switch(gameMode) {
6739       case MachinePlaysBlack:
6740       case IcsPlayingWhite:
6741       case BeginningOfGame:
6742         if(!WhiteOnMove(currentMove)) return FALSE;
6743         break;
6744       case MachinePlaysWhite:
6745       case IcsPlayingBlack:
6746         if(WhiteOnMove(currentMove)) return FALSE;
6747         break;
6748       case EditGame:
6749         break;
6750       default:
6751         return FALSE;
6752     }
6753     cl.pieceIn = EmptySquare;
6754     cl.rfIn = *y;
6755     cl.ffIn = *x;
6756     cl.rtIn = -1;
6757     cl.ftIn = -1;
6758     cl.promoCharIn = NULLCHAR;
6759     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6760     if( cl.kind == NormalMove ||
6761         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6762         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6763         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6764       fromX = cl.ff;
6765       fromY = cl.rf;
6766       *x = cl.ft;
6767       *y = cl.rt;
6768       return TRUE;
6769     }
6770     if(cl.kind != ImpossibleMove) return FALSE;
6771     cl.pieceIn = EmptySquare;
6772     cl.rfIn = -1;
6773     cl.ffIn = -1;
6774     cl.rtIn = *y;
6775     cl.ftIn = *x;
6776     cl.promoCharIn = NULLCHAR;
6777     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6778     if( cl.kind == NormalMove ||
6779         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6780         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6781         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6782       fromX = cl.ff;
6783       fromY = cl.rf;
6784       *x = cl.ft;
6785       *y = cl.rt;
6786       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6787       return TRUE;
6788     }
6789     return FALSE;
6790 }
6791
6792 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6793 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6794 int lastLoadGameUseList = FALSE;
6795 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6796 ChessMove lastLoadGameStart = EndOfFile;
6797 int doubleClick;
6798
6799 void
6800 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6801 {
6802     ChessMove moveType;
6803     ChessSquare pup;
6804     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6805
6806     /* Check if the user is playing in turn.  This is complicated because we
6807        let the user "pick up" a piece before it is his turn.  So the piece he
6808        tried to pick up may have been captured by the time he puts it down!
6809        Therefore we use the color the user is supposed to be playing in this
6810        test, not the color of the piece that is currently on the starting
6811        square---except in EditGame mode, where the user is playing both
6812        sides; fortunately there the capture race can't happen.  (It can
6813        now happen in IcsExamining mode, but that's just too bad.  The user
6814        will get a somewhat confusing message in that case.)
6815        */
6816
6817     switch (gameMode) {
6818       case AnalyzeFile:
6819       case TwoMachinesPlay:
6820       case EndOfGame:
6821       case IcsObserving:
6822       case IcsIdle:
6823         /* We switched into a game mode where moves are not accepted,
6824            perhaps while the mouse button was down. */
6825         return;
6826
6827       case MachinePlaysWhite:
6828         /* User is moving for Black */
6829         if (WhiteOnMove(currentMove)) {
6830             DisplayMoveError(_("It is White's turn"));
6831             return;
6832         }
6833         break;
6834
6835       case MachinePlaysBlack:
6836         /* User is moving for White */
6837         if (!WhiteOnMove(currentMove)) {
6838             DisplayMoveError(_("It is Black's turn"));
6839             return;
6840         }
6841         break;
6842
6843       case PlayFromGameFile:
6844             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6845       case EditGame:
6846       case IcsExamining:
6847       case BeginningOfGame:
6848       case AnalyzeMode:
6849       case Training:
6850         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6851         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6852             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6853             /* User is moving for Black */
6854             if (WhiteOnMove(currentMove)) {
6855                 DisplayMoveError(_("It is White's turn"));
6856                 return;
6857             }
6858         } else {
6859             /* User is moving for White */
6860             if (!WhiteOnMove(currentMove)) {
6861                 DisplayMoveError(_("It is Black's turn"));
6862                 return;
6863             }
6864         }
6865         break;
6866
6867       case IcsPlayingBlack:
6868         /* User is moving for Black */
6869         if (WhiteOnMove(currentMove)) {
6870             if (!appData.premove) {
6871                 DisplayMoveError(_("It is White's turn"));
6872             } else if (toX >= 0 && toY >= 0) {
6873                 premoveToX = toX;
6874                 premoveToY = toY;
6875                 premoveFromX = fromX;
6876                 premoveFromY = fromY;
6877                 premovePromoChar = promoChar;
6878                 gotPremove = 1;
6879                 if (appData.debugMode)
6880                     fprintf(debugFP, "Got premove: fromX %d,"
6881                             "fromY %d, toX %d, toY %d\n",
6882                             fromX, fromY, toX, toY);
6883             }
6884             return;
6885         }
6886         break;
6887
6888       case IcsPlayingWhite:
6889         /* User is moving for White */
6890         if (!WhiteOnMove(currentMove)) {
6891             if (!appData.premove) {
6892                 DisplayMoveError(_("It is Black's turn"));
6893             } else if (toX >= 0 && toY >= 0) {
6894                 premoveToX = toX;
6895                 premoveToY = toY;
6896                 premoveFromX = fromX;
6897                 premoveFromY = fromY;
6898                 premovePromoChar = promoChar;
6899                 gotPremove = 1;
6900                 if (appData.debugMode)
6901                     fprintf(debugFP, "Got premove: fromX %d,"
6902                             "fromY %d, toX %d, toY %d\n",
6903                             fromX, fromY, toX, toY);
6904             }
6905             return;
6906         }
6907         break;
6908
6909       default:
6910         break;
6911
6912       case EditPosition:
6913         /* EditPosition, empty square, or different color piece;
6914            click-click move is possible */
6915         if (toX == -2 || toY == -2) {
6916             boards[0][fromY][fromX] = EmptySquare;
6917             DrawPosition(FALSE, boards[currentMove]);
6918             return;
6919         } else if (toX >= 0 && toY >= 0) {
6920             boards[0][toY][toX] = boards[0][fromY][fromX];
6921             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6922                 if(boards[0][fromY][0] != EmptySquare) {
6923                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6924                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6925                 }
6926             } else
6927             if(fromX == BOARD_RGHT+1) {
6928                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6929                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6930                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6931                 }
6932             } else
6933             boards[0][fromY][fromX] = gatingPiece;
6934             DrawPosition(FALSE, boards[currentMove]);
6935             return;
6936         }
6937         return;
6938     }
6939
6940     if(toX < 0 || toY < 0) return;
6941     pup = boards[currentMove][toY][toX];
6942
6943     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6944     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6945          if( pup != EmptySquare ) return;
6946          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6947            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6948                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6949            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6950            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6951            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6952            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6953          fromY = DROP_RANK;
6954     }
6955
6956     /* [HGM] always test for legality, to get promotion info */
6957     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6958                                          fromY, fromX, toY, toX, promoChar);
6959
6960     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6961
6962     /* [HGM] but possibly ignore an IllegalMove result */
6963     if (appData.testLegality) {
6964         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6965             DisplayMoveError(_("Illegal move"));
6966             return;
6967         }
6968     }
6969
6970     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6971         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6972              ClearPremoveHighlights(); // was included
6973         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6974         return;
6975     }
6976
6977     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6978 }
6979
6980 /* Common tail of UserMoveEvent and DropMenuEvent */
6981 int
6982 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6983 {
6984     char *bookHit = 0;
6985
6986     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6987         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6988         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6989         if(WhiteOnMove(currentMove)) {
6990             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6991         } else {
6992             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6993         }
6994     }
6995
6996     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6997        move type in caller when we know the move is a legal promotion */
6998     if(moveType == NormalMove && promoChar)
6999         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7000
7001     /* [HGM] <popupFix> The following if has been moved here from
7002        UserMoveEvent(). Because it seemed to belong here (why not allow
7003        piece drops in training games?), and because it can only be
7004        performed after it is known to what we promote. */
7005     if (gameMode == Training) {
7006       /* compare the move played on the board to the next move in the
7007        * game. If they match, display the move and the opponent's response.
7008        * If they don't match, display an error message.
7009        */
7010       int saveAnimate;
7011       Board testBoard;
7012       CopyBoard(testBoard, boards[currentMove]);
7013       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7014
7015       if (CompareBoards(testBoard, boards[currentMove+1])) {
7016         ForwardInner(currentMove+1);
7017
7018         /* Autoplay the opponent's response.
7019          * if appData.animate was TRUE when Training mode was entered,
7020          * the response will be animated.
7021          */
7022         saveAnimate = appData.animate;
7023         appData.animate = animateTraining;
7024         ForwardInner(currentMove+1);
7025         appData.animate = saveAnimate;
7026
7027         /* check for the end of the game */
7028         if (currentMove >= forwardMostMove) {
7029           gameMode = PlayFromGameFile;
7030           ModeHighlight();
7031           SetTrainingModeOff();
7032           DisplayInformation(_("End of game"));
7033         }
7034       } else {
7035         DisplayError(_("Incorrect move"), 0);
7036       }
7037       return 1;
7038     }
7039
7040   /* Ok, now we know that the move is good, so we can kill
7041      the previous line in Analysis Mode */
7042   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7043                                 && currentMove < forwardMostMove) {
7044     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7045     else forwardMostMove = currentMove;
7046   }
7047
7048   ClearMap();
7049
7050   /* If we need the chess program but it's dead, restart it */
7051   ResurrectChessProgram();
7052
7053   /* A user move restarts a paused game*/
7054   if (pausing)
7055     PauseEvent();
7056
7057   thinkOutput[0] = NULLCHAR;
7058
7059   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7060
7061   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7062     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7063     return 1;
7064   }
7065
7066   if (gameMode == BeginningOfGame) {
7067     if (appData.noChessProgram) {
7068       gameMode = EditGame;
7069       SetGameInfo();
7070     } else {
7071       char buf[MSG_SIZ];
7072       gameMode = MachinePlaysBlack;
7073       StartClocks();
7074       SetGameInfo();
7075       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7076       DisplayTitle(buf);
7077       if (first.sendName) {
7078         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7079         SendToProgram(buf, &first);
7080       }
7081       StartClocks();
7082     }
7083     ModeHighlight();
7084   }
7085
7086   /* Relay move to ICS or chess engine */
7087   if (appData.icsActive) {
7088     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7089         gameMode == IcsExamining) {
7090       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7091         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7092         SendToICS("draw ");
7093         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7094       }
7095       // also send plain move, in case ICS does not understand atomic claims
7096       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7097       ics_user_moved = 1;
7098     }
7099   } else {
7100     if (first.sendTime && (gameMode == BeginningOfGame ||
7101                            gameMode == MachinePlaysWhite ||
7102                            gameMode == MachinePlaysBlack)) {
7103       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7104     }
7105     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7106          // [HGM] book: if program might be playing, let it use book
7107         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7108         first.maybeThinking = TRUE;
7109     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7110         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7111         SendBoard(&first, currentMove+1);
7112         if(second.analyzing) {
7113             if(!second.useSetboard) SendToProgram("undo\n", &second);
7114             SendBoard(&second, currentMove+1);
7115         }
7116     } else {
7117         SendMoveToProgram(forwardMostMove-1, &first);
7118         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7119     }
7120     if (currentMove == cmailOldMove + 1) {
7121       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7122     }
7123   }
7124
7125   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7126
7127   switch (gameMode) {
7128   case EditGame:
7129     if(appData.testLegality)
7130     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7131     case MT_NONE:
7132     case MT_CHECK:
7133       break;
7134     case MT_CHECKMATE:
7135     case MT_STAINMATE:
7136       if (WhiteOnMove(currentMove)) {
7137         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7138       } else {
7139         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7140       }
7141       break;
7142     case MT_STALEMATE:
7143       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7144       break;
7145     }
7146     break;
7147
7148   case MachinePlaysBlack:
7149   case MachinePlaysWhite:
7150     /* disable certain menu options while machine is thinking */
7151     SetMachineThinkingEnables();
7152     break;
7153
7154   default:
7155     break;
7156   }
7157
7158   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7159   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7160
7161   if(bookHit) { // [HGM] book: simulate book reply
7162         static char bookMove[MSG_SIZ]; // a bit generous?
7163
7164         programStats.nodes = programStats.depth = programStats.time =
7165         programStats.score = programStats.got_only_move = 0;
7166         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7167
7168         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7169         strcat(bookMove, bookHit);
7170         HandleMachineMove(bookMove, &first);
7171   }
7172   return 1;
7173 }
7174
7175 void
7176 MarkByFEN(char *fen)
7177 {
7178         int r, f;
7179         if(!appData.markers || !appData.highlightDragging) return;
7180         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7181         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7182         while(*fen) {
7183             int s = 0;
7184             marker[r][f] = 0;
7185             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7186             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7187             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7188             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7189             if(*fen == 'T') marker[r][f++] = 0; else
7190             if(*fen == 'Y') marker[r][f++] = 1; else
7191             if(*fen == 'G') marker[r][f++] = 3; else
7192             if(*fen == 'B') marker[r][f++] = 4; else
7193             if(*fen == 'C') marker[r][f++] = 5; else
7194             if(*fen == 'M') marker[r][f++] = 6; else
7195             if(*fen == 'W') marker[r][f++] = 7; else
7196             if(*fen == 'D') marker[r][f++] = 8; else
7197             if(*fen == 'R') marker[r][f++] = 2; else {
7198                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7199               f += s; fen -= s>0;
7200             }
7201             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7202             if(r < 0) break;
7203             fen++;
7204         }
7205         DrawPosition(TRUE, NULL);
7206 }
7207
7208 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7209
7210 void
7211 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7212 {
7213     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7214     Markers *m = (Markers *) closure;
7215     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7216         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7217                          || kind == WhiteCapturesEnPassant
7218                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7219     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7220 }
7221
7222 static int hoverSavedValid;
7223
7224 void
7225 MarkTargetSquares (int clear)
7226 {
7227   int x, y, sum=0;
7228   if(clear) { // no reason to ever suppress clearing
7229     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7230     hoverSavedValid = 0;
7231     if(!sum) return; // nothing was cleared,no redraw needed
7232   } else {
7233     int capt = 0;
7234     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7235        !appData.testLegality || gameMode == EditPosition) return;
7236     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7237     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7238       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7239       if(capt)
7240       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7241     }
7242   }
7243   DrawPosition(FALSE, NULL);
7244 }
7245
7246 int
7247 Explode (Board board, int fromX, int fromY, int toX, int toY)
7248 {
7249     if(gameInfo.variant == VariantAtomic &&
7250        (board[toY][toX] != EmptySquare ||                     // capture?
7251         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7252                          board[fromY][fromX] == BlackPawn   )
7253       )) {
7254         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7255         return TRUE;
7256     }
7257     return FALSE;
7258 }
7259
7260 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7261
7262 int
7263 CanPromote (ChessSquare piece, int y)
7264 {
7265         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7266         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7267         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7268         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7269            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7270            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7271          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7272         return (piece == BlackPawn && y <= zone ||
7273                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7274                 piece == BlackLance && y == 1 ||
7275                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7276 }
7277
7278 void
7279 HoverEvent (int xPix, int yPix, int x, int y)
7280 {
7281         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7282         int r, f;
7283         if(!first.highlight) return;
7284         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7285         if(x == oldX && y == oldY) return; // only do something if we enter new square
7286         oldFromX = fromX; oldFromY = fromY;
7287         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7288           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7289             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7290           hoverSavedValid = 1;
7291         } else if(oldX != x || oldY != y) {
7292           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7293           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7294           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7295             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7296           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7297             char buf[MSG_SIZ];
7298             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7299             SendToProgram(buf, &first);
7300           }
7301           oldX = x; oldY = y;
7302 //        SetHighlights(fromX, fromY, x, y);
7303         }
7304 }
7305
7306 void ReportClick(char *action, int x, int y)
7307 {
7308         char buf[MSG_SIZ]; // Inform engine of what user does
7309         int r, f;
7310         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7311           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7312         if(!first.highlight || gameMode == EditPosition) return;
7313         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7314         SendToProgram(buf, &first);
7315 }
7316
7317 void
7318 LeftClick (ClickType clickType, int xPix, int yPix)
7319 {
7320     int x, y;
7321     Boolean saveAnimate;
7322     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7323     char promoChoice = NULLCHAR;
7324     ChessSquare piece;
7325     static TimeMark lastClickTime, prevClickTime;
7326
7327     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7328
7329     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7330
7331     if (clickType == Press) ErrorPopDown();
7332     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7333
7334     x = EventToSquare(xPix, BOARD_WIDTH);
7335     y = EventToSquare(yPix, BOARD_HEIGHT);
7336     if (!flipView && y >= 0) {
7337         y = BOARD_HEIGHT - 1 - y;
7338     }
7339     if (flipView && x >= 0) {
7340         x = BOARD_WIDTH - 1 - x;
7341     }
7342
7343     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7344         defaultPromoChoice = promoSweep;
7345         promoSweep = EmptySquare;   // terminate sweep
7346         promoDefaultAltered = TRUE;
7347         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7348     }
7349
7350     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7351         if(clickType == Release) return; // ignore upclick of click-click destination
7352         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7353         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7354         if(gameInfo.holdingsWidth &&
7355                 (WhiteOnMove(currentMove)
7356                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7357                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7358             // click in right holdings, for determining promotion piece
7359             ChessSquare p = boards[currentMove][y][x];
7360             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7361             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7362             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7363                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7364                 fromX = fromY = -1;
7365                 return;
7366             }
7367         }
7368         DrawPosition(FALSE, boards[currentMove]);
7369         return;
7370     }
7371
7372     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7373     if(clickType == Press
7374             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7375               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7376               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7377         return;
7378
7379     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7380         // could be static click on premove from-square: abort premove
7381         gotPremove = 0;
7382         ClearPremoveHighlights();
7383     }
7384
7385     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7386         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7387
7388     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7389         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7390                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7391         defaultPromoChoice = DefaultPromoChoice(side);
7392     }
7393
7394     autoQueen = appData.alwaysPromoteToQueen;
7395
7396     if (fromX == -1) {
7397       int originalY = y;
7398       gatingPiece = EmptySquare;
7399       if (clickType != Press) {
7400         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7401             DragPieceEnd(xPix, yPix); dragging = 0;
7402             DrawPosition(FALSE, NULL);
7403         }
7404         return;
7405       }
7406       doubleClick = FALSE;
7407       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7408         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7409       }
7410       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7411       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7412          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7413          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7414             /* First square */
7415             if (OKToStartUserMove(fromX, fromY)) {
7416                 second = 0;
7417                 ReportClick("lift", x, y);
7418                 MarkTargetSquares(0);
7419                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7420                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7421                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7422                     promoSweep = defaultPromoChoice;
7423                     selectFlag = 0; lastX = xPix; lastY = yPix;
7424                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7425                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7426                 }
7427                 if (appData.highlightDragging) {
7428                     SetHighlights(fromX, fromY, -1, -1);
7429                 } else {
7430                     ClearHighlights();
7431                 }
7432             } else fromX = fromY = -1;
7433             return;
7434         }
7435     }
7436
7437     /* fromX != -1 */
7438     if (clickType == Press && gameMode != EditPosition) {
7439         ChessSquare fromP;
7440         ChessSquare toP;
7441         int frc;
7442
7443         // ignore off-board to clicks
7444         if(y < 0 || x < 0) return;
7445
7446         /* Check if clicking again on the same color piece */
7447         fromP = boards[currentMove][fromY][fromX];
7448         toP = boards[currentMove][y][x];
7449         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7450         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7451            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7452              WhitePawn <= toP && toP <= WhiteKing &&
7453              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7454              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7455             (BlackPawn <= fromP && fromP <= BlackKing &&
7456              BlackPawn <= toP && toP <= BlackKing &&
7457              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7458              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7459             /* Clicked again on same color piece -- changed his mind */
7460             second = (x == fromX && y == fromY);
7461             killX = killY = -1;
7462             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7463                 second = FALSE; // first double-click rather than scond click
7464                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7465             }
7466             promoDefaultAltered = FALSE;
7467             MarkTargetSquares(1);
7468            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7469             if (appData.highlightDragging) {
7470                 SetHighlights(x, y, -1, -1);
7471             } else {
7472                 ClearHighlights();
7473             }
7474             if (OKToStartUserMove(x, y)) {
7475                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7476                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7477                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7478                  gatingPiece = boards[currentMove][fromY][fromX];
7479                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7480                 fromX = x;
7481                 fromY = y; dragging = 1;
7482                 ReportClick("lift", x, y);
7483                 MarkTargetSquares(0);
7484                 DragPieceBegin(xPix, yPix, FALSE);
7485                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7486                     promoSweep = defaultPromoChoice;
7487                     selectFlag = 0; lastX = xPix; lastY = yPix;
7488                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7489                 }
7490             }
7491            }
7492            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7493            second = FALSE;
7494         }
7495         // ignore clicks on holdings
7496         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7497     }
7498
7499     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7500         DragPieceEnd(xPix, yPix); dragging = 0;
7501         if(clearFlag) {
7502             // a deferred attempt to click-click move an empty square on top of a piece
7503             boards[currentMove][y][x] = EmptySquare;
7504             ClearHighlights();
7505             DrawPosition(FALSE, boards[currentMove]);
7506             fromX = fromY = -1; clearFlag = 0;
7507             return;
7508         }
7509         if (appData.animateDragging) {
7510             /* Undo animation damage if any */
7511             DrawPosition(FALSE, NULL);
7512         }
7513         if (second || sweepSelecting) {
7514             /* Second up/down in same square; just abort move */
7515             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7516             second = sweepSelecting = 0;
7517             fromX = fromY = -1;
7518             gatingPiece = EmptySquare;
7519             MarkTargetSquares(1);
7520             ClearHighlights();
7521             gotPremove = 0;
7522             ClearPremoveHighlights();
7523         } else {
7524             /* First upclick in same square; start click-click mode */
7525             SetHighlights(x, y, -1, -1);
7526         }
7527         return;
7528     }
7529
7530     clearFlag = 0;
7531
7532     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7533         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7534         DisplayMessage(_("only marked squares are legal"),"");
7535         DrawPosition(TRUE, NULL);
7536         return; // ignore to-click
7537     }
7538
7539     /* we now have a different from- and (possibly off-board) to-square */
7540     /* Completed move */
7541     if(!sweepSelecting) {
7542         toX = x;
7543         toY = y;
7544     }
7545
7546     piece = boards[currentMove][fromY][fromX];
7547
7548     saveAnimate = appData.animate;
7549     if (clickType == Press) {
7550         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7551         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7552             // must be Edit Position mode with empty-square selected
7553             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7554             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7555             return;
7556         }
7557         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7558             return;
7559         }
7560         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7561             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7562         } else
7563         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7564         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7565           if(appData.sweepSelect) {
7566             promoSweep = defaultPromoChoice;
7567             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7568             selectFlag = 0; lastX = xPix; lastY = yPix;
7569             Sweep(0); // Pawn that is going to promote: preview promotion piece
7570             sweepSelecting = 1;
7571             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7572             MarkTargetSquares(1);
7573           }
7574           return; // promo popup appears on up-click
7575         }
7576         /* Finish clickclick move */
7577         if (appData.animate || appData.highlightLastMove) {
7578             SetHighlights(fromX, fromY, toX, toY);
7579         } else {
7580             ClearHighlights();
7581         }
7582     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7583         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7584         if (appData.animate || appData.highlightLastMove) {
7585             SetHighlights(fromX, fromY, toX, toY);
7586         } else {
7587             ClearHighlights();
7588         }
7589     } else {
7590 #if 0
7591 // [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
7592         /* Finish drag move */
7593         if (appData.highlightLastMove) {
7594             SetHighlights(fromX, fromY, toX, toY);
7595         } else {
7596             ClearHighlights();
7597         }
7598 #endif
7599         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7600         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7601           dragging *= 2;            // flag button-less dragging if we are dragging
7602           MarkTargetSquares(1);
7603           if(x == killX && y == killY) killX = killY = -1; else {
7604             killX = x; killY = y;     //remeber this square as intermediate
7605             ReportClick("put", x, y); // and inform engine
7606             ReportClick("lift", x, y);
7607             MarkTargetSquares(0);
7608             return;
7609           }
7610         }
7611         DragPieceEnd(xPix, yPix); dragging = 0;
7612         /* Don't animate move and drag both */
7613         appData.animate = FALSE;
7614     }
7615
7616     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7617     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7618         ChessSquare piece = boards[currentMove][fromY][fromX];
7619         if(gameMode == EditPosition && piece != EmptySquare &&
7620            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7621             int n;
7622
7623             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7624                 n = PieceToNumber(piece - (int)BlackPawn);
7625                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7626                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7627                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7628             } else
7629             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7630                 n = PieceToNumber(piece);
7631                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7632                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7633                 boards[currentMove][n][BOARD_WIDTH-2]++;
7634             }
7635             boards[currentMove][fromY][fromX] = EmptySquare;
7636         }
7637         ClearHighlights();
7638         fromX = fromY = -1;
7639         MarkTargetSquares(1);
7640         DrawPosition(TRUE, boards[currentMove]);
7641         return;
7642     }
7643
7644     // off-board moves should not be highlighted
7645     if(x < 0 || y < 0) ClearHighlights();
7646     else ReportClick("put", x, y);
7647
7648     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7649
7650     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7651         SetHighlights(fromX, fromY, toX, toY);
7652         MarkTargetSquares(1);
7653         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7654             // [HGM] super: promotion to captured piece selected from holdings
7655             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7656             promotionChoice = TRUE;
7657             // kludge follows to temporarily execute move on display, without promoting yet
7658             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7659             boards[currentMove][toY][toX] = p;
7660             DrawPosition(FALSE, boards[currentMove]);
7661             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7662             boards[currentMove][toY][toX] = q;
7663             DisplayMessage("Click in holdings to choose piece", "");
7664             return;
7665         }
7666         PromotionPopUp(promoChoice);
7667     } else {
7668         int oldMove = currentMove;
7669         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7670         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7671         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7672         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7673            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7674             DrawPosition(TRUE, boards[currentMove]);
7675         MarkTargetSquares(1);
7676         fromX = fromY = -1;
7677     }
7678     appData.animate = saveAnimate;
7679     if (appData.animate || appData.animateDragging) {
7680         /* Undo animation damage if needed */
7681         DrawPosition(FALSE, NULL);
7682     }
7683 }
7684
7685 int
7686 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7687 {   // front-end-free part taken out of PieceMenuPopup
7688     int whichMenu; int xSqr, ySqr;
7689
7690     if(seekGraphUp) { // [HGM] seekgraph
7691         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7692         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7693         return -2;
7694     }
7695
7696     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7697          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7698         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7699         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7700         if(action == Press)   {
7701             originalFlip = flipView;
7702             flipView = !flipView; // temporarily flip board to see game from partners perspective
7703             DrawPosition(TRUE, partnerBoard);
7704             DisplayMessage(partnerStatus, "");
7705             partnerUp = TRUE;
7706         } else if(action == Release) {
7707             flipView = originalFlip;
7708             DrawPosition(TRUE, boards[currentMove]);
7709             partnerUp = FALSE;
7710         }
7711         return -2;
7712     }
7713
7714     xSqr = EventToSquare(x, BOARD_WIDTH);
7715     ySqr = EventToSquare(y, BOARD_HEIGHT);
7716     if (action == Release) {
7717         if(pieceSweep != EmptySquare) {
7718             EditPositionMenuEvent(pieceSweep, toX, toY);
7719             pieceSweep = EmptySquare;
7720         } else UnLoadPV(); // [HGM] pv
7721     }
7722     if (action != Press) return -2; // return code to be ignored
7723     switch (gameMode) {
7724       case IcsExamining:
7725         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7726       case EditPosition:
7727         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7728         if (xSqr < 0 || ySqr < 0) return -1;
7729         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7730         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7731         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7732         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7733         NextPiece(0);
7734         return 2; // grab
7735       case IcsObserving:
7736         if(!appData.icsEngineAnalyze) return -1;
7737       case IcsPlayingWhite:
7738       case IcsPlayingBlack:
7739         if(!appData.zippyPlay) goto noZip;
7740       case AnalyzeMode:
7741       case AnalyzeFile:
7742       case MachinePlaysWhite:
7743       case MachinePlaysBlack:
7744       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7745         if (!appData.dropMenu) {
7746           LoadPV(x, y);
7747           return 2; // flag front-end to grab mouse events
7748         }
7749         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7750            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7751       case EditGame:
7752       noZip:
7753         if (xSqr < 0 || ySqr < 0) return -1;
7754         if (!appData.dropMenu || appData.testLegality &&
7755             gameInfo.variant != VariantBughouse &&
7756             gameInfo.variant != VariantCrazyhouse) return -1;
7757         whichMenu = 1; // drop menu
7758         break;
7759       default:
7760         return -1;
7761     }
7762
7763     if (((*fromX = xSqr) < 0) ||
7764         ((*fromY = ySqr) < 0)) {
7765         *fromX = *fromY = -1;
7766         return -1;
7767     }
7768     if (flipView)
7769       *fromX = BOARD_WIDTH - 1 - *fromX;
7770     else
7771       *fromY = BOARD_HEIGHT - 1 - *fromY;
7772
7773     return whichMenu;
7774 }
7775
7776 void
7777 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7778 {
7779 //    char * hint = lastHint;
7780     FrontEndProgramStats stats;
7781
7782     stats.which = cps == &first ? 0 : 1;
7783     stats.depth = cpstats->depth;
7784     stats.nodes = cpstats->nodes;
7785     stats.score = cpstats->score;
7786     stats.time = cpstats->time;
7787     stats.pv = cpstats->movelist;
7788     stats.hint = lastHint;
7789     stats.an_move_index = 0;
7790     stats.an_move_count = 0;
7791
7792     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7793         stats.hint = cpstats->move_name;
7794         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7795         stats.an_move_count = cpstats->nr_moves;
7796     }
7797
7798     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
7799
7800     SetProgramStats( &stats );
7801 }
7802
7803 void
7804 ClearEngineOutputPane (int which)
7805 {
7806     static FrontEndProgramStats dummyStats;
7807     dummyStats.which = which;
7808     dummyStats.pv = "#";
7809     SetProgramStats( &dummyStats );
7810 }
7811
7812 #define MAXPLAYERS 500
7813
7814 char *
7815 TourneyStandings (int display)
7816 {
7817     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7818     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7819     char result, *p, *names[MAXPLAYERS];
7820
7821     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7822         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7823     names[0] = p = strdup(appData.participants);
7824     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7825
7826     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7827
7828     while(result = appData.results[nr]) {
7829         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7830         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7831         wScore = bScore = 0;
7832         switch(result) {
7833           case '+': wScore = 2; break;
7834           case '-': bScore = 2; break;
7835           case '=': wScore = bScore = 1; break;
7836           case ' ':
7837           case '*': return strdup("busy"); // tourney not finished
7838         }
7839         score[w] += wScore;
7840         score[b] += bScore;
7841         games[w]++;
7842         games[b]++;
7843         nr++;
7844     }
7845     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7846     for(w=0; w<nPlayers; w++) {
7847         bScore = -1;
7848         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7849         ranking[w] = b; points[w] = bScore; score[b] = -2;
7850     }
7851     p = malloc(nPlayers*34+1);
7852     for(w=0; w<nPlayers && w<display; w++)
7853         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7854     free(names[0]);
7855     return p;
7856 }
7857
7858 void
7859 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7860 {       // count all piece types
7861         int p, f, r;
7862         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7863         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7864         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7865                 p = board[r][f];
7866                 pCnt[p]++;
7867                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7868                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7869                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7870                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7871                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7872                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7873         }
7874 }
7875
7876 int
7877 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7878 {
7879         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7880         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7881
7882         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7883         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7884         if(myPawns == 2 && nMine == 3) // KPP
7885             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7886         if(myPawns == 1 && nMine == 2) // KP
7887             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7888         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7889             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7890         if(myPawns) return FALSE;
7891         if(pCnt[WhiteRook+side])
7892             return pCnt[BlackRook-side] ||
7893                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7894                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7895                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7896         if(pCnt[WhiteCannon+side]) {
7897             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7898             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7899         }
7900         if(pCnt[WhiteKnight+side])
7901             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7902         return FALSE;
7903 }
7904
7905 int
7906 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7907 {
7908         VariantClass v = gameInfo.variant;
7909
7910         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7911         if(v == VariantShatranj) return TRUE; // always winnable through baring
7912         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7913         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7914
7915         if(v == VariantXiangqi) {
7916                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7917
7918                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7919                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7920                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7921                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7922                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7923                 if(stale) // we have at least one last-rank P plus perhaps C
7924                     return majors // KPKX
7925                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7926                 else // KCA*E*
7927                     return pCnt[WhiteFerz+side] // KCAK
7928                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7929                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7930                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7931
7932         } else if(v == VariantKnightmate) {
7933                 if(nMine == 1) return FALSE;
7934                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7935         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7936                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7937
7938                 if(nMine == 1) return FALSE; // bare King
7939                 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
7940                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7941                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7942                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7943                 if(pCnt[WhiteKnight+side])
7944                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7945                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7946                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7947                 if(nBishops)
7948                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7949                 if(pCnt[WhiteAlfil+side])
7950                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7951                 if(pCnt[WhiteWazir+side])
7952                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7953         }
7954
7955         return TRUE;
7956 }
7957
7958 int
7959 CompareWithRights (Board b1, Board b2)
7960 {
7961     int rights = 0;
7962     if(!CompareBoards(b1, b2)) return FALSE;
7963     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7964     /* compare castling rights */
7965     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7966            rights++; /* King lost rights, while rook still had them */
7967     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7968         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7969            rights++; /* but at least one rook lost them */
7970     }
7971     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7972            rights++;
7973     if( b1[CASTLING][5] != NoRights ) {
7974         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7975            rights++;
7976     }
7977     return rights == 0;
7978 }
7979
7980 int
7981 Adjudicate (ChessProgramState *cps)
7982 {       // [HGM] some adjudications useful with buggy engines
7983         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7984         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7985         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7986         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7987         int k, drop, count = 0; static int bare = 1;
7988         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7989         Boolean canAdjudicate = !appData.icsActive;
7990
7991         // most tests only when we understand the game, i.e. legality-checking on
7992             if( appData.testLegality )
7993             {   /* [HGM] Some more adjudications for obstinate engines */
7994                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7995                 static int moveCount = 6;
7996                 ChessMove result;
7997                 char *reason = NULL;
7998
7999                 /* Count what is on board. */
8000                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8001
8002                 /* Some material-based adjudications that have to be made before stalemate test */
8003                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8004                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8005                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8006                      if(canAdjudicate && appData.checkMates) {
8007                          if(engineOpponent)
8008                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8009                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8010                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8011                          return 1;
8012                      }
8013                 }
8014
8015                 /* Bare King in Shatranj (loses) or Losers (wins) */
8016                 if( nrW == 1 || nrB == 1) {
8017                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8018                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8019                      if(canAdjudicate && appData.checkMates) {
8020                          if(engineOpponent)
8021                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8022                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8023                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8024                          return 1;
8025                      }
8026                   } else
8027                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8028                   {    /* bare King */
8029                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8030                         if(canAdjudicate && appData.checkMates) {
8031                             /* but only adjudicate if adjudication enabled */
8032                             if(engineOpponent)
8033                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8034                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8035                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8036                             return 1;
8037                         }
8038                   }
8039                 } else bare = 1;
8040
8041
8042             // don't wait for engine to announce game end if we can judge ourselves
8043             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8044               case MT_CHECK:
8045                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8046                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8047                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8048                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8049                             checkCnt++;
8050                         if(checkCnt >= 2) {
8051                             reason = "Xboard adjudication: 3rd check";
8052                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8053                             break;
8054                         }
8055                     }
8056                 }
8057               case MT_NONE:
8058               default:
8059                 break;
8060               case MT_STEALMATE:
8061               case MT_STALEMATE:
8062               case MT_STAINMATE:
8063                 reason = "Xboard adjudication: Stalemate";
8064                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8065                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8066                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8067                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8068                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8069                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8070                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8071                                                                         EP_CHECKMATE : EP_WINS);
8072                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8073                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8074                 }
8075                 break;
8076               case MT_CHECKMATE:
8077                 reason = "Xboard adjudication: Checkmate";
8078                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8079                 if(gameInfo.variant == VariantShogi) {
8080                     if(forwardMostMove > backwardMostMove
8081                        && moveList[forwardMostMove-1][1] == '@'
8082                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8083                         reason = "XBoard adjudication: pawn-drop mate";
8084                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8085                     }
8086                 }
8087                 break;
8088             }
8089
8090                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8091                     case EP_STALEMATE:
8092                         result = GameIsDrawn; break;
8093                     case EP_CHECKMATE:
8094                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8095                     case EP_WINS:
8096                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8097                     default:
8098                         result = EndOfFile;
8099                 }
8100                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8101                     if(engineOpponent)
8102                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8103                     GameEnds( result, reason, GE_XBOARD );
8104                     return 1;
8105                 }
8106
8107                 /* Next absolutely insufficient mating material. */
8108                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8109                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8110                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8111
8112                      /* always flag draws, for judging claims */
8113                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8114
8115                      if(canAdjudicate && appData.materialDraws) {
8116                          /* but only adjudicate them if adjudication enabled */
8117                          if(engineOpponent) {
8118                            SendToProgram("force\n", engineOpponent); // suppress reply
8119                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8120                          }
8121                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8122                          return 1;
8123                      }
8124                 }
8125
8126                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8127                 if(gameInfo.variant == VariantXiangqi ?
8128                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8129                  : nrW + nrB == 4 &&
8130                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8131                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8132                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8133                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8134                    ) ) {
8135                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8136                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8137                           if(engineOpponent) {
8138                             SendToProgram("force\n", engineOpponent); // suppress reply
8139                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8140                           }
8141                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8142                           return 1;
8143                      }
8144                 } else moveCount = 6;
8145             }
8146
8147         // Repetition draws and 50-move rule can be applied independently of legality testing
8148
8149                 /* Check for rep-draws */
8150                 count = 0;
8151                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8152                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8153                 for(k = forwardMostMove-2;
8154                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8155                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8156                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8157                     k-=2)
8158                 {   int rights=0;
8159                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8160                         /* compare castling rights */
8161                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8162                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8163                                 rights++; /* King lost rights, while rook still had them */
8164                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8165                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8166                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8167                                    rights++; /* but at least one rook lost them */
8168                         }
8169                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8170                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8171                                 rights++;
8172                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8173                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8174                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8175                                    rights++;
8176                         }
8177                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8178                             && appData.drawRepeats > 1) {
8179                              /* adjudicate after user-specified nr of repeats */
8180                              int result = GameIsDrawn;
8181                              char *details = "XBoard adjudication: repetition draw";
8182                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8183                                 // [HGM] xiangqi: check for forbidden perpetuals
8184                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8185                                 for(m=forwardMostMove; m>k; m-=2) {
8186                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8187                                         ourPerpetual = 0; // the current mover did not always check
8188                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8189                                         hisPerpetual = 0; // the opponent did not always check
8190                                 }
8191                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8192                                                                         ourPerpetual, hisPerpetual);
8193                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8194                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8195                                     details = "Xboard adjudication: perpetual checking";
8196                                 } else
8197                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8198                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8199                                 } else
8200                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8201                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8202                                         result = BlackWins;
8203                                         details = "Xboard adjudication: repetition";
8204                                     }
8205                                 } else // it must be XQ
8206                                 // Now check for perpetual chases
8207                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8208                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8209                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8210                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8211                                         static char resdet[MSG_SIZ];
8212                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8213                                         details = resdet;
8214                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8215                                     } else
8216                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8217                                         break; // Abort repetition-checking loop.
8218                                 }
8219                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8220                              }
8221                              if(engineOpponent) {
8222                                SendToProgram("force\n", engineOpponent); // suppress reply
8223                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8224                              }
8225                              GameEnds( result, details, GE_XBOARD );
8226                              return 1;
8227                         }
8228                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8229                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8230                     }
8231                 }
8232
8233                 /* Now we test for 50-move draws. Determine ply count */
8234                 count = forwardMostMove;
8235                 /* look for last irreversble move */
8236                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8237                     count--;
8238                 /* if we hit starting position, add initial plies */
8239                 if( count == backwardMostMove )
8240                     count -= initialRulePlies;
8241                 count = forwardMostMove - count;
8242                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8243                         // adjust reversible move counter for checks in Xiangqi
8244                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8245                         if(i < backwardMostMove) i = backwardMostMove;
8246                         while(i <= forwardMostMove) {
8247                                 lastCheck = inCheck; // check evasion does not count
8248                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8249                                 if(inCheck || lastCheck) count--; // check does not count
8250                                 i++;
8251                         }
8252                 }
8253                 if( count >= 100)
8254                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8255                          /* this is used to judge if draw claims are legal */
8256                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8257                          if(engineOpponent) {
8258                            SendToProgram("force\n", engineOpponent); // suppress reply
8259                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8260                          }
8261                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8262                          return 1;
8263                 }
8264
8265                 /* if draw offer is pending, treat it as a draw claim
8266                  * when draw condition present, to allow engines a way to
8267                  * claim draws before making their move to avoid a race
8268                  * condition occurring after their move
8269                  */
8270                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8271                          char *p = NULL;
8272                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8273                              p = "Draw claim: 50-move rule";
8274                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8275                              p = "Draw claim: 3-fold repetition";
8276                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8277                              p = "Draw claim: insufficient mating material";
8278                          if( p != NULL && canAdjudicate) {
8279                              if(engineOpponent) {
8280                                SendToProgram("force\n", engineOpponent); // suppress reply
8281                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8282                              }
8283                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8284                              return 1;
8285                          }
8286                 }
8287
8288                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8289                     if(engineOpponent) {
8290                       SendToProgram("force\n", engineOpponent); // suppress reply
8291                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8292                     }
8293                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8294                     return 1;
8295                 }
8296         return 0;
8297 }
8298
8299 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8300 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8301 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8302
8303 static int
8304 BitbaseProbe ()
8305 {
8306     int pieces[10], squares[10], cnt=0, r, f, res;
8307     static int loaded;
8308     static PPROBE_EGBB probeBB;
8309     if(!appData.testLegality) return 10;
8310     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8311     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8312     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8313     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8314         ChessSquare piece = boards[forwardMostMove][r][f];
8315         int black = (piece >= BlackPawn);
8316         int type = piece - black*BlackPawn;
8317         if(piece == EmptySquare) continue;
8318         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8319         if(type == WhiteKing) type = WhiteQueen + 1;
8320         type = egbbCode[type];
8321         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8322         pieces[cnt] = type + black*6;
8323         if(++cnt > 5) return 11;
8324     }
8325     pieces[cnt] = squares[cnt] = 0;
8326     // probe EGBB
8327     if(loaded == 2) return 13; // loading failed before
8328     if(loaded == 0) {
8329         loaded = 2; // prepare for failure
8330         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8331         HMODULE lib;
8332         PLOAD_EGBB loadBB;
8333         if(!path) return 13; // no egbb installed
8334         strncpy(buf, path + 8, MSG_SIZ);
8335         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8336         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8337         lib = LoadLibrary(buf);
8338         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8339         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8340         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8341         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8342         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8343         loaded = 1; // success!
8344     }
8345     res = probeBB(forwardMostMove & 1, pieces, squares);
8346     return res > 0 ? 1 : res < 0 ? -1 : 0;
8347 }
8348
8349 char *
8350 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8351 {   // [HGM] book: this routine intercepts moves to simulate book replies
8352     char *bookHit = NULL;
8353
8354     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8355         char buf[MSG_SIZ];
8356         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8357         SendToProgram(buf, cps);
8358     }
8359     //first determine if the incoming move brings opponent into his book
8360     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8361         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8362     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8363     if(bookHit != NULL && !cps->bookSuspend) {
8364         // make sure opponent is not going to reply after receiving move to book position
8365         SendToProgram("force\n", cps);
8366         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8367     }
8368     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8369     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8370     // now arrange restart after book miss
8371     if(bookHit) {
8372         // after a book hit we never send 'go', and the code after the call to this routine
8373         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8374         char buf[MSG_SIZ], *move = bookHit;
8375         if(cps->useSAN) {
8376             int fromX, fromY, toX, toY;
8377             char promoChar;
8378             ChessMove moveType;
8379             move = buf + 30;
8380             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8381                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8382                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8383                                     PosFlags(forwardMostMove),
8384                                     fromY, fromX, toY, toX, promoChar, move);
8385             } else {
8386                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8387                 bookHit = NULL;
8388             }
8389         }
8390         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8391         SendToProgram(buf, cps);
8392         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8393     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8394         SendToProgram("go\n", cps);
8395         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8396     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8397         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8398             SendToProgram("go\n", cps);
8399         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8400     }
8401     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8402 }
8403
8404 int
8405 LoadError (char *errmess, ChessProgramState *cps)
8406 {   // unloads engine and switches back to -ncp mode if it was first
8407     if(cps->initDone) return FALSE;
8408     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8409     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8410     cps->pr = NoProc;
8411     if(cps == &first) {
8412         appData.noChessProgram = TRUE;
8413         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8414         gameMode = BeginningOfGame; ModeHighlight();
8415         SetNCPMode();
8416     }
8417     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8418     DisplayMessage("", ""); // erase waiting message
8419     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8420     return TRUE;
8421 }
8422
8423 char *savedMessage;
8424 ChessProgramState *savedState;
8425 void
8426 DeferredBookMove (void)
8427 {
8428         if(savedState->lastPing != savedState->lastPong)
8429                     ScheduleDelayedEvent(DeferredBookMove, 10);
8430         else
8431         HandleMachineMove(savedMessage, savedState);
8432 }
8433
8434 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8435 static ChessProgramState *stalledEngine;
8436 static char stashedInputMove[MSG_SIZ];
8437
8438 void
8439 HandleMachineMove (char *message, ChessProgramState *cps)
8440 {
8441     static char firstLeg[20];
8442     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8443     char realname[MSG_SIZ];
8444     int fromX, fromY, toX, toY;
8445     ChessMove moveType;
8446     char promoChar, roar;
8447     char *p, *pv=buf1;
8448     int machineWhite, oldError;
8449     char *bookHit;
8450
8451     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8452         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8453         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8454             DisplayError(_("Invalid pairing from pairing engine"), 0);
8455             return;
8456         }
8457         pairingReceived = 1;
8458         NextMatchGame();
8459         return; // Skim the pairing messages here.
8460     }
8461
8462     oldError = cps->userError; cps->userError = 0;
8463
8464 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8465     /*
8466      * Kludge to ignore BEL characters
8467      */
8468     while (*message == '\007') message++;
8469
8470     /*
8471      * [HGM] engine debug message: ignore lines starting with '#' character
8472      */
8473     if(cps->debug && *message == '#') return;
8474
8475     /*
8476      * Look for book output
8477      */
8478     if (cps == &first && bookRequested) {
8479         if (message[0] == '\t' || message[0] == ' ') {
8480             /* Part of the book output is here; append it */
8481             strcat(bookOutput, message);
8482             strcat(bookOutput, "  \n");
8483             return;
8484         } else if (bookOutput[0] != NULLCHAR) {
8485             /* All of book output has arrived; display it */
8486             char *p = bookOutput;
8487             while (*p != NULLCHAR) {
8488                 if (*p == '\t') *p = ' ';
8489                 p++;
8490             }
8491             DisplayInformation(bookOutput);
8492             bookRequested = FALSE;
8493             /* Fall through to parse the current output */
8494         }
8495     }
8496
8497     /*
8498      * Look for machine move.
8499      */
8500     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8501         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8502     {
8503         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8504             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8505             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8506             stalledEngine = cps;
8507             if(appData.ponderNextMove) { // bring opponent out of ponder
8508                 if(gameMode == TwoMachinesPlay) {
8509                     if(cps->other->pause)
8510                         PauseEngine(cps->other);
8511                     else
8512                         SendToProgram("easy\n", cps->other);
8513                 }
8514             }
8515             StopClocks();
8516             return;
8517         }
8518
8519         /* This method is only useful on engines that support ping */
8520         if (cps->lastPing != cps->lastPong) {
8521           if (gameMode == BeginningOfGame) {
8522             /* Extra move from before last new; ignore */
8523             if (appData.debugMode) {
8524                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8525             }
8526           } else {
8527             if (appData.debugMode) {
8528                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8529                         cps->which, gameMode);
8530             }
8531
8532             SendToProgram("undo\n", cps);
8533           }
8534           return;
8535         }
8536
8537         switch (gameMode) {
8538           case BeginningOfGame:
8539             /* Extra move from before last reset; ignore */
8540             if (appData.debugMode) {
8541                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8542             }
8543             return;
8544
8545           case EndOfGame:
8546           case IcsIdle:
8547           default:
8548             /* Extra move after we tried to stop.  The mode test is
8549                not a reliable way of detecting this problem, but it's
8550                the best we can do on engines that don't support ping.
8551             */
8552             if (appData.debugMode) {
8553                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8554                         cps->which, gameMode);
8555             }
8556             SendToProgram("undo\n", cps);
8557             return;
8558
8559           case MachinePlaysWhite:
8560           case IcsPlayingWhite:
8561             machineWhite = TRUE;
8562             break;
8563
8564           case MachinePlaysBlack:
8565           case IcsPlayingBlack:
8566             machineWhite = FALSE;
8567             break;
8568
8569           case TwoMachinesPlay:
8570             machineWhite = (cps->twoMachinesColor[0] == 'w');
8571             break;
8572         }
8573         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8574             if (appData.debugMode) {
8575                 fprintf(debugFP,
8576                         "Ignoring move out of turn by %s, gameMode %d"
8577                         ", forwardMost %d\n",
8578                         cps->which, gameMode, forwardMostMove);
8579             }
8580             return;
8581         }
8582
8583         if(cps->alphaRank) AlphaRank(machineMove, 4);
8584
8585         // [HGM] lion: (some very limited) support for Alien protocol
8586         killX = killY = -1;
8587         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8588             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8589             return;
8590         } else if(firstLeg[0]) { // there was a previous leg;
8591             // only support case where same piece makes two step (and don't even test that!)
8592             char buf[20], *p = machineMove+1, *q = buf+1, f;
8593             safeStrCpy(buf, machineMove, 20);
8594             while(isdigit(*q)) q++; // find start of to-square
8595             safeStrCpy(machineMove, firstLeg, 20);
8596             while(isdigit(*p)) p++;
8597             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8598             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8599             firstLeg[0] = NULLCHAR;
8600         }
8601
8602         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8603                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8604             /* Machine move could not be parsed; ignore it. */
8605           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8606                     machineMove, _(cps->which));
8607             DisplayMoveError(buf1);
8608             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8609                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8610             if (gameMode == TwoMachinesPlay) {
8611               GameEnds(machineWhite ? BlackWins : WhiteWins,
8612                        buf1, GE_XBOARD);
8613             }
8614             return;
8615         }
8616
8617         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8618         /* So we have to redo legality test with true e.p. status here,  */
8619         /* to make sure an illegal e.p. capture does not slip through,   */
8620         /* to cause a forfeit on a justified illegal-move complaint      */
8621         /* of the opponent.                                              */
8622         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8623            ChessMove moveType;
8624            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8625                              fromY, fromX, toY, toX, promoChar);
8626             if(moveType == IllegalMove) {
8627               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8628                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8629                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8630                            buf1, GE_XBOARD);
8631                 return;
8632            } else if(!appData.fischerCastling)
8633            /* [HGM] Kludge to handle engines that send FRC-style castling
8634               when they shouldn't (like TSCP-Gothic) */
8635            switch(moveType) {
8636              case WhiteASideCastleFR:
8637              case BlackASideCastleFR:
8638                toX+=2;
8639                currentMoveString[2]++;
8640                break;
8641              case WhiteHSideCastleFR:
8642              case BlackHSideCastleFR:
8643                toX--;
8644                currentMoveString[2]--;
8645                break;
8646              default: ; // nothing to do, but suppresses warning of pedantic compilers
8647            }
8648         }
8649         hintRequested = FALSE;
8650         lastHint[0] = NULLCHAR;
8651         bookRequested = FALSE;
8652         /* Program may be pondering now */
8653         cps->maybeThinking = TRUE;
8654         if (cps->sendTime == 2) cps->sendTime = 1;
8655         if (cps->offeredDraw) cps->offeredDraw--;
8656
8657         /* [AS] Save move info*/
8658         pvInfoList[ forwardMostMove ].score = programStats.score;
8659         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8660         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8661
8662         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8663
8664         /* Test suites abort the 'game' after one move */
8665         if(*appData.finger) {
8666            static FILE *f;
8667            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8668            if(!f) f = fopen(appData.finger, "w");
8669            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8670            else { DisplayFatalError("Bad output file", errno, 0); return; }
8671            free(fen);
8672            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8673         }
8674
8675         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8676         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8677             int count = 0;
8678
8679             while( count < adjudicateLossPlies ) {
8680                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8681
8682                 if( count & 1 ) {
8683                     score = -score; /* Flip score for winning side */
8684                 }
8685
8686                 if( score > adjudicateLossThreshold ) {
8687                     break;
8688                 }
8689
8690                 count++;
8691             }
8692
8693             if( count >= adjudicateLossPlies ) {
8694                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8695
8696                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8697                     "Xboard adjudication",
8698                     GE_XBOARD );
8699
8700                 return;
8701             }
8702         }
8703
8704         if(Adjudicate(cps)) {
8705             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8706             return; // [HGM] adjudicate: for all automatic game ends
8707         }
8708
8709 #if ZIPPY
8710         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8711             first.initDone) {
8712           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8713                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8714                 SendToICS("draw ");
8715                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8716           }
8717           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8718           ics_user_moved = 1;
8719           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8720                 char buf[3*MSG_SIZ];
8721
8722                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8723                         programStats.score / 100.,
8724                         programStats.depth,
8725                         programStats.time / 100.,
8726                         (unsigned int)programStats.nodes,
8727                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8728                         programStats.movelist);
8729                 SendToICS(buf);
8730 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8731           }
8732         }
8733 #endif
8734
8735         /* [AS] Clear stats for next move */
8736         ClearProgramStats();
8737         thinkOutput[0] = NULLCHAR;
8738         hiddenThinkOutputState = 0;
8739
8740         bookHit = NULL;
8741         if (gameMode == TwoMachinesPlay) {
8742             /* [HGM] relaying draw offers moved to after reception of move */
8743             /* and interpreting offer as claim if it brings draw condition */
8744             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8745                 SendToProgram("draw\n", cps->other);
8746             }
8747             if (cps->other->sendTime) {
8748                 SendTimeRemaining(cps->other,
8749                                   cps->other->twoMachinesColor[0] == 'w');
8750             }
8751             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8752             if (firstMove && !bookHit) {
8753                 firstMove = FALSE;
8754                 if (cps->other->useColors) {
8755                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8756                 }
8757                 SendToProgram("go\n", cps->other);
8758             }
8759             cps->other->maybeThinking = TRUE;
8760         }
8761
8762         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8763
8764         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8765
8766         if (!pausing && appData.ringBellAfterMoves) {
8767             if(!roar) RingBell();
8768         }
8769
8770         /*
8771          * Reenable menu items that were disabled while
8772          * machine was thinking
8773          */
8774         if (gameMode != TwoMachinesPlay)
8775             SetUserThinkingEnables();
8776
8777         // [HGM] book: after book hit opponent has received move and is now in force mode
8778         // force the book reply into it, and then fake that it outputted this move by jumping
8779         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8780         if(bookHit) {
8781                 static char bookMove[MSG_SIZ]; // a bit generous?
8782
8783                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8784                 strcat(bookMove, bookHit);
8785                 message = bookMove;
8786                 cps = cps->other;
8787                 programStats.nodes = programStats.depth = programStats.time =
8788                 programStats.score = programStats.got_only_move = 0;
8789                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8790
8791                 if(cps->lastPing != cps->lastPong) {
8792                     savedMessage = message; // args for deferred call
8793                     savedState = cps;
8794                     ScheduleDelayedEvent(DeferredBookMove, 10);
8795                     return;
8796                 }
8797                 goto FakeBookMove;
8798         }
8799
8800         return;
8801     }
8802
8803     /* Set special modes for chess engines.  Later something general
8804      *  could be added here; for now there is just one kludge feature,
8805      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8806      *  when "xboard" is given as an interactive command.
8807      */
8808     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8809         cps->useSigint = FALSE;
8810         cps->useSigterm = FALSE;
8811     }
8812     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8813       ParseFeatures(message+8, cps);
8814       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8815     }
8816
8817     if (!strncmp(message, "setup ", 6) && 
8818         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8819           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8820                                         ) { // [HGM] allow first engine to define opening position
8821       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8822       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8823       *buf = NULLCHAR;
8824       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8825       if(startedFromSetupPosition) return;
8826       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8827       if(dummy >= 3) {
8828         while(message[s] && message[s++] != ' ');
8829         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8830            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8831             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8832             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8833           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8834           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8835         }
8836       }
8837       ParseFEN(boards[0], &dummy, message+s, FALSE);
8838       DrawPosition(TRUE, boards[0]);
8839       startedFromSetupPosition = TRUE;
8840       return;
8841     }
8842     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8843      * want this, I was asked to put it in, and obliged.
8844      */
8845     if (!strncmp(message, "setboard ", 9)) {
8846         Board initial_position;
8847
8848         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8849
8850         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8851             DisplayError(_("Bad FEN received from engine"), 0);
8852             return ;
8853         } else {
8854            Reset(TRUE, FALSE);
8855            CopyBoard(boards[0], initial_position);
8856            initialRulePlies = FENrulePlies;
8857            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8858            else gameMode = MachinePlaysBlack;
8859            DrawPosition(FALSE, boards[currentMove]);
8860         }
8861         return;
8862     }
8863
8864     /*
8865      * Look for communication commands
8866      */
8867     if (!strncmp(message, "telluser ", 9)) {
8868         if(message[9] == '\\' && message[10] == '\\')
8869             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8870         PlayTellSound();
8871         DisplayNote(message + 9);
8872         return;
8873     }
8874     if (!strncmp(message, "tellusererror ", 14)) {
8875         cps->userError = 1;
8876         if(message[14] == '\\' && message[15] == '\\')
8877             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8878         PlayTellSound();
8879         DisplayError(message + 14, 0);
8880         return;
8881     }
8882     if (!strncmp(message, "tellopponent ", 13)) {
8883       if (appData.icsActive) {
8884         if (loggedOn) {
8885           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8886           SendToICS(buf1);
8887         }
8888       } else {
8889         DisplayNote(message + 13);
8890       }
8891       return;
8892     }
8893     if (!strncmp(message, "tellothers ", 11)) {
8894       if (appData.icsActive) {
8895         if (loggedOn) {
8896           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8897           SendToICS(buf1);
8898         }
8899       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8900       return;
8901     }
8902     if (!strncmp(message, "tellall ", 8)) {
8903       if (appData.icsActive) {
8904         if (loggedOn) {
8905           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8906           SendToICS(buf1);
8907         }
8908       } else {
8909         DisplayNote(message + 8);
8910       }
8911       return;
8912     }
8913     if (strncmp(message, "warning", 7) == 0) {
8914         /* Undocumented feature, use tellusererror in new code */
8915         DisplayError(message, 0);
8916         return;
8917     }
8918     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8919         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8920         strcat(realname, " query");
8921         AskQuestion(realname, buf2, buf1, cps->pr);
8922         return;
8923     }
8924     /* Commands from the engine directly to ICS.  We don't allow these to be
8925      *  sent until we are logged on. Crafty kibitzes have been known to
8926      *  interfere with the login process.
8927      */
8928     if (loggedOn) {
8929         if (!strncmp(message, "tellics ", 8)) {
8930             SendToICS(message + 8);
8931             SendToICS("\n");
8932             return;
8933         }
8934         if (!strncmp(message, "tellicsnoalias ", 15)) {
8935             SendToICS(ics_prefix);
8936             SendToICS(message + 15);
8937             SendToICS("\n");
8938             return;
8939         }
8940         /* The following are for backward compatibility only */
8941         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8942             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8943             SendToICS(ics_prefix);
8944             SendToICS(message);
8945             SendToICS("\n");
8946             return;
8947         }
8948     }
8949     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8950         if(initPing == cps->lastPong) {
8951             if(gameInfo.variant == VariantUnknown) {
8952                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8953                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8954                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8955             }
8956             initPing = -1;
8957         }
8958         return;
8959     }
8960     if(!strncmp(message, "highlight ", 10)) {
8961         if(appData.testLegality && appData.markers) return;
8962         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8963         return;
8964     }
8965     if(!strncmp(message, "click ", 6)) {
8966         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8967         if(appData.testLegality || !appData.oneClick) return;
8968         sscanf(message+6, "%c%d%c", &f, &y, &c);
8969         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8970         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8971         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8972         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8973         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8974         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8975             LeftClick(Release, lastLeftX, lastLeftY);
8976         controlKey  = (c == ',');
8977         LeftClick(Press, x, y);
8978         LeftClick(Release, x, y);
8979         first.highlight = f;
8980         return;
8981     }
8982     /*
8983      * If the move is illegal, cancel it and redraw the board.
8984      * Also deal with other error cases.  Matching is rather loose
8985      * here to accommodate engines written before the spec.
8986      */
8987     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8988         strncmp(message, "Error", 5) == 0) {
8989         if (StrStr(message, "name") ||
8990             StrStr(message, "rating") || StrStr(message, "?") ||
8991             StrStr(message, "result") || StrStr(message, "board") ||
8992             StrStr(message, "bk") || StrStr(message, "computer") ||
8993             StrStr(message, "variant") || StrStr(message, "hint") ||
8994             StrStr(message, "random") || StrStr(message, "depth") ||
8995             StrStr(message, "accepted")) {
8996             return;
8997         }
8998         if (StrStr(message, "protover")) {
8999           /* Program is responding to input, so it's apparently done
9000              initializing, and this error message indicates it is
9001              protocol version 1.  So we don't need to wait any longer
9002              for it to initialize and send feature commands. */
9003           FeatureDone(cps, 1);
9004           cps->protocolVersion = 1;
9005           return;
9006         }
9007         cps->maybeThinking = FALSE;
9008
9009         if (StrStr(message, "draw")) {
9010             /* Program doesn't have "draw" command */
9011             cps->sendDrawOffers = 0;
9012             return;
9013         }
9014         if (cps->sendTime != 1 &&
9015             (StrStr(message, "time") || StrStr(message, "otim"))) {
9016           /* Program apparently doesn't have "time" or "otim" command */
9017           cps->sendTime = 0;
9018           return;
9019         }
9020         if (StrStr(message, "analyze")) {
9021             cps->analysisSupport = FALSE;
9022             cps->analyzing = FALSE;
9023 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9024             EditGameEvent(); // [HGM] try to preserve loaded game
9025             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9026             DisplayError(buf2, 0);
9027             return;
9028         }
9029         if (StrStr(message, "(no matching move)st")) {
9030           /* Special kludge for GNU Chess 4 only */
9031           cps->stKludge = TRUE;
9032           SendTimeControl(cps, movesPerSession, timeControl,
9033                           timeIncrement, appData.searchDepth,
9034                           searchTime);
9035           return;
9036         }
9037         if (StrStr(message, "(no matching move)sd")) {
9038           /* Special kludge for GNU Chess 4 only */
9039           cps->sdKludge = TRUE;
9040           SendTimeControl(cps, movesPerSession, timeControl,
9041                           timeIncrement, appData.searchDepth,
9042                           searchTime);
9043           return;
9044         }
9045         if (!StrStr(message, "llegal")) {
9046             return;
9047         }
9048         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9049             gameMode == IcsIdle) return;
9050         if (forwardMostMove <= backwardMostMove) return;
9051         if (pausing) PauseEvent();
9052       if(appData.forceIllegal) {
9053             // [HGM] illegal: machine refused move; force position after move into it
9054           SendToProgram("force\n", cps);
9055           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9056                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9057                 // when black is to move, while there might be nothing on a2 or black
9058                 // might already have the move. So send the board as if white has the move.
9059                 // But first we must change the stm of the engine, as it refused the last move
9060                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9061                 if(WhiteOnMove(forwardMostMove)) {
9062                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9063                     SendBoard(cps, forwardMostMove); // kludgeless board
9064                 } else {
9065                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9066                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9067                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9068                 }
9069           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9070             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9071                  gameMode == TwoMachinesPlay)
9072               SendToProgram("go\n", cps);
9073             return;
9074       } else
9075         if (gameMode == PlayFromGameFile) {
9076             /* Stop reading this game file */
9077             gameMode = EditGame;
9078             ModeHighlight();
9079         }
9080         /* [HGM] illegal-move claim should forfeit game when Xboard */
9081         /* only passes fully legal moves                            */
9082         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9083             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9084                                 "False illegal-move claim", GE_XBOARD );
9085             return; // do not take back move we tested as valid
9086         }
9087         currentMove = forwardMostMove-1;
9088         DisplayMove(currentMove-1); /* before DisplayMoveError */
9089         SwitchClocks(forwardMostMove-1); // [HGM] race
9090         DisplayBothClocks();
9091         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9092                 parseList[currentMove], _(cps->which));
9093         DisplayMoveError(buf1);
9094         DrawPosition(FALSE, boards[currentMove]);
9095
9096         SetUserThinkingEnables();
9097         return;
9098     }
9099     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9100         /* Program has a broken "time" command that
9101            outputs a string not ending in newline.
9102            Don't use it. */
9103         cps->sendTime = 0;
9104     }
9105
9106     /*
9107      * If chess program startup fails, exit with an error message.
9108      * Attempts to recover here are futile. [HGM] Well, we try anyway
9109      */
9110     if ((StrStr(message, "unknown host") != NULL)
9111         || (StrStr(message, "No remote directory") != NULL)
9112         || (StrStr(message, "not found") != NULL)
9113         || (StrStr(message, "No such file") != NULL)
9114         || (StrStr(message, "can't alloc") != NULL)
9115         || (StrStr(message, "Permission denied") != NULL)) {
9116
9117         cps->maybeThinking = FALSE;
9118         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9119                 _(cps->which), cps->program, cps->host, message);
9120         RemoveInputSource(cps->isr);
9121         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9122             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9123             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9124         }
9125         return;
9126     }
9127
9128     /*
9129      * Look for hint output
9130      */
9131     if (sscanf(message, "Hint: %s", buf1) == 1) {
9132         if (cps == &first && hintRequested) {
9133             hintRequested = FALSE;
9134             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9135                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9136                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9137                                     PosFlags(forwardMostMove),
9138                                     fromY, fromX, toY, toX, promoChar, buf1);
9139                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9140                 DisplayInformation(buf2);
9141             } else {
9142                 /* Hint move could not be parsed!? */
9143               snprintf(buf2, sizeof(buf2),
9144                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9145                         buf1, _(cps->which));
9146                 DisplayError(buf2, 0);
9147             }
9148         } else {
9149           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9150         }
9151         return;
9152     }
9153
9154     /*
9155      * Ignore other messages if game is not in progress
9156      */
9157     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9158         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9159
9160     /*
9161      * look for win, lose, draw, or draw offer
9162      */
9163     if (strncmp(message, "1-0", 3) == 0) {
9164         char *p, *q, *r = "";
9165         p = strchr(message, '{');
9166         if (p) {
9167             q = strchr(p, '}');
9168             if (q) {
9169                 *q = NULLCHAR;
9170                 r = p + 1;
9171             }
9172         }
9173         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9174         return;
9175     } else if (strncmp(message, "0-1", 3) == 0) {
9176         char *p, *q, *r = "";
9177         p = strchr(message, '{');
9178         if (p) {
9179             q = strchr(p, '}');
9180             if (q) {
9181                 *q = NULLCHAR;
9182                 r = p + 1;
9183             }
9184         }
9185         /* Kludge for Arasan 4.1 bug */
9186         if (strcmp(r, "Black resigns") == 0) {
9187             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9188             return;
9189         }
9190         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9191         return;
9192     } else if (strncmp(message, "1/2", 3) == 0) {
9193         char *p, *q, *r = "";
9194         p = strchr(message, '{');
9195         if (p) {
9196             q = strchr(p, '}');
9197             if (q) {
9198                 *q = NULLCHAR;
9199                 r = p + 1;
9200             }
9201         }
9202
9203         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9204         return;
9205
9206     } else if (strncmp(message, "White resign", 12) == 0) {
9207         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9208         return;
9209     } else if (strncmp(message, "Black resign", 12) == 0) {
9210         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9211         return;
9212     } else if (strncmp(message, "White matches", 13) == 0 ||
9213                strncmp(message, "Black matches", 13) == 0   ) {
9214         /* [HGM] ignore GNUShogi noises */
9215         return;
9216     } else if (strncmp(message, "White", 5) == 0 &&
9217                message[5] != '(' &&
9218                StrStr(message, "Black") == NULL) {
9219         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9220         return;
9221     } else if (strncmp(message, "Black", 5) == 0 &&
9222                message[5] != '(') {
9223         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9224         return;
9225     } else if (strcmp(message, "resign") == 0 ||
9226                strcmp(message, "computer resigns") == 0) {
9227         switch (gameMode) {
9228           case MachinePlaysBlack:
9229           case IcsPlayingBlack:
9230             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9231             break;
9232           case MachinePlaysWhite:
9233           case IcsPlayingWhite:
9234             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9235             break;
9236           case TwoMachinesPlay:
9237             if (cps->twoMachinesColor[0] == 'w')
9238               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9239             else
9240               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9241             break;
9242           default:
9243             /* can't happen */
9244             break;
9245         }
9246         return;
9247     } else if (strncmp(message, "opponent mates", 14) == 0) {
9248         switch (gameMode) {
9249           case MachinePlaysBlack:
9250           case IcsPlayingBlack:
9251             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9252             break;
9253           case MachinePlaysWhite:
9254           case IcsPlayingWhite:
9255             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9256             break;
9257           case TwoMachinesPlay:
9258             if (cps->twoMachinesColor[0] == 'w')
9259               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9260             else
9261               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9262             break;
9263           default:
9264             /* can't happen */
9265             break;
9266         }
9267         return;
9268     } else if (strncmp(message, "computer mates", 14) == 0) {
9269         switch (gameMode) {
9270           case MachinePlaysBlack:
9271           case IcsPlayingBlack:
9272             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9273             break;
9274           case MachinePlaysWhite:
9275           case IcsPlayingWhite:
9276             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9277             break;
9278           case TwoMachinesPlay:
9279             if (cps->twoMachinesColor[0] == 'w')
9280               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9281             else
9282               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9283             break;
9284           default:
9285             /* can't happen */
9286             break;
9287         }
9288         return;
9289     } else if (strncmp(message, "checkmate", 9) == 0) {
9290         if (WhiteOnMove(forwardMostMove)) {
9291             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9292         } else {
9293             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9294         }
9295         return;
9296     } else if (strstr(message, "Draw") != NULL ||
9297                strstr(message, "game is a draw") != NULL) {
9298         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9299         return;
9300     } else if (strstr(message, "offer") != NULL &&
9301                strstr(message, "draw") != NULL) {
9302 #if ZIPPY
9303         if (appData.zippyPlay && first.initDone) {
9304             /* Relay offer to ICS */
9305             SendToICS(ics_prefix);
9306             SendToICS("draw\n");
9307         }
9308 #endif
9309         cps->offeredDraw = 2; /* valid until this engine moves twice */
9310         if (gameMode == TwoMachinesPlay) {
9311             if (cps->other->offeredDraw) {
9312                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9313             /* [HGM] in two-machine mode we delay relaying draw offer      */
9314             /* until after we also have move, to see if it is really claim */
9315             }
9316         } else if (gameMode == MachinePlaysWhite ||
9317                    gameMode == MachinePlaysBlack) {
9318           if (userOfferedDraw) {
9319             DisplayInformation(_("Machine accepts your draw offer"));
9320             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9321           } else {
9322             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9323           }
9324         }
9325     }
9326
9327
9328     /*
9329      * Look for thinking output
9330      */
9331     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9332           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9333                                 ) {
9334         int plylev, mvleft, mvtot, curscore, time;
9335         char mvname[MOVE_LEN];
9336         u64 nodes; // [DM]
9337         char plyext;
9338         int ignore = FALSE;
9339         int prefixHint = FALSE;
9340         mvname[0] = NULLCHAR;
9341
9342         switch (gameMode) {
9343           case MachinePlaysBlack:
9344           case IcsPlayingBlack:
9345             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9346             break;
9347           case MachinePlaysWhite:
9348           case IcsPlayingWhite:
9349             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9350             break;
9351           case AnalyzeMode:
9352           case AnalyzeFile:
9353             break;
9354           case IcsObserving: /* [DM] icsEngineAnalyze */
9355             if (!appData.icsEngineAnalyze) ignore = TRUE;
9356             break;
9357           case TwoMachinesPlay:
9358             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9359                 ignore = TRUE;
9360             }
9361             break;
9362           default:
9363             ignore = TRUE;
9364             break;
9365         }
9366
9367         if (!ignore) {
9368             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9369             buf1[0] = NULLCHAR;
9370             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9371                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9372
9373                 if (plyext != ' ' && plyext != '\t') {
9374                     time *= 100;
9375                 }
9376
9377                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9378                 if( cps->scoreIsAbsolute &&
9379                     ( gameMode == MachinePlaysBlack ||
9380                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9381                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9382                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9383                      !WhiteOnMove(currentMove)
9384                     ) )
9385                 {
9386                     curscore = -curscore;
9387                 }
9388
9389                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9390
9391                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9392                         char buf[MSG_SIZ];
9393                         FILE *f;
9394                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9395                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9396                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9397                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9398                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9399                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9400                                 fclose(f);
9401                         }
9402                         else
9403                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9404                           DisplayError(_("failed writing PV"), 0);
9405                 }
9406
9407                 tempStats.depth = plylev;
9408                 tempStats.nodes = nodes;
9409                 tempStats.time = time;
9410                 tempStats.score = curscore;
9411                 tempStats.got_only_move = 0;
9412
9413                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9414                         int ticklen;
9415
9416                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9417                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9418                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9419                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9420                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9421                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9422                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9423                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9424                 }
9425
9426                 /* Buffer overflow protection */
9427                 if (pv[0] != NULLCHAR) {
9428                     if (strlen(pv) >= sizeof(tempStats.movelist)
9429                         && appData.debugMode) {
9430                         fprintf(debugFP,
9431                                 "PV is too long; using the first %u bytes.\n",
9432                                 (unsigned) sizeof(tempStats.movelist) - 1);
9433                     }
9434
9435                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9436                 } else {
9437                     sprintf(tempStats.movelist, " no PV\n");
9438                 }
9439
9440                 if (tempStats.seen_stat) {
9441                     tempStats.ok_to_send = 1;
9442                 }
9443
9444                 if (strchr(tempStats.movelist, '(') != NULL) {
9445                     tempStats.line_is_book = 1;
9446                     tempStats.nr_moves = 0;
9447                     tempStats.moves_left = 0;
9448                 } else {
9449                     tempStats.line_is_book = 0;
9450                 }
9451
9452                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9453                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9454
9455                 SendProgramStatsToFrontend( cps, &tempStats );
9456
9457                 /*
9458                     [AS] Protect the thinkOutput buffer from overflow... this
9459                     is only useful if buf1 hasn't overflowed first!
9460                 */
9461                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9462                          plylev,
9463                          (gameMode == TwoMachinesPlay ?
9464                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9465                          ((double) curscore) / 100.0,
9466                          prefixHint ? lastHint : "",
9467                          prefixHint ? " " : "" );
9468
9469                 if( buf1[0] != NULLCHAR ) {
9470                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9471
9472                     if( strlen(pv) > max_len ) {
9473                         if( appData.debugMode) {
9474                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9475                         }
9476                         pv[max_len+1] = '\0';
9477                     }
9478
9479                     strcat( thinkOutput, pv);
9480                 }
9481
9482                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9483                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9484                     DisplayMove(currentMove - 1);
9485                 }
9486                 return;
9487
9488             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9489                 /* crafty (9.25+) says "(only move) <move>"
9490                  * if there is only 1 legal move
9491                  */
9492                 sscanf(p, "(only move) %s", buf1);
9493                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9494                 sprintf(programStats.movelist, "%s (only move)", buf1);
9495                 programStats.depth = 1;
9496                 programStats.nr_moves = 1;
9497                 programStats.moves_left = 1;
9498                 programStats.nodes = 1;
9499                 programStats.time = 1;
9500                 programStats.got_only_move = 1;
9501
9502                 /* Not really, but we also use this member to
9503                    mean "line isn't going to change" (Crafty
9504                    isn't searching, so stats won't change) */
9505                 programStats.line_is_book = 1;
9506
9507                 SendProgramStatsToFrontend( cps, &programStats );
9508
9509                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9510                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9511                     DisplayMove(currentMove - 1);
9512                 }
9513                 return;
9514             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9515                               &time, &nodes, &plylev, &mvleft,
9516                               &mvtot, mvname) >= 5) {
9517                 /* The stat01: line is from Crafty (9.29+) in response
9518                    to the "." command */
9519                 programStats.seen_stat = 1;
9520                 cps->maybeThinking = TRUE;
9521
9522                 if (programStats.got_only_move || !appData.periodicUpdates)
9523                   return;
9524
9525                 programStats.depth = plylev;
9526                 programStats.time = time;
9527                 programStats.nodes = nodes;
9528                 programStats.moves_left = mvleft;
9529                 programStats.nr_moves = mvtot;
9530                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9531                 programStats.ok_to_send = 1;
9532                 programStats.movelist[0] = '\0';
9533
9534                 SendProgramStatsToFrontend( cps, &programStats );
9535
9536                 return;
9537
9538             } else if (strncmp(message,"++",2) == 0) {
9539                 /* Crafty 9.29+ outputs this */
9540                 programStats.got_fail = 2;
9541                 return;
9542
9543             } else if (strncmp(message,"--",2) == 0) {
9544                 /* Crafty 9.29+ outputs this */
9545                 programStats.got_fail = 1;
9546                 return;
9547
9548             } else if (thinkOutput[0] != NULLCHAR &&
9549                        strncmp(message, "    ", 4) == 0) {
9550                 unsigned message_len;
9551
9552                 p = message;
9553                 while (*p && *p == ' ') p++;
9554
9555                 message_len = strlen( p );
9556
9557                 /* [AS] Avoid buffer overflow */
9558                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9559                     strcat(thinkOutput, " ");
9560                     strcat(thinkOutput, p);
9561                 }
9562
9563                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9564                     strcat(programStats.movelist, " ");
9565                     strcat(programStats.movelist, p);
9566                 }
9567
9568                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9569                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9570                     DisplayMove(currentMove - 1);
9571                 }
9572                 return;
9573             }
9574         }
9575         else {
9576             buf1[0] = NULLCHAR;
9577
9578             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9579                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9580             {
9581                 ChessProgramStats cpstats;
9582
9583                 if (plyext != ' ' && plyext != '\t') {
9584                     time *= 100;
9585                 }
9586
9587                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9588                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9589                     curscore = -curscore;
9590                 }
9591
9592                 cpstats.depth = plylev;
9593                 cpstats.nodes = nodes;
9594                 cpstats.time = time;
9595                 cpstats.score = curscore;
9596                 cpstats.got_only_move = 0;
9597                 cpstats.movelist[0] = '\0';
9598
9599                 if (buf1[0] != NULLCHAR) {
9600                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9601                 }
9602
9603                 cpstats.ok_to_send = 0;
9604                 cpstats.line_is_book = 0;
9605                 cpstats.nr_moves = 0;
9606                 cpstats.moves_left = 0;
9607
9608                 SendProgramStatsToFrontend( cps, &cpstats );
9609             }
9610         }
9611     }
9612 }
9613
9614
9615 /* Parse a game score from the character string "game", and
9616    record it as the history of the current game.  The game
9617    score is NOT assumed to start from the standard position.
9618    The display is not updated in any way.
9619    */
9620 void
9621 ParseGameHistory (char *game)
9622 {
9623     ChessMove moveType;
9624     int fromX, fromY, toX, toY, boardIndex;
9625     char promoChar;
9626     char *p, *q;
9627     char buf[MSG_SIZ];
9628
9629     if (appData.debugMode)
9630       fprintf(debugFP, "Parsing game history: %s\n", game);
9631
9632     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9633     gameInfo.site = StrSave(appData.icsHost);
9634     gameInfo.date = PGNDate();
9635     gameInfo.round = StrSave("-");
9636
9637     /* Parse out names of players */
9638     while (*game == ' ') game++;
9639     p = buf;
9640     while (*game != ' ') *p++ = *game++;
9641     *p = NULLCHAR;
9642     gameInfo.white = StrSave(buf);
9643     while (*game == ' ') game++;
9644     p = buf;
9645     while (*game != ' ' && *game != '\n') *p++ = *game++;
9646     *p = NULLCHAR;
9647     gameInfo.black = StrSave(buf);
9648
9649     /* Parse moves */
9650     boardIndex = blackPlaysFirst ? 1 : 0;
9651     yynewstr(game);
9652     for (;;) {
9653         yyboardindex = boardIndex;
9654         moveType = (ChessMove) Myylex();
9655         switch (moveType) {
9656           case IllegalMove:             /* maybe suicide chess, etc. */
9657   if (appData.debugMode) {
9658     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9659     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9660     setbuf(debugFP, NULL);
9661   }
9662           case WhitePromotion:
9663           case BlackPromotion:
9664           case WhiteNonPromotion:
9665           case BlackNonPromotion:
9666           case NormalMove:
9667           case FirstLeg:
9668           case WhiteCapturesEnPassant:
9669           case BlackCapturesEnPassant:
9670           case WhiteKingSideCastle:
9671           case WhiteQueenSideCastle:
9672           case BlackKingSideCastle:
9673           case BlackQueenSideCastle:
9674           case WhiteKingSideCastleWild:
9675           case WhiteQueenSideCastleWild:
9676           case BlackKingSideCastleWild:
9677           case BlackQueenSideCastleWild:
9678           /* PUSH Fabien */
9679           case WhiteHSideCastleFR:
9680           case WhiteASideCastleFR:
9681           case BlackHSideCastleFR:
9682           case BlackASideCastleFR:
9683           /* POP Fabien */
9684             fromX = currentMoveString[0] - AAA;
9685             fromY = currentMoveString[1] - ONE;
9686             toX = currentMoveString[2] - AAA;
9687             toY = currentMoveString[3] - ONE;
9688             promoChar = currentMoveString[4];
9689             break;
9690           case WhiteDrop:
9691           case BlackDrop:
9692             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9693             fromX = moveType == WhiteDrop ?
9694               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9695             (int) CharToPiece(ToLower(currentMoveString[0]));
9696             fromY = DROP_RANK;
9697             toX = currentMoveString[2] - AAA;
9698             toY = currentMoveString[3] - ONE;
9699             promoChar = NULLCHAR;
9700             break;
9701           case AmbiguousMove:
9702             /* bug? */
9703             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9704   if (appData.debugMode) {
9705     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9706     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9707     setbuf(debugFP, NULL);
9708   }
9709             DisplayError(buf, 0);
9710             return;
9711           case ImpossibleMove:
9712             /* bug? */
9713             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9714   if (appData.debugMode) {
9715     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9716     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9717     setbuf(debugFP, NULL);
9718   }
9719             DisplayError(buf, 0);
9720             return;
9721           case EndOfFile:
9722             if (boardIndex < backwardMostMove) {
9723                 /* Oops, gap.  How did that happen? */
9724                 DisplayError(_("Gap in move list"), 0);
9725                 return;
9726             }
9727             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9728             if (boardIndex > forwardMostMove) {
9729                 forwardMostMove = boardIndex;
9730             }
9731             return;
9732           case ElapsedTime:
9733             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9734                 strcat(parseList[boardIndex-1], " ");
9735                 strcat(parseList[boardIndex-1], yy_text);
9736             }
9737             continue;
9738           case Comment:
9739           case PGNTag:
9740           case NAG:
9741           default:
9742             /* ignore */
9743             continue;
9744           case WhiteWins:
9745           case BlackWins:
9746           case GameIsDrawn:
9747           case GameUnfinished:
9748             if (gameMode == IcsExamining) {
9749                 if (boardIndex < backwardMostMove) {
9750                     /* Oops, gap.  How did that happen? */
9751                     return;
9752                 }
9753                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9754                 return;
9755             }
9756             gameInfo.result = moveType;
9757             p = strchr(yy_text, '{');
9758             if (p == NULL) p = strchr(yy_text, '(');
9759             if (p == NULL) {
9760                 p = yy_text;
9761                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9762             } else {
9763                 q = strchr(p, *p == '{' ? '}' : ')');
9764                 if (q != NULL) *q = NULLCHAR;
9765                 p++;
9766             }
9767             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9768             gameInfo.resultDetails = StrSave(p);
9769             continue;
9770         }
9771         if (boardIndex >= forwardMostMove &&
9772             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9773             backwardMostMove = blackPlaysFirst ? 1 : 0;
9774             return;
9775         }
9776         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9777                                  fromY, fromX, toY, toX, promoChar,
9778                                  parseList[boardIndex]);
9779         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9780         /* currentMoveString is set as a side-effect of yylex */
9781         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9782         strcat(moveList[boardIndex], "\n");
9783         boardIndex++;
9784         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9785         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9786           case MT_NONE:
9787           case MT_STALEMATE:
9788           default:
9789             break;
9790           case MT_CHECK:
9791             if(!IS_SHOGI(gameInfo.variant))
9792                 strcat(parseList[boardIndex - 1], "+");
9793             break;
9794           case MT_CHECKMATE:
9795           case MT_STAINMATE:
9796             strcat(parseList[boardIndex - 1], "#");
9797             break;
9798         }
9799     }
9800 }
9801
9802
9803 /* Apply a move to the given board  */
9804 void
9805 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9806 {
9807   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9808   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9809
9810     /* [HGM] compute & store e.p. status and castling rights for new position */
9811     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9812
9813       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9814       oldEP = (signed char)board[EP_STATUS];
9815       board[EP_STATUS] = EP_NONE;
9816
9817   if (fromY == DROP_RANK) {
9818         /* must be first */
9819         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9820             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9821             return;
9822         }
9823         piece = board[toY][toX] = (ChessSquare) fromX;
9824   } else {
9825       ChessSquare victim;
9826       int i;
9827
9828       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9829            victim = board[killY][killX],
9830            board[killY][killX] = EmptySquare,
9831            board[EP_STATUS] = EP_CAPTURE;
9832
9833       if( board[toY][toX] != EmptySquare ) {
9834            board[EP_STATUS] = EP_CAPTURE;
9835            if( (fromX != toX || fromY != toY) && // not igui!
9836                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9837                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9838                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9839            }
9840       }
9841
9842       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9843            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9844                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9845       } else
9846       if( board[fromY][fromX] == WhitePawn ) {
9847            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9848                board[EP_STATUS] = EP_PAWN_MOVE;
9849            if( toY-fromY==2) {
9850                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9851                         gameInfo.variant != VariantBerolina || toX < fromX)
9852                       board[EP_STATUS] = toX | berolina;
9853                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9854                         gameInfo.variant != VariantBerolina || toX > fromX)
9855                       board[EP_STATUS] = toX;
9856            }
9857       } else
9858       if( board[fromY][fromX] == BlackPawn ) {
9859            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9860                board[EP_STATUS] = EP_PAWN_MOVE;
9861            if( toY-fromY== -2) {
9862                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9863                         gameInfo.variant != VariantBerolina || toX < fromX)
9864                       board[EP_STATUS] = toX | berolina;
9865                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9866                         gameInfo.variant != VariantBerolina || toX > fromX)
9867                       board[EP_STATUS] = toX;
9868            }
9869        }
9870
9871        for(i=0; i<nrCastlingRights; i++) {
9872            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9873               board[CASTLING][i] == toX   && castlingRank[i] == toY
9874              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9875        }
9876
9877        if(gameInfo.variant == VariantSChess) { // update virginity
9878            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9879            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9880            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9881            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9882        }
9883
9884      if (fromX == toX && fromY == toY) return;
9885
9886      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9887      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9888      if(gameInfo.variant == VariantKnightmate)
9889          king += (int) WhiteUnicorn - (int) WhiteKing;
9890
9891     /* Code added by Tord: */
9892     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9893     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9894         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9895       board[fromY][fromX] = EmptySquare;
9896       board[toY][toX] = EmptySquare;
9897       if((toX > fromX) != (piece == WhiteRook)) {
9898         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9899       } else {
9900         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9901       }
9902     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9903                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9904       board[fromY][fromX] = EmptySquare;
9905       board[toY][toX] = EmptySquare;
9906       if((toX > fromX) != (piece == BlackRook)) {
9907         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9908       } else {
9909         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9910       }
9911     /* End of code added by Tord */
9912
9913     } else if (board[fromY][fromX] == king
9914         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9915         && toY == fromY && toX > fromX+1) {
9916         board[fromY][fromX] = EmptySquare;
9917         board[toY][toX] = king;
9918         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9919         board[fromY][BOARD_RGHT-1] = EmptySquare;
9920     } else if (board[fromY][fromX] == king
9921         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9922                && toY == fromY && toX < fromX-1) {
9923         board[fromY][fromX] = EmptySquare;
9924         board[toY][toX] = king;
9925         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9926         board[fromY][BOARD_LEFT] = EmptySquare;
9927     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9928                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9929                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9930                ) {
9931         /* white pawn promotion */
9932         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9933         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9934             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9935         board[fromY][fromX] = EmptySquare;
9936     } else if ((fromY >= BOARD_HEIGHT>>1)
9937                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9938                && (toX != fromX)
9939                && gameInfo.variant != VariantXiangqi
9940                && gameInfo.variant != VariantBerolina
9941                && (board[fromY][fromX] == WhitePawn)
9942                && (board[toY][toX] == EmptySquare)) {
9943         board[fromY][fromX] = EmptySquare;
9944         board[toY][toX] = WhitePawn;
9945         captured = board[toY - 1][toX];
9946         board[toY - 1][toX] = EmptySquare;
9947     } else if ((fromY == BOARD_HEIGHT-4)
9948                && (toX == fromX)
9949                && gameInfo.variant == VariantBerolina
9950                && (board[fromY][fromX] == WhitePawn)
9951                && (board[toY][toX] == EmptySquare)) {
9952         board[fromY][fromX] = EmptySquare;
9953         board[toY][toX] = WhitePawn;
9954         if(oldEP & EP_BEROLIN_A) {
9955                 captured = board[fromY][fromX-1];
9956                 board[fromY][fromX-1] = EmptySquare;
9957         }else{  captured = board[fromY][fromX+1];
9958                 board[fromY][fromX+1] = EmptySquare;
9959         }
9960     } else if (board[fromY][fromX] == king
9961         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9962                && toY == fromY && toX > fromX+1) {
9963         board[fromY][fromX] = EmptySquare;
9964         board[toY][toX] = king;
9965         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9966         board[fromY][BOARD_RGHT-1] = EmptySquare;
9967     } else if (board[fromY][fromX] == king
9968         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9969                && toY == fromY && toX < fromX-1) {
9970         board[fromY][fromX] = EmptySquare;
9971         board[toY][toX] = king;
9972         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9973         board[fromY][BOARD_LEFT] = EmptySquare;
9974     } else if (fromY == 7 && fromX == 3
9975                && board[fromY][fromX] == BlackKing
9976                && toY == 7 && toX == 5) {
9977         board[fromY][fromX] = EmptySquare;
9978         board[toY][toX] = BlackKing;
9979         board[fromY][7] = EmptySquare;
9980         board[toY][4] = BlackRook;
9981     } else if (fromY == 7 && fromX == 3
9982                && board[fromY][fromX] == BlackKing
9983                && toY == 7 && toX == 1) {
9984         board[fromY][fromX] = EmptySquare;
9985         board[toY][toX] = BlackKing;
9986         board[fromY][0] = EmptySquare;
9987         board[toY][2] = BlackRook;
9988     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9989                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9990                && toY < promoRank && promoChar
9991                ) {
9992         /* black pawn promotion */
9993         board[toY][toX] = CharToPiece(ToLower(promoChar));
9994         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9995             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9996         board[fromY][fromX] = EmptySquare;
9997     } else if ((fromY < BOARD_HEIGHT>>1)
9998                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9999                && (toX != fromX)
10000                && gameInfo.variant != VariantXiangqi
10001                && gameInfo.variant != VariantBerolina
10002                && (board[fromY][fromX] == BlackPawn)
10003                && (board[toY][toX] == EmptySquare)) {
10004         board[fromY][fromX] = EmptySquare;
10005         board[toY][toX] = BlackPawn;
10006         captured = board[toY + 1][toX];
10007         board[toY + 1][toX] = EmptySquare;
10008     } else if ((fromY == 3)
10009                && (toX == fromX)
10010                && gameInfo.variant == VariantBerolina
10011                && (board[fromY][fromX] == BlackPawn)
10012                && (board[toY][toX] == EmptySquare)) {
10013         board[fromY][fromX] = EmptySquare;
10014         board[toY][toX] = BlackPawn;
10015         if(oldEP & EP_BEROLIN_A) {
10016                 captured = board[fromY][fromX-1];
10017                 board[fromY][fromX-1] = EmptySquare;
10018         }else{  captured = board[fromY][fromX+1];
10019                 board[fromY][fromX+1] = EmptySquare;
10020         }
10021     } else {
10022         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10023         board[fromY][fromX] = EmptySquare;
10024         board[toY][toX] = piece;
10025     }
10026   }
10027
10028     if (gameInfo.holdingsWidth != 0) {
10029
10030       /* !!A lot more code needs to be written to support holdings  */
10031       /* [HGM] OK, so I have written it. Holdings are stored in the */
10032       /* penultimate board files, so they are automaticlly stored   */
10033       /* in the game history.                                       */
10034       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10035                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10036         /* Delete from holdings, by decreasing count */
10037         /* and erasing image if necessary            */
10038         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10039         if(p < (int) BlackPawn) { /* white drop */
10040              p -= (int)WhitePawn;
10041                  p = PieceToNumber((ChessSquare)p);
10042              if(p >= gameInfo.holdingsSize) p = 0;
10043              if(--board[p][BOARD_WIDTH-2] <= 0)
10044                   board[p][BOARD_WIDTH-1] = EmptySquare;
10045              if((int)board[p][BOARD_WIDTH-2] < 0)
10046                         board[p][BOARD_WIDTH-2] = 0;
10047         } else {                  /* black drop */
10048              p -= (int)BlackPawn;
10049                  p = PieceToNumber((ChessSquare)p);
10050              if(p >= gameInfo.holdingsSize) p = 0;
10051              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10052                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10053              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10054                         board[BOARD_HEIGHT-1-p][1] = 0;
10055         }
10056       }
10057       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10058           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10059         /* [HGM] holdings: Add to holdings, if holdings exist */
10060         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10061                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10062                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10063         }
10064         p = (int) captured;
10065         if (p >= (int) BlackPawn) {
10066           p -= (int)BlackPawn;
10067           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10068                   /* in Shogi restore piece to its original  first */
10069                   captured = (ChessSquare) (DEMOTED captured);
10070                   p = DEMOTED p;
10071           }
10072           p = PieceToNumber((ChessSquare)p);
10073           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10074           board[p][BOARD_WIDTH-2]++;
10075           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10076         } else {
10077           p -= (int)WhitePawn;
10078           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10079                   captured = (ChessSquare) (DEMOTED captured);
10080                   p = DEMOTED p;
10081           }
10082           p = PieceToNumber((ChessSquare)p);
10083           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10084           board[BOARD_HEIGHT-1-p][1]++;
10085           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10086         }
10087       }
10088     } else if (gameInfo.variant == VariantAtomic) {
10089       if (captured != EmptySquare) {
10090         int y, x;
10091         for (y = toY-1; y <= toY+1; y++) {
10092           for (x = toX-1; x <= toX+1; x++) {
10093             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10094                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10095               board[y][x] = EmptySquare;
10096             }
10097           }
10098         }
10099         board[toY][toX] = EmptySquare;
10100       }
10101     }
10102
10103     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10104         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10105     } else
10106     if(promoChar == '+') {
10107         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10108         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10109         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10110           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10111     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10112         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10113         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10114            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10115         board[toY][toX] = newPiece;
10116     }
10117     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10118                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10119         // [HGM] superchess: take promotion piece out of holdings
10120         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10121         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10122             if(!--board[k][BOARD_WIDTH-2])
10123                 board[k][BOARD_WIDTH-1] = EmptySquare;
10124         } else {
10125             if(!--board[BOARD_HEIGHT-1-k][1])
10126                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10127         }
10128     }
10129 }
10130
10131 /* Updates forwardMostMove */
10132 void
10133 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10134 {
10135     int x = toX, y = toY;
10136     char *s = parseList[forwardMostMove];
10137     ChessSquare p = boards[forwardMostMove][toY][toX];
10138 //    forwardMostMove++; // [HGM] bare: moved downstream
10139
10140     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10141     (void) CoordsToAlgebraic(boards[forwardMostMove],
10142                              PosFlags(forwardMostMove),
10143                              fromY, fromX, y, x, promoChar,
10144                              s);
10145     if(killX >= 0 && killY >= 0)
10146         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10147
10148     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10149         int timeLeft; static int lastLoadFlag=0; int king, piece;
10150         piece = boards[forwardMostMove][fromY][fromX];
10151         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10152         if(gameInfo.variant == VariantKnightmate)
10153             king += (int) WhiteUnicorn - (int) WhiteKing;
10154         if(forwardMostMove == 0) {
10155             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10156                 fprintf(serverMoves, "%s;", UserName());
10157             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10158                 fprintf(serverMoves, "%s;", second.tidy);
10159             fprintf(serverMoves, "%s;", first.tidy);
10160             if(gameMode == MachinePlaysWhite)
10161                 fprintf(serverMoves, "%s;", UserName());
10162             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10163                 fprintf(serverMoves, "%s;", second.tidy);
10164         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10165         lastLoadFlag = loadFlag;
10166         // print base move
10167         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10168         // print castling suffix
10169         if( toY == fromY && piece == king ) {
10170             if(toX-fromX > 1)
10171                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10172             if(fromX-toX >1)
10173                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10174         }
10175         // e.p. suffix
10176         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10177              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10178              boards[forwardMostMove][toY][toX] == EmptySquare
10179              && fromX != toX && fromY != toY)
10180                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10181         // promotion suffix
10182         if(promoChar != NULLCHAR) {
10183             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10184                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10185                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10186             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10187         }
10188         if(!loadFlag) {
10189                 char buf[MOVE_LEN*2], *p; int len;
10190             fprintf(serverMoves, "/%d/%d",
10191                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10192             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10193             else                      timeLeft = blackTimeRemaining/1000;
10194             fprintf(serverMoves, "/%d", timeLeft);
10195                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10196                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10197                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10198                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10199             fprintf(serverMoves, "/%s", buf);
10200         }
10201         fflush(serverMoves);
10202     }
10203
10204     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10205         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10206       return;
10207     }
10208     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10209     if (commentList[forwardMostMove+1] != NULL) {
10210         free(commentList[forwardMostMove+1]);
10211         commentList[forwardMostMove+1] = NULL;
10212     }
10213     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10214     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10215     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10216     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10217     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10218     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10219     adjustedClock = FALSE;
10220     gameInfo.result = GameUnfinished;
10221     if (gameInfo.resultDetails != NULL) {
10222         free(gameInfo.resultDetails);
10223         gameInfo.resultDetails = NULL;
10224     }
10225     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10226                               moveList[forwardMostMove - 1]);
10227     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10228       case MT_NONE:
10229       case MT_STALEMATE:
10230       default:
10231         break;
10232       case MT_CHECK:
10233         if(!IS_SHOGI(gameInfo.variant))
10234             strcat(parseList[forwardMostMove - 1], "+");
10235         break;
10236       case MT_CHECKMATE:
10237       case MT_STAINMATE:
10238         strcat(parseList[forwardMostMove - 1], "#");
10239         break;
10240     }
10241 }
10242
10243 /* Updates currentMove if not pausing */
10244 void
10245 ShowMove (int fromX, int fromY, int toX, int toY)
10246 {
10247     int instant = (gameMode == PlayFromGameFile) ?
10248         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10249     if(appData.noGUI) return;
10250     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10251         if (!instant) {
10252             if (forwardMostMove == currentMove + 1) {
10253                 AnimateMove(boards[forwardMostMove - 1],
10254                             fromX, fromY, toX, toY);
10255             }
10256         }
10257         currentMove = forwardMostMove;
10258     }
10259
10260     killX = killY = -1; // [HGM] lion: used up
10261
10262     if (instant) return;
10263
10264     DisplayMove(currentMove - 1);
10265     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10266             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10267                 SetHighlights(fromX, fromY, toX, toY);
10268             }
10269     }
10270     DrawPosition(FALSE, boards[currentMove]);
10271     DisplayBothClocks();
10272     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10273 }
10274
10275 void
10276 SendEgtPath (ChessProgramState *cps)
10277 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10278         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10279
10280         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10281
10282         while(*p) {
10283             char c, *q = name+1, *r, *s;
10284
10285             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10286             while(*p && *p != ',') *q++ = *p++;
10287             *q++ = ':'; *q = 0;
10288             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10289                 strcmp(name, ",nalimov:") == 0 ) {
10290                 // take nalimov path from the menu-changeable option first, if it is defined
10291               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10292                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10293             } else
10294             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10295                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10296                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10297                 s = r = StrStr(s, ":") + 1; // beginning of path info
10298                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10299                 c = *r; *r = 0;             // temporarily null-terminate path info
10300                     *--q = 0;               // strip of trailig ':' from name
10301                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10302                 *r = c;
10303                 SendToProgram(buf,cps);     // send egtbpath command for this format
10304             }
10305             if(*p == ',') p++; // read away comma to position for next format name
10306         }
10307 }
10308
10309 static int
10310 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10311 {
10312       int width = 8, height = 8, holdings = 0;             // most common sizes
10313       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10314       // correct the deviations default for each variant
10315       if( v == VariantXiangqi ) width = 9,  height = 10;
10316       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10317       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10318       if( v == VariantCapablanca || v == VariantCapaRandom ||
10319           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10320                                 width = 10;
10321       if( v == VariantCourier ) width = 12;
10322       if( v == VariantSuper )                            holdings = 8;
10323       if( v == VariantGreat )   width = 10,              holdings = 8;
10324       if( v == VariantSChess )                           holdings = 7;
10325       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10326       if( v == VariantChuChess) width = 10, height = 10;
10327       if( v == VariantChu )     width = 12, height = 12;
10328       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10329              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10330              holdingsSize >= 0 && holdingsSize != holdings;
10331 }
10332
10333 char variantError[MSG_SIZ];
10334
10335 char *
10336 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10337 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10338       char *p, *variant = VariantName(v);
10339       static char b[MSG_SIZ];
10340       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10341            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10342                                                holdingsSize, variant); // cook up sized variant name
10343            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10344            if(StrStr(list, b) == NULL) {
10345                // specific sized variant not known, check if general sizing allowed
10346                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10347                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10348                             boardWidth, boardHeight, holdingsSize, engine);
10349                    return NULL;
10350                }
10351                /* [HGM] here we really should compare with the maximum supported board size */
10352            }
10353       } else snprintf(b, MSG_SIZ,"%s", variant);
10354       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10355       p = StrStr(list, b);
10356       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10357       if(p == NULL) {
10358           // occurs not at all in list, or only as sub-string
10359           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10360           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10361               int l = strlen(variantError);
10362               char *q;
10363               while(p != list && p[-1] != ',') p--;
10364               q = strchr(p, ',');
10365               if(q) *q = NULLCHAR;
10366               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10367               if(q) *q= ',';
10368           }
10369           return NULL;
10370       }
10371       return b;
10372 }
10373
10374 void
10375 InitChessProgram (ChessProgramState *cps, int setup)
10376 /* setup needed to setup FRC opening position */
10377 {
10378     char buf[MSG_SIZ], *b;
10379     if (appData.noChessProgram) return;
10380     hintRequested = FALSE;
10381     bookRequested = FALSE;
10382
10383     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10384     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10385     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10386     if(cps->memSize) { /* [HGM] memory */
10387       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10388         SendToProgram(buf, cps);
10389     }
10390     SendEgtPath(cps); /* [HGM] EGT */
10391     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10392       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10393         SendToProgram(buf, cps);
10394     }
10395
10396     SendToProgram(cps->initString, cps);
10397     if (gameInfo.variant != VariantNormal &&
10398         gameInfo.variant != VariantLoadable
10399         /* [HGM] also send variant if board size non-standard */
10400         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10401
10402       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10403                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10404       if (b == NULL) {
10405         DisplayFatalError(variantError, 0, 1);
10406         return;
10407       }
10408
10409       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10410       SendToProgram(buf, cps);
10411     }
10412     currentlyInitializedVariant = gameInfo.variant;
10413
10414     /* [HGM] send opening position in FRC to first engine */
10415     if(setup) {
10416           SendToProgram("force\n", cps);
10417           SendBoard(cps, 0);
10418           /* engine is now in force mode! Set flag to wake it up after first move. */
10419           setboardSpoiledMachineBlack = 1;
10420     }
10421
10422     if (cps->sendICS) {
10423       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10424       SendToProgram(buf, cps);
10425     }
10426     cps->maybeThinking = FALSE;
10427     cps->offeredDraw = 0;
10428     if (!appData.icsActive) {
10429         SendTimeControl(cps, movesPerSession, timeControl,
10430                         timeIncrement, appData.searchDepth,
10431                         searchTime);
10432     }
10433     if (appData.showThinking
10434         // [HGM] thinking: four options require thinking output to be sent
10435         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10436                                 ) {
10437         SendToProgram("post\n", cps);
10438     }
10439     SendToProgram("hard\n", cps);
10440     if (!appData.ponderNextMove) {
10441         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10442            it without being sure what state we are in first.  "hard"
10443            is not a toggle, so that one is OK.
10444          */
10445         SendToProgram("easy\n", cps);
10446     }
10447     if (cps->usePing) {
10448       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10449       SendToProgram(buf, cps);
10450     }
10451     cps->initDone = TRUE;
10452     ClearEngineOutputPane(cps == &second);
10453 }
10454
10455
10456 void
10457 ResendOptions (ChessProgramState *cps)
10458 { // send the stored value of the options
10459   int i;
10460   char buf[MSG_SIZ];
10461   Option *opt = cps->option;
10462   for(i=0; i<cps->nrOptions; i++, opt++) {
10463       switch(opt->type) {
10464         case Spin:
10465         case Slider:
10466         case CheckBox:
10467             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10468           break;
10469         case ComboBox:
10470           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10471           break;
10472         default:
10473             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10474           break;
10475         case Button:
10476         case SaveButton:
10477           continue;
10478       }
10479       SendToProgram(buf, cps);
10480   }
10481 }
10482
10483 void
10484 StartChessProgram (ChessProgramState *cps)
10485 {
10486     char buf[MSG_SIZ];
10487     int err;
10488
10489     if (appData.noChessProgram) return;
10490     cps->initDone = FALSE;
10491
10492     if (strcmp(cps->host, "localhost") == 0) {
10493         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10494     } else if (*appData.remoteShell == NULLCHAR) {
10495         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10496     } else {
10497         if (*appData.remoteUser == NULLCHAR) {
10498           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10499                     cps->program);
10500         } else {
10501           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10502                     cps->host, appData.remoteUser, cps->program);
10503         }
10504         err = StartChildProcess(buf, "", &cps->pr);
10505     }
10506
10507     if (err != 0) {
10508       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10509         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10510         if(cps != &first) return;
10511         appData.noChessProgram = TRUE;
10512         ThawUI();
10513         SetNCPMode();
10514 //      DisplayFatalError(buf, err, 1);
10515 //      cps->pr = NoProc;
10516 //      cps->isr = NULL;
10517         return;
10518     }
10519
10520     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10521     if (cps->protocolVersion > 1) {
10522       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10523       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10524         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10525         cps->comboCnt = 0;  //                and values of combo boxes
10526       }
10527       SendToProgram(buf, cps);
10528       if(cps->reload) ResendOptions(cps);
10529     } else {
10530       SendToProgram("xboard\n", cps);
10531     }
10532 }
10533
10534 void
10535 TwoMachinesEventIfReady P((void))
10536 {
10537   static int curMess = 0;
10538   if (first.lastPing != first.lastPong) {
10539     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10540     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10541     return;
10542   }
10543   if (second.lastPing != second.lastPong) {
10544     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10545     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10546     return;
10547   }
10548   DisplayMessage("", ""); curMess = 0;
10549   TwoMachinesEvent();
10550 }
10551
10552 char *
10553 MakeName (char *template)
10554 {
10555     time_t clock;
10556     struct tm *tm;
10557     static char buf[MSG_SIZ];
10558     char *p = buf;
10559     int i;
10560
10561     clock = time((time_t *)NULL);
10562     tm = localtime(&clock);
10563
10564     while(*p++ = *template++) if(p[-1] == '%') {
10565         switch(*template++) {
10566           case 0:   *p = 0; return buf;
10567           case 'Y': i = tm->tm_year+1900; break;
10568           case 'y': i = tm->tm_year-100; break;
10569           case 'M': i = tm->tm_mon+1; break;
10570           case 'd': i = tm->tm_mday; break;
10571           case 'h': i = tm->tm_hour; break;
10572           case 'm': i = tm->tm_min; break;
10573           case 's': i = tm->tm_sec; break;
10574           default:  i = 0;
10575         }
10576         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10577     }
10578     return buf;
10579 }
10580
10581 int
10582 CountPlayers (char *p)
10583 {
10584     int n = 0;
10585     while(p = strchr(p, '\n')) p++, n++; // count participants
10586     return n;
10587 }
10588
10589 FILE *
10590 WriteTourneyFile (char *results, FILE *f)
10591 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10592     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10593     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10594         // create a file with tournament description
10595         fprintf(f, "-participants {%s}\n", appData.participants);
10596         fprintf(f, "-seedBase %d\n", appData.seedBase);
10597         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10598         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10599         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10600         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10601         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10602         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10603         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10604         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10605         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10606         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10607         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10608         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10609         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10610         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10611         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10612         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10613         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10614         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10615         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10616         fprintf(f, "-smpCores %d\n", appData.smpCores);
10617         if(searchTime > 0)
10618                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10619         else {
10620                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10621                 fprintf(f, "-tc %s\n", appData.timeControl);
10622                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10623         }
10624         fprintf(f, "-results \"%s\"\n", results);
10625     }
10626     return f;
10627 }
10628
10629 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10630
10631 void
10632 Substitute (char *participants, int expunge)
10633 {
10634     int i, changed, changes=0, nPlayers=0;
10635     char *p, *q, *r, buf[MSG_SIZ];
10636     if(participants == NULL) return;
10637     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10638     r = p = participants; q = appData.participants;
10639     while(*p && *p == *q) {
10640         if(*p == '\n') r = p+1, nPlayers++;
10641         p++; q++;
10642     }
10643     if(*p) { // difference
10644         while(*p && *p++ != '\n');
10645         while(*q && *q++ != '\n');
10646       changed = nPlayers;
10647         changes = 1 + (strcmp(p, q) != 0);
10648     }
10649     if(changes == 1) { // a single engine mnemonic was changed
10650         q = r; while(*q) nPlayers += (*q++ == '\n');
10651         p = buf; while(*r && (*p = *r++) != '\n') p++;
10652         *p = NULLCHAR;
10653         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10654         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10655         if(mnemonic[i]) { // The substitute is valid
10656             FILE *f;
10657             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10658                 flock(fileno(f), LOCK_EX);
10659                 ParseArgsFromFile(f);
10660                 fseek(f, 0, SEEK_SET);
10661                 FREE(appData.participants); appData.participants = participants;
10662                 if(expunge) { // erase results of replaced engine
10663                     int len = strlen(appData.results), w, b, dummy;
10664                     for(i=0; i<len; i++) {
10665                         Pairing(i, nPlayers, &w, &b, &dummy);
10666                         if((w == changed || b == changed) && appData.results[i] == '*') {
10667                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10668                             fclose(f);
10669                             return;
10670                         }
10671                     }
10672                     for(i=0; i<len; i++) {
10673                         Pairing(i, nPlayers, &w, &b, &dummy);
10674                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10675                     }
10676                 }
10677                 WriteTourneyFile(appData.results, f);
10678                 fclose(f); // release lock
10679                 return;
10680             }
10681         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10682     }
10683     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10684     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10685     free(participants);
10686     return;
10687 }
10688
10689 int
10690 CheckPlayers (char *participants)
10691 {
10692         int i;
10693         char buf[MSG_SIZ], *p;
10694         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10695         while(p = strchr(participants, '\n')) {
10696             *p = NULLCHAR;
10697             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10698             if(!mnemonic[i]) {
10699                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10700                 *p = '\n';
10701                 DisplayError(buf, 0);
10702                 return 1;
10703             }
10704             *p = '\n';
10705             participants = p + 1;
10706         }
10707         return 0;
10708 }
10709
10710 int
10711 CreateTourney (char *name)
10712 {
10713         FILE *f;
10714         if(matchMode && strcmp(name, appData.tourneyFile)) {
10715              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10716         }
10717         if(name[0] == NULLCHAR) {
10718             if(appData.participants[0])
10719                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10720             return 0;
10721         }
10722         f = fopen(name, "r");
10723         if(f) { // file exists
10724             ASSIGN(appData.tourneyFile, name);
10725             ParseArgsFromFile(f); // parse it
10726         } else {
10727             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10728             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10729                 DisplayError(_("Not enough participants"), 0);
10730                 return 0;
10731             }
10732             if(CheckPlayers(appData.participants)) return 0;
10733             ASSIGN(appData.tourneyFile, name);
10734             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10735             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10736         }
10737         fclose(f);
10738         appData.noChessProgram = FALSE;
10739         appData.clockMode = TRUE;
10740         SetGNUMode();
10741         return 1;
10742 }
10743
10744 int
10745 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10746 {
10747     char buf[MSG_SIZ], *p, *q;
10748     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10749     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10750     skip = !all && group[0]; // if group requested, we start in skip mode
10751     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10752         p = names; q = buf; header = 0;
10753         while(*p && *p != '\n') *q++ = *p++;
10754         *q = 0;
10755         if(*p == '\n') p++;
10756         if(buf[0] == '#') {
10757             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10758             depth++; // we must be entering a new group
10759             if(all) continue; // suppress printing group headers when complete list requested
10760             header = 1;
10761             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10762         }
10763         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10764         if(engineList[i]) free(engineList[i]);
10765         engineList[i] = strdup(buf);
10766         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10767         if(engineMnemonic[i]) free(engineMnemonic[i]);
10768         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10769             strcat(buf, " (");
10770             sscanf(q + 8, "%s", buf + strlen(buf));
10771             strcat(buf, ")");
10772         }
10773         engineMnemonic[i] = strdup(buf);
10774         i++;
10775     }
10776     engineList[i] = engineMnemonic[i] = NULL;
10777     return i;
10778 }
10779
10780 // following implemented as macro to avoid type limitations
10781 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10782
10783 void
10784 SwapEngines (int n)
10785 {   // swap settings for first engine and other engine (so far only some selected options)
10786     int h;
10787     char *p;
10788     if(n == 0) return;
10789     SWAP(directory, p)
10790     SWAP(chessProgram, p)
10791     SWAP(isUCI, h)
10792     SWAP(hasOwnBookUCI, h)
10793     SWAP(protocolVersion, h)
10794     SWAP(reuse, h)
10795     SWAP(scoreIsAbsolute, h)
10796     SWAP(timeOdds, h)
10797     SWAP(logo, p)
10798     SWAP(pgnName, p)
10799     SWAP(pvSAN, h)
10800     SWAP(engOptions, p)
10801     SWAP(engInitString, p)
10802     SWAP(computerString, p)
10803     SWAP(features, p)
10804     SWAP(fenOverride, p)
10805     SWAP(NPS, h)
10806     SWAP(accumulateTC, h)
10807     SWAP(drawDepth, h)
10808     SWAP(host, p)
10809 }
10810
10811 int
10812 GetEngineLine (char *s, int n)
10813 {
10814     int i;
10815     char buf[MSG_SIZ];
10816     extern char *icsNames;
10817     if(!s || !*s) return 0;
10818     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10819     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10820     if(!mnemonic[i]) return 0;
10821     if(n == 11) return 1; // just testing if there was a match
10822     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10823     if(n == 1) SwapEngines(n);
10824     ParseArgsFromString(buf);
10825     if(n == 1) SwapEngines(n);
10826     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10827         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10828         ParseArgsFromString(buf);
10829     }
10830     return 1;
10831 }
10832
10833 int
10834 SetPlayer (int player, char *p)
10835 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10836     int i;
10837     char buf[MSG_SIZ], *engineName;
10838     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10839     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10840     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10841     if(mnemonic[i]) {
10842         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10843         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10844         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10845         ParseArgsFromString(buf);
10846     } else { // no engine with this nickname is installed!
10847         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10848         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10849         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10850         ModeHighlight();
10851         DisplayError(buf, 0);
10852         return 0;
10853     }
10854     free(engineName);
10855     return i;
10856 }
10857
10858 char *recentEngines;
10859
10860 void
10861 RecentEngineEvent (int nr)
10862 {
10863     int n;
10864 //    SwapEngines(1); // bump first to second
10865 //    ReplaceEngine(&second, 1); // and load it there
10866     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10867     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10868     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10869         ReplaceEngine(&first, 0);
10870         FloatToFront(&appData.recentEngineList, command[n]);
10871     }
10872 }
10873
10874 int
10875 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10876 {   // determine players from game number
10877     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10878
10879     if(appData.tourneyType == 0) {
10880         roundsPerCycle = (nPlayers - 1) | 1;
10881         pairingsPerRound = nPlayers / 2;
10882     } else if(appData.tourneyType > 0) {
10883         roundsPerCycle = nPlayers - appData.tourneyType;
10884         pairingsPerRound = appData.tourneyType;
10885     }
10886     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10887     gamesPerCycle = gamesPerRound * roundsPerCycle;
10888     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10889     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10890     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10891     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10892     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10893     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10894
10895     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10896     if(appData.roundSync) *syncInterval = gamesPerRound;
10897
10898     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10899
10900     if(appData.tourneyType == 0) {
10901         if(curPairing == (nPlayers-1)/2 ) {
10902             *whitePlayer = curRound;
10903             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10904         } else {
10905             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10906             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10907             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10908             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10909         }
10910     } else if(appData.tourneyType > 1) {
10911         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10912         *whitePlayer = curRound + appData.tourneyType;
10913     } else if(appData.tourneyType > 0) {
10914         *whitePlayer = curPairing;
10915         *blackPlayer = curRound + appData.tourneyType;
10916     }
10917
10918     // take care of white/black alternation per round.
10919     // For cycles and games this is already taken care of by default, derived from matchGame!
10920     return curRound & 1;
10921 }
10922
10923 int
10924 NextTourneyGame (int nr, int *swapColors)
10925 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10926     char *p, *q;
10927     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10928     FILE *tf;
10929     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10930     tf = fopen(appData.tourneyFile, "r");
10931     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10932     ParseArgsFromFile(tf); fclose(tf);
10933     InitTimeControls(); // TC might be altered from tourney file
10934
10935     nPlayers = CountPlayers(appData.participants); // count participants
10936     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10937     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10938
10939     if(syncInterval) {
10940         p = q = appData.results;
10941         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10942         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10943             DisplayMessage(_("Waiting for other game(s)"),"");
10944             waitingForGame = TRUE;
10945             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10946             return 0;
10947         }
10948         waitingForGame = FALSE;
10949     }
10950
10951     if(appData.tourneyType < 0) {
10952         if(nr>=0 && !pairingReceived) {
10953             char buf[1<<16];
10954             if(pairing.pr == NoProc) {
10955                 if(!appData.pairingEngine[0]) {
10956                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10957                     return 0;
10958                 }
10959                 StartChessProgram(&pairing); // starts the pairing engine
10960             }
10961             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10962             SendToProgram(buf, &pairing);
10963             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10964             SendToProgram(buf, &pairing);
10965             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10966         }
10967         pairingReceived = 0;                              // ... so we continue here
10968         *swapColors = 0;
10969         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10970         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10971         matchGame = 1; roundNr = nr / syncInterval + 1;
10972     }
10973
10974     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10975
10976     // redefine engines, engine dir, etc.
10977     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10978     if(first.pr == NoProc) {
10979       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10980       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10981     }
10982     if(second.pr == NoProc) {
10983       SwapEngines(1);
10984       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10985       SwapEngines(1);         // and make that valid for second engine by swapping
10986       InitEngine(&second, 1);
10987     }
10988     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10989     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10990     return OK;
10991 }
10992
10993 void
10994 NextMatchGame ()
10995 {   // performs game initialization that does not invoke engines, and then tries to start the game
10996     int res, firstWhite, swapColors = 0;
10997     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10998     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
10999         char buf[MSG_SIZ];
11000         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11001         if(strcmp(buf, currentDebugFile)) { // name has changed
11002             FILE *f = fopen(buf, "w");
11003             if(f) { // if opening the new file failed, just keep using the old one
11004                 ASSIGN(currentDebugFile, buf);
11005                 fclose(debugFP);
11006                 debugFP = f;
11007             }
11008             if(appData.serverFileName) {
11009                 if(serverFP) fclose(serverFP);
11010                 serverFP = fopen(appData.serverFileName, "w");
11011                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11012                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11013             }
11014         }
11015     }
11016     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11017     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11018     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11019     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11020     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11021     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11022     Reset(FALSE, first.pr != NoProc);
11023     res = LoadGameOrPosition(matchGame); // setup game
11024     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11025     if(!res) return; // abort when bad game/pos file
11026     TwoMachinesEvent();
11027 }
11028
11029 void
11030 UserAdjudicationEvent (int result)
11031 {
11032     ChessMove gameResult = GameIsDrawn;
11033
11034     if( result > 0 ) {
11035         gameResult = WhiteWins;
11036     }
11037     else if( result < 0 ) {
11038         gameResult = BlackWins;
11039     }
11040
11041     if( gameMode == TwoMachinesPlay ) {
11042         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11043     }
11044 }
11045
11046
11047 // [HGM] save: calculate checksum of game to make games easily identifiable
11048 int
11049 StringCheckSum (char *s)
11050 {
11051         int i = 0;
11052         if(s==NULL) return 0;
11053         while(*s) i = i*259 + *s++;
11054         return i;
11055 }
11056
11057 int
11058 GameCheckSum ()
11059 {
11060         int i, sum=0;
11061         for(i=backwardMostMove; i<forwardMostMove; i++) {
11062                 sum += pvInfoList[i].depth;
11063                 sum += StringCheckSum(parseList[i]);
11064                 sum += StringCheckSum(commentList[i]);
11065                 sum *= 261;
11066         }
11067         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11068         return sum + StringCheckSum(commentList[i]);
11069 } // end of save patch
11070
11071 void
11072 GameEnds (ChessMove result, char *resultDetails, int whosays)
11073 {
11074     GameMode nextGameMode;
11075     int isIcsGame;
11076     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11077
11078     if(endingGame) return; /* [HGM] crash: forbid recursion */
11079     endingGame = 1;
11080     if(twoBoards) { // [HGM] dual: switch back to one board
11081         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11082         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11083     }
11084     if (appData.debugMode) {
11085       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11086               result, resultDetails ? resultDetails : "(null)", whosays);
11087     }
11088
11089     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11090
11091     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11092
11093     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11094         /* If we are playing on ICS, the server decides when the
11095            game is over, but the engine can offer to draw, claim
11096            a draw, or resign.
11097          */
11098 #if ZIPPY
11099         if (appData.zippyPlay && first.initDone) {
11100             if (result == GameIsDrawn) {
11101                 /* In case draw still needs to be claimed */
11102                 SendToICS(ics_prefix);
11103                 SendToICS("draw\n");
11104             } else if (StrCaseStr(resultDetails, "resign")) {
11105                 SendToICS(ics_prefix);
11106                 SendToICS("resign\n");
11107             }
11108         }
11109 #endif
11110         endingGame = 0; /* [HGM] crash */
11111         return;
11112     }
11113
11114     /* If we're loading the game from a file, stop */
11115     if (whosays == GE_FILE) {
11116       (void) StopLoadGameTimer();
11117       gameFileFP = NULL;
11118     }
11119
11120     /* Cancel draw offers */
11121     first.offeredDraw = second.offeredDraw = 0;
11122
11123     /* If this is an ICS game, only ICS can really say it's done;
11124        if not, anyone can. */
11125     isIcsGame = (gameMode == IcsPlayingWhite ||
11126                  gameMode == IcsPlayingBlack ||
11127                  gameMode == IcsObserving    ||
11128                  gameMode == IcsExamining);
11129
11130     if (!isIcsGame || whosays == GE_ICS) {
11131         /* OK -- not an ICS game, or ICS said it was done */
11132         StopClocks();
11133         if (!isIcsGame && !appData.noChessProgram)
11134           SetUserThinkingEnables();
11135
11136         /* [HGM] if a machine claims the game end we verify this claim */
11137         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11138             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11139                 char claimer;
11140                 ChessMove trueResult = (ChessMove) -1;
11141
11142                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11143                                             first.twoMachinesColor[0] :
11144                                             second.twoMachinesColor[0] ;
11145
11146                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11147                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11148                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11149                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11150                 } else
11151                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11152                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11153                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11154                 } else
11155                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11156                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11157                 }
11158
11159                 // now verify win claims, but not in drop games, as we don't understand those yet
11160                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11161                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11162                     (result == WhiteWins && claimer == 'w' ||
11163                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11164                       if (appData.debugMode) {
11165                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11166                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11167                       }
11168                       if(result != trueResult) {
11169                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11170                               result = claimer == 'w' ? BlackWins : WhiteWins;
11171                               resultDetails = buf;
11172                       }
11173                 } else
11174                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11175                     && (forwardMostMove <= backwardMostMove ||
11176                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11177                         (claimer=='b')==(forwardMostMove&1))
11178                                                                                   ) {
11179                       /* [HGM] verify: draws that were not flagged are false claims */
11180                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11181                       result = claimer == 'w' ? BlackWins : WhiteWins;
11182                       resultDetails = buf;
11183                 }
11184                 /* (Claiming a loss is accepted no questions asked!) */
11185             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11186                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11187                 result = GameUnfinished;
11188                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11189             }
11190             /* [HGM] bare: don't allow bare King to win */
11191             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11192                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11193                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11194                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11195                && result != GameIsDrawn)
11196             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11197                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11198                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11199                         if(p >= 0 && p <= (int)WhiteKing) k++;
11200                 }
11201                 if (appData.debugMode) {
11202                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11203                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11204                 }
11205                 if(k <= 1) {
11206                         result = GameIsDrawn;
11207                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11208                         resultDetails = buf;
11209                 }
11210             }
11211         }
11212
11213
11214         if(serverMoves != NULL && !loadFlag) { char c = '=';
11215             if(result==WhiteWins) c = '+';
11216             if(result==BlackWins) c = '-';
11217             if(resultDetails != NULL)
11218                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11219         }
11220         if (resultDetails != NULL) {
11221             gameInfo.result = result;
11222             gameInfo.resultDetails = StrSave(resultDetails);
11223
11224             /* display last move only if game was not loaded from file */
11225             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11226                 DisplayMove(currentMove - 1);
11227
11228             if (forwardMostMove != 0) {
11229                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11230                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11231                                                                 ) {
11232                     if (*appData.saveGameFile != NULLCHAR) {
11233                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11234                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11235                         else
11236                         SaveGameToFile(appData.saveGameFile, TRUE);
11237                     } else if (appData.autoSaveGames) {
11238                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11239                     }
11240                     if (*appData.savePositionFile != NULLCHAR) {
11241                         SavePositionToFile(appData.savePositionFile);
11242                     }
11243                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11244                 }
11245             }
11246
11247             /* Tell program how game ended in case it is learning */
11248             /* [HGM] Moved this to after saving the PGN, just in case */
11249             /* engine died and we got here through time loss. In that */
11250             /* case we will get a fatal error writing the pipe, which */
11251             /* would otherwise lose us the PGN.                       */
11252             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11253             /* output during GameEnds should never be fatal anymore   */
11254             if (gameMode == MachinePlaysWhite ||
11255                 gameMode == MachinePlaysBlack ||
11256                 gameMode == TwoMachinesPlay ||
11257                 gameMode == IcsPlayingWhite ||
11258                 gameMode == IcsPlayingBlack ||
11259                 gameMode == BeginningOfGame) {
11260                 char buf[MSG_SIZ];
11261                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11262                         resultDetails);
11263                 if (first.pr != NoProc) {
11264                     SendToProgram(buf, &first);
11265                 }
11266                 if (second.pr != NoProc &&
11267                     gameMode == TwoMachinesPlay) {
11268                     SendToProgram(buf, &second);
11269                 }
11270             }
11271         }
11272
11273         if (appData.icsActive) {
11274             if (appData.quietPlay &&
11275                 (gameMode == IcsPlayingWhite ||
11276                  gameMode == IcsPlayingBlack)) {
11277                 SendToICS(ics_prefix);
11278                 SendToICS("set shout 1\n");
11279             }
11280             nextGameMode = IcsIdle;
11281             ics_user_moved = FALSE;
11282             /* clean up premove.  It's ugly when the game has ended and the
11283              * premove highlights are still on the board.
11284              */
11285             if (gotPremove) {
11286               gotPremove = FALSE;
11287               ClearPremoveHighlights();
11288               DrawPosition(FALSE, boards[currentMove]);
11289             }
11290             if (whosays == GE_ICS) {
11291                 switch (result) {
11292                 case WhiteWins:
11293                     if (gameMode == IcsPlayingWhite)
11294                         PlayIcsWinSound();
11295                     else if(gameMode == IcsPlayingBlack)
11296                         PlayIcsLossSound();
11297                     break;
11298                 case BlackWins:
11299                     if (gameMode == IcsPlayingBlack)
11300                         PlayIcsWinSound();
11301                     else if(gameMode == IcsPlayingWhite)
11302                         PlayIcsLossSound();
11303                     break;
11304                 case GameIsDrawn:
11305                     PlayIcsDrawSound();
11306                     break;
11307                 default:
11308                     PlayIcsUnfinishedSound();
11309                 }
11310             }
11311             if(appData.quitNext) { ExitEvent(0); return; }
11312         } else if (gameMode == EditGame ||
11313                    gameMode == PlayFromGameFile ||
11314                    gameMode == AnalyzeMode ||
11315                    gameMode == AnalyzeFile) {
11316             nextGameMode = gameMode;
11317         } else {
11318             nextGameMode = EndOfGame;
11319         }
11320         pausing = FALSE;
11321         ModeHighlight();
11322     } else {
11323         nextGameMode = gameMode;
11324     }
11325
11326     if (appData.noChessProgram) {
11327         gameMode = nextGameMode;
11328         ModeHighlight();
11329         endingGame = 0; /* [HGM] crash */
11330         return;
11331     }
11332
11333     if (first.reuse) {
11334         /* Put first chess program into idle state */
11335         if (first.pr != NoProc &&
11336             (gameMode == MachinePlaysWhite ||
11337              gameMode == MachinePlaysBlack ||
11338              gameMode == TwoMachinesPlay ||
11339              gameMode == IcsPlayingWhite ||
11340              gameMode == IcsPlayingBlack ||
11341              gameMode == BeginningOfGame)) {
11342             SendToProgram("force\n", &first);
11343             if (first.usePing) {
11344               char buf[MSG_SIZ];
11345               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11346               SendToProgram(buf, &first);
11347             }
11348         }
11349     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11350         /* Kill off first chess program */
11351         if (first.isr != NULL)
11352           RemoveInputSource(first.isr);
11353         first.isr = NULL;
11354
11355         if (first.pr != NoProc) {
11356             ExitAnalyzeMode();
11357             DoSleep( appData.delayBeforeQuit );
11358             SendToProgram("quit\n", &first);
11359             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11360             first.reload = TRUE;
11361         }
11362         first.pr = NoProc;
11363     }
11364     if (second.reuse) {
11365         /* Put second chess program into idle state */
11366         if (second.pr != NoProc &&
11367             gameMode == TwoMachinesPlay) {
11368             SendToProgram("force\n", &second);
11369             if (second.usePing) {
11370               char buf[MSG_SIZ];
11371               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11372               SendToProgram(buf, &second);
11373             }
11374         }
11375     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11376         /* Kill off second chess program */
11377         if (second.isr != NULL)
11378           RemoveInputSource(second.isr);
11379         second.isr = NULL;
11380
11381         if (second.pr != NoProc) {
11382             DoSleep( appData.delayBeforeQuit );
11383             SendToProgram("quit\n", &second);
11384             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11385             second.reload = TRUE;
11386         }
11387         second.pr = NoProc;
11388     }
11389
11390     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11391         char resChar = '=';
11392         switch (result) {
11393         case WhiteWins:
11394           resChar = '+';
11395           if (first.twoMachinesColor[0] == 'w') {
11396             first.matchWins++;
11397           } else {
11398             second.matchWins++;
11399           }
11400           break;
11401         case BlackWins:
11402           resChar = '-';
11403           if (first.twoMachinesColor[0] == 'b') {
11404             first.matchWins++;
11405           } else {
11406             second.matchWins++;
11407           }
11408           break;
11409         case GameUnfinished:
11410           resChar = ' ';
11411         default:
11412           break;
11413         }
11414
11415         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11416         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11417             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11418             ReserveGame(nextGame, resChar); // sets nextGame
11419             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11420             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11421         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11422
11423         if (nextGame <= appData.matchGames && !abortMatch) {
11424             gameMode = nextGameMode;
11425             matchGame = nextGame; // this will be overruled in tourney mode!
11426             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11427             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11428             endingGame = 0; /* [HGM] crash */
11429             return;
11430         } else {
11431             gameMode = nextGameMode;
11432             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11433                      first.tidy, second.tidy,
11434                      first.matchWins, second.matchWins,
11435                      appData.matchGames - (first.matchWins + second.matchWins));
11436             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11437             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11438             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11439             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11440                 first.twoMachinesColor = "black\n";
11441                 second.twoMachinesColor = "white\n";
11442             } else {
11443                 first.twoMachinesColor = "white\n";
11444                 second.twoMachinesColor = "black\n";
11445             }
11446         }
11447     }
11448     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11449         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11450       ExitAnalyzeMode();
11451     gameMode = nextGameMode;
11452     ModeHighlight();
11453     endingGame = 0;  /* [HGM] crash */
11454     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11455         if(matchMode == TRUE) { // match through command line: exit with or without popup
11456             if(ranking) {
11457                 ToNrEvent(forwardMostMove);
11458                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11459                 else ExitEvent(0);
11460             } else DisplayFatalError(buf, 0, 0);
11461         } else { // match through menu; just stop, with or without popup
11462             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11463             ModeHighlight();
11464             if(ranking){
11465                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11466             } else DisplayNote(buf);
11467       }
11468       if(ranking) free(ranking);
11469     }
11470 }
11471
11472 /* Assumes program was just initialized (initString sent).
11473    Leaves program in force mode. */
11474 void
11475 FeedMovesToProgram (ChessProgramState *cps, int upto)
11476 {
11477     int i;
11478
11479     if (appData.debugMode)
11480       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11481               startedFromSetupPosition ? "position and " : "",
11482               backwardMostMove, upto, cps->which);
11483     if(currentlyInitializedVariant != gameInfo.variant) {
11484       char buf[MSG_SIZ];
11485         // [HGM] variantswitch: make engine aware of new variant
11486         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11487                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11488                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11489         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11490         SendToProgram(buf, cps);
11491         currentlyInitializedVariant = gameInfo.variant;
11492     }
11493     SendToProgram("force\n", cps);
11494     if (startedFromSetupPosition) {
11495         SendBoard(cps, backwardMostMove);
11496     if (appData.debugMode) {
11497         fprintf(debugFP, "feedMoves\n");
11498     }
11499     }
11500     for (i = backwardMostMove; i < upto; i++) {
11501         SendMoveToProgram(i, cps);
11502     }
11503 }
11504
11505
11506 int
11507 ResurrectChessProgram ()
11508 {
11509      /* The chess program may have exited.
11510         If so, restart it and feed it all the moves made so far. */
11511     static int doInit = 0;
11512
11513     if (appData.noChessProgram) return 1;
11514
11515     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11516         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11517         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11518         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11519     } else {
11520         if (first.pr != NoProc) return 1;
11521         StartChessProgram(&first);
11522     }
11523     InitChessProgram(&first, FALSE);
11524     FeedMovesToProgram(&first, currentMove);
11525
11526     if (!first.sendTime) {
11527         /* can't tell gnuchess what its clock should read,
11528            so we bow to its notion. */
11529         ResetClocks();
11530         timeRemaining[0][currentMove] = whiteTimeRemaining;
11531         timeRemaining[1][currentMove] = blackTimeRemaining;
11532     }
11533
11534     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11535                 appData.icsEngineAnalyze) && first.analysisSupport) {
11536       SendToProgram("analyze\n", &first);
11537       first.analyzing = TRUE;
11538     }
11539     return 1;
11540 }
11541
11542 /*
11543  * Button procedures
11544  */
11545 void
11546 Reset (int redraw, int init)
11547 {
11548     int i;
11549
11550     if (appData.debugMode) {
11551         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11552                 redraw, init, gameMode);
11553     }
11554     CleanupTail(); // [HGM] vari: delete any stored variations
11555     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11556     pausing = pauseExamInvalid = FALSE;
11557     startedFromSetupPosition = blackPlaysFirst = FALSE;
11558     firstMove = TRUE;
11559     whiteFlag = blackFlag = FALSE;
11560     userOfferedDraw = FALSE;
11561     hintRequested = bookRequested = FALSE;
11562     first.maybeThinking = FALSE;
11563     second.maybeThinking = FALSE;
11564     first.bookSuspend = FALSE; // [HGM] book
11565     second.bookSuspend = FALSE;
11566     thinkOutput[0] = NULLCHAR;
11567     lastHint[0] = NULLCHAR;
11568     ClearGameInfo(&gameInfo);
11569     gameInfo.variant = StringToVariant(appData.variant);
11570     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11571     ics_user_moved = ics_clock_paused = FALSE;
11572     ics_getting_history = H_FALSE;
11573     ics_gamenum = -1;
11574     white_holding[0] = black_holding[0] = NULLCHAR;
11575     ClearProgramStats();
11576     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11577
11578     ResetFrontEnd();
11579     ClearHighlights();
11580     flipView = appData.flipView;
11581     ClearPremoveHighlights();
11582     gotPremove = FALSE;
11583     alarmSounded = FALSE;
11584     killX = killY = -1; // [HGM] lion
11585
11586     GameEnds(EndOfFile, NULL, GE_PLAYER);
11587     if(appData.serverMovesName != NULL) {
11588         /* [HGM] prepare to make moves file for broadcasting */
11589         clock_t t = clock();
11590         if(serverMoves != NULL) fclose(serverMoves);
11591         serverMoves = fopen(appData.serverMovesName, "r");
11592         if(serverMoves != NULL) {
11593             fclose(serverMoves);
11594             /* delay 15 sec before overwriting, so all clients can see end */
11595             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11596         }
11597         serverMoves = fopen(appData.serverMovesName, "w");
11598     }
11599
11600     ExitAnalyzeMode();
11601     gameMode = BeginningOfGame;
11602     ModeHighlight();
11603     if(appData.icsActive) gameInfo.variant = VariantNormal;
11604     currentMove = forwardMostMove = backwardMostMove = 0;
11605     MarkTargetSquares(1);
11606     InitPosition(redraw);
11607     for (i = 0; i < MAX_MOVES; i++) {
11608         if (commentList[i] != NULL) {
11609             free(commentList[i]);
11610             commentList[i] = NULL;
11611         }
11612     }
11613     ResetClocks();
11614     timeRemaining[0][0] = whiteTimeRemaining;
11615     timeRemaining[1][0] = blackTimeRemaining;
11616
11617     if (first.pr == NoProc) {
11618         StartChessProgram(&first);
11619     }
11620     if (init) {
11621             InitChessProgram(&first, startedFromSetupPosition);
11622     }
11623     DisplayTitle("");
11624     DisplayMessage("", "");
11625     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11626     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11627     ClearMap();        // [HGM] exclude: invalidate map
11628 }
11629
11630 void
11631 AutoPlayGameLoop ()
11632 {
11633     for (;;) {
11634         if (!AutoPlayOneMove())
11635           return;
11636         if (matchMode || appData.timeDelay == 0)
11637           continue;
11638         if (appData.timeDelay < 0)
11639           return;
11640         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11641         break;
11642     }
11643 }
11644
11645 void
11646 AnalyzeNextGame()
11647 {
11648     ReloadGame(1); // next game
11649 }
11650
11651 int
11652 AutoPlayOneMove ()
11653 {
11654     int fromX, fromY, toX, toY;
11655
11656     if (appData.debugMode) {
11657       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11658     }
11659
11660     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11661       return FALSE;
11662
11663     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11664       pvInfoList[currentMove].depth = programStats.depth;
11665       pvInfoList[currentMove].score = programStats.score;
11666       pvInfoList[currentMove].time  = 0;
11667       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11668       else { // append analysis of final position as comment
11669         char buf[MSG_SIZ];
11670         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11671         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11672       }
11673       programStats.depth = 0;
11674     }
11675
11676     if (currentMove >= forwardMostMove) {
11677       if(gameMode == AnalyzeFile) {
11678           if(appData.loadGameIndex == -1) {
11679             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11680           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11681           } else {
11682           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11683         }
11684       }
11685 //      gameMode = EndOfGame;
11686 //      ModeHighlight();
11687
11688       /* [AS] Clear current move marker at the end of a game */
11689       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11690
11691       return FALSE;
11692     }
11693
11694     toX = moveList[currentMove][2] - AAA;
11695     toY = moveList[currentMove][3] - ONE;
11696
11697     if (moveList[currentMove][1] == '@') {
11698         if (appData.highlightLastMove) {
11699             SetHighlights(-1, -1, toX, toY);
11700         }
11701     } else {
11702         fromX = moveList[currentMove][0] - AAA;
11703         fromY = moveList[currentMove][1] - ONE;
11704
11705         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11706
11707         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11708
11709         if (appData.highlightLastMove) {
11710             SetHighlights(fromX, fromY, toX, toY);
11711         }
11712     }
11713     DisplayMove(currentMove);
11714     SendMoveToProgram(currentMove++, &first);
11715     DisplayBothClocks();
11716     DrawPosition(FALSE, boards[currentMove]);
11717     // [HGM] PV info: always display, routine tests if empty
11718     DisplayComment(currentMove - 1, commentList[currentMove]);
11719     return TRUE;
11720 }
11721
11722
11723 int
11724 LoadGameOneMove (ChessMove readAhead)
11725 {
11726     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11727     char promoChar = NULLCHAR;
11728     ChessMove moveType;
11729     char move[MSG_SIZ];
11730     char *p, *q;
11731
11732     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11733         gameMode != AnalyzeMode && gameMode != Training) {
11734         gameFileFP = NULL;
11735         return FALSE;
11736     }
11737
11738     yyboardindex = forwardMostMove;
11739     if (readAhead != EndOfFile) {
11740       moveType = readAhead;
11741     } else {
11742       if (gameFileFP == NULL)
11743           return FALSE;
11744       moveType = (ChessMove) Myylex();
11745     }
11746
11747     done = FALSE;
11748     switch (moveType) {
11749       case Comment:
11750         if (appData.debugMode)
11751           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11752         p = yy_text;
11753
11754         /* append the comment but don't display it */
11755         AppendComment(currentMove, p, FALSE);
11756         return TRUE;
11757
11758       case WhiteCapturesEnPassant:
11759       case BlackCapturesEnPassant:
11760       case WhitePromotion:
11761       case BlackPromotion:
11762       case WhiteNonPromotion:
11763       case BlackNonPromotion:
11764       case NormalMove:
11765       case FirstLeg:
11766       case WhiteKingSideCastle:
11767       case WhiteQueenSideCastle:
11768       case BlackKingSideCastle:
11769       case BlackQueenSideCastle:
11770       case WhiteKingSideCastleWild:
11771       case WhiteQueenSideCastleWild:
11772       case BlackKingSideCastleWild:
11773       case BlackQueenSideCastleWild:
11774       /* PUSH Fabien */
11775       case WhiteHSideCastleFR:
11776       case WhiteASideCastleFR:
11777       case BlackHSideCastleFR:
11778       case BlackASideCastleFR:
11779       /* POP Fabien */
11780         if (appData.debugMode)
11781           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11782         fromX = currentMoveString[0] - AAA;
11783         fromY = currentMoveString[1] - ONE;
11784         toX = currentMoveString[2] - AAA;
11785         toY = currentMoveString[3] - ONE;
11786         promoChar = currentMoveString[4];
11787         if(promoChar == ';') promoChar = NULLCHAR;
11788         break;
11789
11790       case WhiteDrop:
11791       case BlackDrop:
11792         if (appData.debugMode)
11793           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11794         fromX = moveType == WhiteDrop ?
11795           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11796         (int) CharToPiece(ToLower(currentMoveString[0]));
11797         fromY = DROP_RANK;
11798         toX = currentMoveString[2] - AAA;
11799         toY = currentMoveString[3] - ONE;
11800         break;
11801
11802       case WhiteWins:
11803       case BlackWins:
11804       case GameIsDrawn:
11805       case GameUnfinished:
11806         if (appData.debugMode)
11807           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11808         p = strchr(yy_text, '{');
11809         if (p == NULL) p = strchr(yy_text, '(');
11810         if (p == NULL) {
11811             p = yy_text;
11812             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11813         } else {
11814             q = strchr(p, *p == '{' ? '}' : ')');
11815             if (q != NULL) *q = NULLCHAR;
11816             p++;
11817         }
11818         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11819         GameEnds(moveType, p, GE_FILE);
11820         done = TRUE;
11821         if (cmailMsgLoaded) {
11822             ClearHighlights();
11823             flipView = WhiteOnMove(currentMove);
11824             if (moveType == GameUnfinished) flipView = !flipView;
11825             if (appData.debugMode)
11826               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11827         }
11828         break;
11829
11830       case EndOfFile:
11831         if (appData.debugMode)
11832           fprintf(debugFP, "Parser hit end of file\n");
11833         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11834           case MT_NONE:
11835           case MT_CHECK:
11836             break;
11837           case MT_CHECKMATE:
11838           case MT_STAINMATE:
11839             if (WhiteOnMove(currentMove)) {
11840                 GameEnds(BlackWins, "Black mates", GE_FILE);
11841             } else {
11842                 GameEnds(WhiteWins, "White mates", GE_FILE);
11843             }
11844             break;
11845           case MT_STALEMATE:
11846             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11847             break;
11848         }
11849         done = TRUE;
11850         break;
11851
11852       case MoveNumberOne:
11853         if (lastLoadGameStart == GNUChessGame) {
11854             /* GNUChessGames have numbers, but they aren't move numbers */
11855             if (appData.debugMode)
11856               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11857                       yy_text, (int) moveType);
11858             return LoadGameOneMove(EndOfFile); /* tail recursion */
11859         }
11860         /* else fall thru */
11861
11862       case XBoardGame:
11863       case GNUChessGame:
11864       case PGNTag:
11865         /* Reached start of next game in file */
11866         if (appData.debugMode)
11867           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11868         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11869           case MT_NONE:
11870           case MT_CHECK:
11871             break;
11872           case MT_CHECKMATE:
11873           case MT_STAINMATE:
11874             if (WhiteOnMove(currentMove)) {
11875                 GameEnds(BlackWins, "Black mates", GE_FILE);
11876             } else {
11877                 GameEnds(WhiteWins, "White mates", GE_FILE);
11878             }
11879             break;
11880           case MT_STALEMATE:
11881             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11882             break;
11883         }
11884         done = TRUE;
11885         break;
11886
11887       case PositionDiagram:     /* should not happen; ignore */
11888       case ElapsedTime:         /* ignore */
11889       case NAG:                 /* ignore */
11890         if (appData.debugMode)
11891           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11892                   yy_text, (int) moveType);
11893         return LoadGameOneMove(EndOfFile); /* tail recursion */
11894
11895       case IllegalMove:
11896         if (appData.testLegality) {
11897             if (appData.debugMode)
11898               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11899             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11900                     (forwardMostMove / 2) + 1,
11901                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11902             DisplayError(move, 0);
11903             done = TRUE;
11904         } else {
11905             if (appData.debugMode)
11906               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11907                       yy_text, currentMoveString);
11908             fromX = currentMoveString[0] - AAA;
11909             fromY = currentMoveString[1] - ONE;
11910             toX = currentMoveString[2] - AAA;
11911             toY = currentMoveString[3] - ONE;
11912             promoChar = currentMoveString[4];
11913         }
11914         break;
11915
11916       case AmbiguousMove:
11917         if (appData.debugMode)
11918           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11919         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11920                 (forwardMostMove / 2) + 1,
11921                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11922         DisplayError(move, 0);
11923         done = TRUE;
11924         break;
11925
11926       default:
11927       case ImpossibleMove:
11928         if (appData.debugMode)
11929           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11930         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11931                 (forwardMostMove / 2) + 1,
11932                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11933         DisplayError(move, 0);
11934         done = TRUE;
11935         break;
11936     }
11937
11938     if (done) {
11939         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11940             DrawPosition(FALSE, boards[currentMove]);
11941             DisplayBothClocks();
11942             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11943               DisplayComment(currentMove - 1, commentList[currentMove]);
11944         }
11945         (void) StopLoadGameTimer();
11946         gameFileFP = NULL;
11947         cmailOldMove = forwardMostMove;
11948         return FALSE;
11949     } else {
11950         /* currentMoveString is set as a side-effect of yylex */
11951
11952         thinkOutput[0] = NULLCHAR;
11953         MakeMove(fromX, fromY, toX, toY, promoChar);
11954         killX = killY = -1; // [HGM] lion: used up
11955         currentMove = forwardMostMove;
11956         return TRUE;
11957     }
11958 }
11959
11960 /* Load the nth game from the given file */
11961 int
11962 LoadGameFromFile (char *filename, int n, char *title, int useList)
11963 {
11964     FILE *f;
11965     char buf[MSG_SIZ];
11966
11967     if (strcmp(filename, "-") == 0) {
11968         f = stdin;
11969         title = "stdin";
11970     } else {
11971         f = fopen(filename, "rb");
11972         if (f == NULL) {
11973           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11974             DisplayError(buf, errno);
11975             return FALSE;
11976         }
11977     }
11978     if (fseek(f, 0, 0) == -1) {
11979         /* f is not seekable; probably a pipe */
11980         useList = FALSE;
11981     }
11982     if (useList && n == 0) {
11983         int error = GameListBuild(f);
11984         if (error) {
11985             DisplayError(_("Cannot build game list"), error);
11986         } else if (!ListEmpty(&gameList) &&
11987                    ((ListGame *) gameList.tailPred)->number > 1) {
11988             GameListPopUp(f, title);
11989             return TRUE;
11990         }
11991         GameListDestroy();
11992         n = 1;
11993     }
11994     if (n == 0) n = 1;
11995     return LoadGame(f, n, title, FALSE);
11996 }
11997
11998
11999 void
12000 MakeRegisteredMove ()
12001 {
12002     int fromX, fromY, toX, toY;
12003     char promoChar;
12004     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12005         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12006           case CMAIL_MOVE:
12007           case CMAIL_DRAW:
12008             if (appData.debugMode)
12009               fprintf(debugFP, "Restoring %s for game %d\n",
12010                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12011
12012             thinkOutput[0] = NULLCHAR;
12013             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12014             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12015             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12016             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12017             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12018             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12019             MakeMove(fromX, fromY, toX, toY, promoChar);
12020             ShowMove(fromX, fromY, toX, toY);
12021
12022             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12023               case MT_NONE:
12024               case MT_CHECK:
12025                 break;
12026
12027               case MT_CHECKMATE:
12028               case MT_STAINMATE:
12029                 if (WhiteOnMove(currentMove)) {
12030                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12031                 } else {
12032                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12033                 }
12034                 break;
12035
12036               case MT_STALEMATE:
12037                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12038                 break;
12039             }
12040
12041             break;
12042
12043           case CMAIL_RESIGN:
12044             if (WhiteOnMove(currentMove)) {
12045                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12046             } else {
12047                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12048             }
12049             break;
12050
12051           case CMAIL_ACCEPT:
12052             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12053             break;
12054
12055           default:
12056             break;
12057         }
12058     }
12059
12060     return;
12061 }
12062
12063 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12064 int
12065 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12066 {
12067     int retVal;
12068
12069     if (gameNumber > nCmailGames) {
12070         DisplayError(_("No more games in this message"), 0);
12071         return FALSE;
12072     }
12073     if (f == lastLoadGameFP) {
12074         int offset = gameNumber - lastLoadGameNumber;
12075         if (offset == 0) {
12076             cmailMsg[0] = NULLCHAR;
12077             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12078                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12079                 nCmailMovesRegistered--;
12080             }
12081             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12082             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12083                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12084             }
12085         } else {
12086             if (! RegisterMove()) return FALSE;
12087         }
12088     }
12089
12090     retVal = LoadGame(f, gameNumber, title, useList);
12091
12092     /* Make move registered during previous look at this game, if any */
12093     MakeRegisteredMove();
12094
12095     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12096         commentList[currentMove]
12097           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12098         DisplayComment(currentMove - 1, commentList[currentMove]);
12099     }
12100
12101     return retVal;
12102 }
12103
12104 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12105 int
12106 ReloadGame (int offset)
12107 {
12108     int gameNumber = lastLoadGameNumber + offset;
12109     if (lastLoadGameFP == NULL) {
12110         DisplayError(_("No game has been loaded yet"), 0);
12111         return FALSE;
12112     }
12113     if (gameNumber <= 0) {
12114         DisplayError(_("Can't back up any further"), 0);
12115         return FALSE;
12116     }
12117     if (cmailMsgLoaded) {
12118         return CmailLoadGame(lastLoadGameFP, gameNumber,
12119                              lastLoadGameTitle, lastLoadGameUseList);
12120     } else {
12121         return LoadGame(lastLoadGameFP, gameNumber,
12122                         lastLoadGameTitle, lastLoadGameUseList);
12123     }
12124 }
12125
12126 int keys[EmptySquare+1];
12127
12128 int
12129 PositionMatches (Board b1, Board b2)
12130 {
12131     int r, f, sum=0;
12132     switch(appData.searchMode) {
12133         case 1: return CompareWithRights(b1, b2);
12134         case 2:
12135             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12136                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12137             }
12138             return TRUE;
12139         case 3:
12140             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12141               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12142                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12143             }
12144             return sum==0;
12145         case 4:
12146             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12147                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12148             }
12149             return sum==0;
12150     }
12151     return TRUE;
12152 }
12153
12154 #define Q_PROMO  4
12155 #define Q_EP     3
12156 #define Q_BCASTL 2
12157 #define Q_WCASTL 1
12158
12159 int pieceList[256], quickBoard[256];
12160 ChessSquare pieceType[256] = { EmptySquare };
12161 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12162 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12163 int soughtTotal, turn;
12164 Boolean epOK, flipSearch;
12165
12166 typedef struct {
12167     unsigned char piece, to;
12168 } Move;
12169
12170 #define DSIZE (250000)
12171
12172 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12173 Move *moveDatabase = initialSpace;
12174 unsigned int movePtr, dataSize = DSIZE;
12175
12176 int
12177 MakePieceList (Board board, int *counts)
12178 {
12179     int r, f, n=Q_PROMO, total=0;
12180     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12181     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12182         int sq = f + (r<<4);
12183         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12184             quickBoard[sq] = ++n;
12185             pieceList[n] = sq;
12186             pieceType[n] = board[r][f];
12187             counts[board[r][f]]++;
12188             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12189             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12190             total++;
12191         }
12192     }
12193     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12194     return total;
12195 }
12196
12197 void
12198 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12199 {
12200     int sq = fromX + (fromY<<4);
12201     int piece = quickBoard[sq], rook;
12202     quickBoard[sq] = 0;
12203     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12204     if(piece == pieceList[1] && fromY == toY) {
12205       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12206         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12207         moveDatabase[movePtr++].piece = Q_WCASTL;
12208         quickBoard[sq] = piece;
12209         piece = quickBoard[from]; quickBoard[from] = 0;
12210         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12211       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12212         quickBoard[sq] = 0; // remove Rook
12213         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12214         moveDatabase[movePtr++].piece = Q_WCASTL;
12215         quickBoard[sq] = pieceList[1]; // put King
12216         piece = rook;
12217         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12218       }
12219     } else
12220     if(piece == pieceList[2] && fromY == toY) {
12221       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12222         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12223         moveDatabase[movePtr++].piece = Q_BCASTL;
12224         quickBoard[sq] = piece;
12225         piece = quickBoard[from]; quickBoard[from] = 0;
12226         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12227       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12228         quickBoard[sq] = 0; // remove Rook
12229         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12230         moveDatabase[movePtr++].piece = Q_BCASTL;
12231         quickBoard[sq] = pieceList[2]; // put King
12232         piece = rook;
12233         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12234       }
12235     } else
12236     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12237         quickBoard[(fromY<<4)+toX] = 0;
12238         moveDatabase[movePtr].piece = Q_EP;
12239         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12240         moveDatabase[movePtr].to = sq;
12241     } else
12242     if(promoPiece != pieceType[piece]) {
12243         moveDatabase[movePtr++].piece = Q_PROMO;
12244         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12245     }
12246     moveDatabase[movePtr].piece = piece;
12247     quickBoard[sq] = piece;
12248     movePtr++;
12249 }
12250
12251 int
12252 PackGame (Board board)
12253 {
12254     Move *newSpace = NULL;
12255     moveDatabase[movePtr].piece = 0; // terminate previous game
12256     if(movePtr > dataSize) {
12257         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12258         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12259         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12260         if(newSpace) {
12261             int i;
12262             Move *p = moveDatabase, *q = newSpace;
12263             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12264             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12265             moveDatabase = newSpace;
12266         } else { // calloc failed, we must be out of memory. Too bad...
12267             dataSize = 0; // prevent calloc events for all subsequent games
12268             return 0;     // and signal this one isn't cached
12269         }
12270     }
12271     movePtr++;
12272     MakePieceList(board, counts);
12273     return movePtr;
12274 }
12275
12276 int
12277 QuickCompare (Board board, int *minCounts, int *maxCounts)
12278 {   // compare according to search mode
12279     int r, f;
12280     switch(appData.searchMode)
12281     {
12282       case 1: // exact position match
12283         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12284         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12285             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12286         }
12287         break;
12288       case 2: // can have extra material on empty squares
12289         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12290             if(board[r][f] == EmptySquare) continue;
12291             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12292         }
12293         break;
12294       case 3: // material with exact Pawn structure
12295         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12296             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12297             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12298         } // fall through to material comparison
12299       case 4: // exact material
12300         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12301         break;
12302       case 6: // material range with given imbalance
12303         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12304         // fall through to range comparison
12305       case 5: // material range
12306         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12307     }
12308     return TRUE;
12309 }
12310
12311 int
12312 QuickScan (Board board, Move *move)
12313 {   // reconstruct game,and compare all positions in it
12314     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12315     do {
12316         int piece = move->piece;
12317         int to = move->to, from = pieceList[piece];
12318         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12319           if(!piece) return -1;
12320           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12321             piece = (++move)->piece;
12322             from = pieceList[piece];
12323             counts[pieceType[piece]]--;
12324             pieceType[piece] = (ChessSquare) move->to;
12325             counts[move->to]++;
12326           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12327             counts[pieceType[quickBoard[to]]]--;
12328             quickBoard[to] = 0; total--;
12329             move++;
12330             continue;
12331           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12332             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12333             from  = pieceList[piece]; // so this must be King
12334             quickBoard[from] = 0;
12335             pieceList[piece] = to;
12336             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12337             quickBoard[from] = 0; // rook
12338             quickBoard[to] = piece;
12339             to = move->to; piece = move->piece;
12340             goto aftercastle;
12341           }
12342         }
12343         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12344         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12345         quickBoard[from] = 0;
12346       aftercastle:
12347         quickBoard[to] = piece;
12348         pieceList[piece] = to;
12349         cnt++; turn ^= 3;
12350         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12351            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12352            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12353                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12354           ) {
12355             static int lastCounts[EmptySquare+1];
12356             int i;
12357             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12358             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12359         } else stretch = 0;
12360         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12361         move++;
12362     } while(1);
12363 }
12364
12365 void
12366 InitSearch ()
12367 {
12368     int r, f;
12369     flipSearch = FALSE;
12370     CopyBoard(soughtBoard, boards[currentMove]);
12371     soughtTotal = MakePieceList(soughtBoard, maxSought);
12372     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12373     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12374     CopyBoard(reverseBoard, boards[currentMove]);
12375     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12376         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12377         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12378         reverseBoard[r][f] = piece;
12379     }
12380     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12381     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12382     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12383                  || (boards[currentMove][CASTLING][2] == NoRights ||
12384                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12385                  && (boards[currentMove][CASTLING][5] == NoRights ||
12386                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12387       ) {
12388         flipSearch = TRUE;
12389         CopyBoard(flipBoard, soughtBoard);
12390         CopyBoard(rotateBoard, reverseBoard);
12391         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12392             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12393             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12394         }
12395     }
12396     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12397     if(appData.searchMode >= 5) {
12398         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12399         MakePieceList(soughtBoard, minSought);
12400         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12401     }
12402     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12403         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12404 }
12405
12406 GameInfo dummyInfo;
12407 static int creatingBook;
12408
12409 int
12410 GameContainsPosition (FILE *f, ListGame *lg)
12411 {
12412     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12413     int fromX, fromY, toX, toY;
12414     char promoChar;
12415     static int initDone=FALSE;
12416
12417     // weed out games based on numerical tag comparison
12418     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12419     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12420     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12421     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12422     if(!initDone) {
12423         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12424         initDone = TRUE;
12425     }
12426     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12427     else CopyBoard(boards[scratch], initialPosition); // default start position
12428     if(lg->moves) {
12429         turn = btm + 1;
12430         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12431         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12432     }
12433     if(btm) plyNr++;
12434     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12435     fseek(f, lg->offset, 0);
12436     yynewfile(f);
12437     while(1) {
12438         yyboardindex = scratch;
12439         quickFlag = plyNr+1;
12440         next = Myylex();
12441         quickFlag = 0;
12442         switch(next) {
12443             case PGNTag:
12444                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12445             default:
12446                 continue;
12447
12448             case XBoardGame:
12449             case GNUChessGame:
12450                 if(plyNr) return -1; // after we have seen moves, this is for new game
12451               continue;
12452
12453             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12454             case ImpossibleMove:
12455             case WhiteWins: // game ends here with these four
12456             case BlackWins:
12457             case GameIsDrawn:
12458             case GameUnfinished:
12459                 return -1;
12460
12461             case IllegalMove:
12462                 if(appData.testLegality) return -1;
12463             case WhiteCapturesEnPassant:
12464             case BlackCapturesEnPassant:
12465             case WhitePromotion:
12466             case BlackPromotion:
12467             case WhiteNonPromotion:
12468             case BlackNonPromotion:
12469             case NormalMove:
12470             case FirstLeg:
12471             case WhiteKingSideCastle:
12472             case WhiteQueenSideCastle:
12473             case BlackKingSideCastle:
12474             case BlackQueenSideCastle:
12475             case WhiteKingSideCastleWild:
12476             case WhiteQueenSideCastleWild:
12477             case BlackKingSideCastleWild:
12478             case BlackQueenSideCastleWild:
12479             case WhiteHSideCastleFR:
12480             case WhiteASideCastleFR:
12481             case BlackHSideCastleFR:
12482             case BlackASideCastleFR:
12483                 fromX = currentMoveString[0] - AAA;
12484                 fromY = currentMoveString[1] - ONE;
12485                 toX = currentMoveString[2] - AAA;
12486                 toY = currentMoveString[3] - ONE;
12487                 promoChar = currentMoveString[4];
12488                 break;
12489             case WhiteDrop:
12490             case BlackDrop:
12491                 fromX = next == WhiteDrop ?
12492                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12493                   (int) CharToPiece(ToLower(currentMoveString[0]));
12494                 fromY = DROP_RANK;
12495                 toX = currentMoveString[2] - AAA;
12496                 toY = currentMoveString[3] - ONE;
12497                 promoChar = 0;
12498                 break;
12499         }
12500         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12501         plyNr++;
12502         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12503         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12504         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12505         if(appData.findMirror) {
12506             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12507             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12508         }
12509     }
12510 }
12511
12512 /* Load the nth game from open file f */
12513 int
12514 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12515 {
12516     ChessMove cm;
12517     char buf[MSG_SIZ];
12518     int gn = gameNumber;
12519     ListGame *lg = NULL;
12520     int numPGNTags = 0;
12521     int err, pos = -1;
12522     GameMode oldGameMode;
12523     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12524
12525     if (appData.debugMode)
12526         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12527
12528     if (gameMode == Training )
12529         SetTrainingModeOff();
12530
12531     oldGameMode = gameMode;
12532     if (gameMode != BeginningOfGame) {
12533       Reset(FALSE, TRUE);
12534     }
12535     killX = killY = -1; // [HGM] lion: in case we did not Reset
12536
12537     gameFileFP = f;
12538     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12539         fclose(lastLoadGameFP);
12540     }
12541
12542     if (useList) {
12543         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12544
12545         if (lg) {
12546             fseek(f, lg->offset, 0);
12547             GameListHighlight(gameNumber);
12548             pos = lg->position;
12549             gn = 1;
12550         }
12551         else {
12552             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12553               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12554             else
12555             DisplayError(_("Game number out of range"), 0);
12556             return FALSE;
12557         }
12558     } else {
12559         GameListDestroy();
12560         if (fseek(f, 0, 0) == -1) {
12561             if (f == lastLoadGameFP ?
12562                 gameNumber == lastLoadGameNumber + 1 :
12563                 gameNumber == 1) {
12564                 gn = 1;
12565             } else {
12566                 DisplayError(_("Can't seek on game file"), 0);
12567                 return FALSE;
12568             }
12569         }
12570     }
12571     lastLoadGameFP = f;
12572     lastLoadGameNumber = gameNumber;
12573     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12574     lastLoadGameUseList = useList;
12575
12576     yynewfile(f);
12577
12578     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12579       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12580                 lg->gameInfo.black);
12581             DisplayTitle(buf);
12582     } else if (*title != NULLCHAR) {
12583         if (gameNumber > 1) {
12584           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12585             DisplayTitle(buf);
12586         } else {
12587             DisplayTitle(title);
12588         }
12589     }
12590
12591     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12592         gameMode = PlayFromGameFile;
12593         ModeHighlight();
12594     }
12595
12596     currentMove = forwardMostMove = backwardMostMove = 0;
12597     CopyBoard(boards[0], initialPosition);
12598     StopClocks();
12599
12600     /*
12601      * Skip the first gn-1 games in the file.
12602      * Also skip over anything that precedes an identifiable
12603      * start of game marker, to avoid being confused by
12604      * garbage at the start of the file.  Currently
12605      * recognized start of game markers are the move number "1",
12606      * the pattern "gnuchess .* game", the pattern
12607      * "^[#;%] [^ ]* game file", and a PGN tag block.
12608      * A game that starts with one of the latter two patterns
12609      * will also have a move number 1, possibly
12610      * following a position diagram.
12611      * 5-4-02: Let's try being more lenient and allowing a game to
12612      * start with an unnumbered move.  Does that break anything?
12613      */
12614     cm = lastLoadGameStart = EndOfFile;
12615     while (gn > 0) {
12616         yyboardindex = forwardMostMove;
12617         cm = (ChessMove) Myylex();
12618         switch (cm) {
12619           case EndOfFile:
12620             if (cmailMsgLoaded) {
12621                 nCmailGames = CMAIL_MAX_GAMES - gn;
12622             } else {
12623                 Reset(TRUE, TRUE);
12624                 DisplayError(_("Game not found in file"), 0);
12625             }
12626             return FALSE;
12627
12628           case GNUChessGame:
12629           case XBoardGame:
12630             gn--;
12631             lastLoadGameStart = cm;
12632             break;
12633
12634           case MoveNumberOne:
12635             switch (lastLoadGameStart) {
12636               case GNUChessGame:
12637               case XBoardGame:
12638               case PGNTag:
12639                 break;
12640               case MoveNumberOne:
12641               case EndOfFile:
12642                 gn--;           /* count this game */
12643                 lastLoadGameStart = cm;
12644                 break;
12645               default:
12646                 /* impossible */
12647                 break;
12648             }
12649             break;
12650
12651           case PGNTag:
12652             switch (lastLoadGameStart) {
12653               case GNUChessGame:
12654               case PGNTag:
12655               case MoveNumberOne:
12656               case EndOfFile:
12657                 gn--;           /* count this game */
12658                 lastLoadGameStart = cm;
12659                 break;
12660               case XBoardGame:
12661                 lastLoadGameStart = cm; /* game counted already */
12662                 break;
12663               default:
12664                 /* impossible */
12665                 break;
12666             }
12667             if (gn > 0) {
12668                 do {
12669                     yyboardindex = forwardMostMove;
12670                     cm = (ChessMove) Myylex();
12671                 } while (cm == PGNTag || cm == Comment);
12672             }
12673             break;
12674
12675           case WhiteWins:
12676           case BlackWins:
12677           case GameIsDrawn:
12678             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12679                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12680                     != CMAIL_OLD_RESULT) {
12681                     nCmailResults ++ ;
12682                     cmailResult[  CMAIL_MAX_GAMES
12683                                 - gn - 1] = CMAIL_OLD_RESULT;
12684                 }
12685             }
12686             break;
12687
12688           case NormalMove:
12689           case FirstLeg:
12690             /* Only a NormalMove can be at the start of a game
12691              * without a position diagram. */
12692             if (lastLoadGameStart == EndOfFile ) {
12693               gn--;
12694               lastLoadGameStart = MoveNumberOne;
12695             }
12696             break;
12697
12698           default:
12699             break;
12700         }
12701     }
12702
12703     if (appData.debugMode)
12704       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12705
12706     if (cm == XBoardGame) {
12707         /* Skip any header junk before position diagram and/or move 1 */
12708         for (;;) {
12709             yyboardindex = forwardMostMove;
12710             cm = (ChessMove) Myylex();
12711
12712             if (cm == EndOfFile ||
12713                 cm == GNUChessGame || cm == XBoardGame) {
12714                 /* Empty game; pretend end-of-file and handle later */
12715                 cm = EndOfFile;
12716                 break;
12717             }
12718
12719             if (cm == MoveNumberOne || cm == PositionDiagram ||
12720                 cm == PGNTag || cm == Comment)
12721               break;
12722         }
12723     } else if (cm == GNUChessGame) {
12724         if (gameInfo.event != NULL) {
12725             free(gameInfo.event);
12726         }
12727         gameInfo.event = StrSave(yy_text);
12728     }
12729
12730     startedFromSetupPosition = FALSE;
12731     while (cm == PGNTag) {
12732         if (appData.debugMode)
12733           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12734         err = ParsePGNTag(yy_text, &gameInfo);
12735         if (!err) numPGNTags++;
12736
12737         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12738         if(gameInfo.variant != oldVariant) {
12739             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12740             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12741             InitPosition(TRUE);
12742             oldVariant = gameInfo.variant;
12743             if (appData.debugMode)
12744               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12745         }
12746
12747
12748         if (gameInfo.fen != NULL) {
12749           Board initial_position;
12750           startedFromSetupPosition = TRUE;
12751           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12752             Reset(TRUE, TRUE);
12753             DisplayError(_("Bad FEN position in file"), 0);
12754             return FALSE;
12755           }
12756           CopyBoard(boards[0], initial_position);
12757           if (blackPlaysFirst) {
12758             currentMove = forwardMostMove = backwardMostMove = 1;
12759             CopyBoard(boards[1], initial_position);
12760             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12761             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12762             timeRemaining[0][1] = whiteTimeRemaining;
12763             timeRemaining[1][1] = blackTimeRemaining;
12764             if (commentList[0] != NULL) {
12765               commentList[1] = commentList[0];
12766               commentList[0] = NULL;
12767             }
12768           } else {
12769             currentMove = forwardMostMove = backwardMostMove = 0;
12770           }
12771           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12772           {   int i;
12773               initialRulePlies = FENrulePlies;
12774               for( i=0; i< nrCastlingRights; i++ )
12775                   initialRights[i] = initial_position[CASTLING][i];
12776           }
12777           yyboardindex = forwardMostMove;
12778           free(gameInfo.fen);
12779           gameInfo.fen = NULL;
12780         }
12781
12782         yyboardindex = forwardMostMove;
12783         cm = (ChessMove) Myylex();
12784
12785         /* Handle comments interspersed among the tags */
12786         while (cm == Comment) {
12787             char *p;
12788             if (appData.debugMode)
12789               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12790             p = yy_text;
12791             AppendComment(currentMove, p, FALSE);
12792             yyboardindex = forwardMostMove;
12793             cm = (ChessMove) Myylex();
12794         }
12795     }
12796
12797     /* don't rely on existence of Event tag since if game was
12798      * pasted from clipboard the Event tag may not exist
12799      */
12800     if (numPGNTags > 0){
12801         char *tags;
12802         if (gameInfo.variant == VariantNormal) {
12803           VariantClass v = StringToVariant(gameInfo.event);
12804           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12805           if(v < VariantShogi) gameInfo.variant = v;
12806         }
12807         if (!matchMode) {
12808           if( appData.autoDisplayTags ) {
12809             tags = PGNTags(&gameInfo);
12810             TagsPopUp(tags, CmailMsg());
12811             free(tags);
12812           }
12813         }
12814     } else {
12815         /* Make something up, but don't display it now */
12816         SetGameInfo();
12817         TagsPopDown();
12818     }
12819
12820     if (cm == PositionDiagram) {
12821         int i, j;
12822         char *p;
12823         Board initial_position;
12824
12825         if (appData.debugMode)
12826           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12827
12828         if (!startedFromSetupPosition) {
12829             p = yy_text;
12830             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12831               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12832                 switch (*p) {
12833                   case '{':
12834                   case '[':
12835                   case '-':
12836                   case ' ':
12837                   case '\t':
12838                   case '\n':
12839                   case '\r':
12840                     break;
12841                   default:
12842                     initial_position[i][j++] = CharToPiece(*p);
12843                     break;
12844                 }
12845             while (*p == ' ' || *p == '\t' ||
12846                    *p == '\n' || *p == '\r') p++;
12847
12848             if (strncmp(p, "black", strlen("black"))==0)
12849               blackPlaysFirst = TRUE;
12850             else
12851               blackPlaysFirst = FALSE;
12852             startedFromSetupPosition = TRUE;
12853
12854             CopyBoard(boards[0], initial_position);
12855             if (blackPlaysFirst) {
12856                 currentMove = forwardMostMove = backwardMostMove = 1;
12857                 CopyBoard(boards[1], initial_position);
12858                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12859                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12860                 timeRemaining[0][1] = whiteTimeRemaining;
12861                 timeRemaining[1][1] = blackTimeRemaining;
12862                 if (commentList[0] != NULL) {
12863                     commentList[1] = commentList[0];
12864                     commentList[0] = NULL;
12865                 }
12866             } else {
12867                 currentMove = forwardMostMove = backwardMostMove = 0;
12868             }
12869         }
12870         yyboardindex = forwardMostMove;
12871         cm = (ChessMove) Myylex();
12872     }
12873
12874   if(!creatingBook) {
12875     if (first.pr == NoProc) {
12876         StartChessProgram(&first);
12877     }
12878     InitChessProgram(&first, FALSE);
12879     SendToProgram("force\n", &first);
12880     if (startedFromSetupPosition) {
12881         SendBoard(&first, forwardMostMove);
12882     if (appData.debugMode) {
12883         fprintf(debugFP, "Load Game\n");
12884     }
12885         DisplayBothClocks();
12886     }
12887   }
12888
12889     /* [HGM] server: flag to write setup moves in broadcast file as one */
12890     loadFlag = appData.suppressLoadMoves;
12891
12892     while (cm == Comment) {
12893         char *p;
12894         if (appData.debugMode)
12895           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12896         p = yy_text;
12897         AppendComment(currentMove, p, FALSE);
12898         yyboardindex = forwardMostMove;
12899         cm = (ChessMove) Myylex();
12900     }
12901
12902     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12903         cm == WhiteWins || cm == BlackWins ||
12904         cm == GameIsDrawn || cm == GameUnfinished) {
12905         DisplayMessage("", _("No moves in game"));
12906         if (cmailMsgLoaded) {
12907             if (appData.debugMode)
12908               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12909             ClearHighlights();
12910             flipView = FALSE;
12911         }
12912         DrawPosition(FALSE, boards[currentMove]);
12913         DisplayBothClocks();
12914         gameMode = EditGame;
12915         ModeHighlight();
12916         gameFileFP = NULL;
12917         cmailOldMove = 0;
12918         return TRUE;
12919     }
12920
12921     // [HGM] PV info: routine tests if comment empty
12922     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12923         DisplayComment(currentMove - 1, commentList[currentMove]);
12924     }
12925     if (!matchMode && appData.timeDelay != 0)
12926       DrawPosition(FALSE, boards[currentMove]);
12927
12928     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12929       programStats.ok_to_send = 1;
12930     }
12931
12932     /* if the first token after the PGN tags is a move
12933      * and not move number 1, retrieve it from the parser
12934      */
12935     if (cm != MoveNumberOne)
12936         LoadGameOneMove(cm);
12937
12938     /* load the remaining moves from the file */
12939     while (LoadGameOneMove(EndOfFile)) {
12940       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12941       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12942     }
12943
12944     /* rewind to the start of the game */
12945     currentMove = backwardMostMove;
12946
12947     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12948
12949     if (oldGameMode == AnalyzeFile) {
12950       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12951       AnalyzeFileEvent();
12952     } else
12953     if (oldGameMode == AnalyzeMode) {
12954       AnalyzeFileEvent();
12955     }
12956
12957     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12958         long int w, b; // [HGM] adjourn: restore saved clock times
12959         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12960         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12961             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12962             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12963         }
12964     }
12965
12966     if(creatingBook) return TRUE;
12967     if (!matchMode && pos > 0) {
12968         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12969     } else
12970     if (matchMode || appData.timeDelay == 0) {
12971       ToEndEvent();
12972     } else if (appData.timeDelay > 0) {
12973       AutoPlayGameLoop();
12974     }
12975
12976     if (appData.debugMode)
12977         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12978
12979     loadFlag = 0; /* [HGM] true game starts */
12980     return TRUE;
12981 }
12982
12983 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12984 int
12985 ReloadPosition (int offset)
12986 {
12987     int positionNumber = lastLoadPositionNumber + offset;
12988     if (lastLoadPositionFP == NULL) {
12989         DisplayError(_("No position has been loaded yet"), 0);
12990         return FALSE;
12991     }
12992     if (positionNumber <= 0) {
12993         DisplayError(_("Can't back up any further"), 0);
12994         return FALSE;
12995     }
12996     return LoadPosition(lastLoadPositionFP, positionNumber,
12997                         lastLoadPositionTitle);
12998 }
12999
13000 /* Load the nth position from the given file */
13001 int
13002 LoadPositionFromFile (char *filename, int n, char *title)
13003 {
13004     FILE *f;
13005     char buf[MSG_SIZ];
13006
13007     if (strcmp(filename, "-") == 0) {
13008         return LoadPosition(stdin, n, "stdin");
13009     } else {
13010         f = fopen(filename, "rb");
13011         if (f == NULL) {
13012             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13013             DisplayError(buf, errno);
13014             return FALSE;
13015         } else {
13016             return LoadPosition(f, n, title);
13017         }
13018     }
13019 }
13020
13021 /* Load the nth position from the given open file, and close it */
13022 int
13023 LoadPosition (FILE *f, int positionNumber, char *title)
13024 {
13025     char *p, line[MSG_SIZ];
13026     Board initial_position;
13027     int i, j, fenMode, pn;
13028
13029     if (gameMode == Training )
13030         SetTrainingModeOff();
13031
13032     if (gameMode != BeginningOfGame) {
13033         Reset(FALSE, TRUE);
13034     }
13035     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13036         fclose(lastLoadPositionFP);
13037     }
13038     if (positionNumber == 0) positionNumber = 1;
13039     lastLoadPositionFP = f;
13040     lastLoadPositionNumber = positionNumber;
13041     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13042     if (first.pr == NoProc && !appData.noChessProgram) {
13043       StartChessProgram(&first);
13044       InitChessProgram(&first, FALSE);
13045     }
13046     pn = positionNumber;
13047     if (positionNumber < 0) {
13048         /* Negative position number means to seek to that byte offset */
13049         if (fseek(f, -positionNumber, 0) == -1) {
13050             DisplayError(_("Can't seek on position file"), 0);
13051             return FALSE;
13052         };
13053         pn = 1;
13054     } else {
13055         if (fseek(f, 0, 0) == -1) {
13056             if (f == lastLoadPositionFP ?
13057                 positionNumber == lastLoadPositionNumber + 1 :
13058                 positionNumber == 1) {
13059                 pn = 1;
13060             } else {
13061                 DisplayError(_("Can't seek on position file"), 0);
13062                 return FALSE;
13063             }
13064         }
13065     }
13066     /* See if this file is FEN or old-style xboard */
13067     if (fgets(line, MSG_SIZ, f) == NULL) {
13068         DisplayError(_("Position not found in file"), 0);
13069         return FALSE;
13070     }
13071     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13072     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13073
13074     if (pn >= 2) {
13075         if (fenMode || line[0] == '#') pn--;
13076         while (pn > 0) {
13077             /* skip positions before number pn */
13078             if (fgets(line, MSG_SIZ, f) == NULL) {
13079                 Reset(TRUE, TRUE);
13080                 DisplayError(_("Position not found in file"), 0);
13081                 return FALSE;
13082             }
13083             if (fenMode || line[0] == '#') pn--;
13084         }
13085     }
13086
13087     if (fenMode) {
13088         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13089             DisplayError(_("Bad FEN position in file"), 0);
13090             return FALSE;
13091         }
13092     } else {
13093         (void) fgets(line, MSG_SIZ, f);
13094         (void) fgets(line, MSG_SIZ, f);
13095
13096         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13097             (void) fgets(line, MSG_SIZ, f);
13098             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13099                 if (*p == ' ')
13100                   continue;
13101                 initial_position[i][j++] = CharToPiece(*p);
13102             }
13103         }
13104
13105         blackPlaysFirst = FALSE;
13106         if (!feof(f)) {
13107             (void) fgets(line, MSG_SIZ, f);
13108             if (strncmp(line, "black", strlen("black"))==0)
13109               blackPlaysFirst = TRUE;
13110         }
13111     }
13112     startedFromSetupPosition = TRUE;
13113
13114     CopyBoard(boards[0], initial_position);
13115     if (blackPlaysFirst) {
13116         currentMove = forwardMostMove = backwardMostMove = 1;
13117         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13118         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13119         CopyBoard(boards[1], initial_position);
13120         DisplayMessage("", _("Black to play"));
13121     } else {
13122         currentMove = forwardMostMove = backwardMostMove = 0;
13123         DisplayMessage("", _("White to play"));
13124     }
13125     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13126     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13127         SendToProgram("force\n", &first);
13128         SendBoard(&first, forwardMostMove);
13129     }
13130     if (appData.debugMode) {
13131 int i, j;
13132   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13133   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13134         fprintf(debugFP, "Load Position\n");
13135     }
13136
13137     if (positionNumber > 1) {
13138       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13139         DisplayTitle(line);
13140     } else {
13141         DisplayTitle(title);
13142     }
13143     gameMode = EditGame;
13144     ModeHighlight();
13145     ResetClocks();
13146     timeRemaining[0][1] = whiteTimeRemaining;
13147     timeRemaining[1][1] = blackTimeRemaining;
13148     DrawPosition(FALSE, boards[currentMove]);
13149
13150     return TRUE;
13151 }
13152
13153
13154 void
13155 CopyPlayerNameIntoFileName (char **dest, char *src)
13156 {
13157     while (*src != NULLCHAR && *src != ',') {
13158         if (*src == ' ') {
13159             *(*dest)++ = '_';
13160             src++;
13161         } else {
13162             *(*dest)++ = *src++;
13163         }
13164     }
13165 }
13166
13167 char *
13168 DefaultFileName (char *ext)
13169 {
13170     static char def[MSG_SIZ];
13171     char *p;
13172
13173     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13174         p = def;
13175         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13176         *p++ = '-';
13177         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13178         *p++ = '.';
13179         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13180     } else {
13181         def[0] = NULLCHAR;
13182     }
13183     return def;
13184 }
13185
13186 /* Save the current game to the given file */
13187 int
13188 SaveGameToFile (char *filename, int append)
13189 {
13190     FILE *f;
13191     char buf[MSG_SIZ];
13192     int result, i, t,tot=0;
13193
13194     if (strcmp(filename, "-") == 0) {
13195         return SaveGame(stdout, 0, NULL);
13196     } else {
13197         for(i=0; i<10; i++) { // upto 10 tries
13198              f = fopen(filename, append ? "a" : "w");
13199              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13200              if(f || errno != 13) break;
13201              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13202              tot += t;
13203         }
13204         if (f == NULL) {
13205             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13206             DisplayError(buf, errno);
13207             return FALSE;
13208         } else {
13209             safeStrCpy(buf, lastMsg, MSG_SIZ);
13210             DisplayMessage(_("Waiting for access to save file"), "");
13211             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13212             DisplayMessage(_("Saving game"), "");
13213             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13214             result = SaveGame(f, 0, NULL);
13215             DisplayMessage(buf, "");
13216             return result;
13217         }
13218     }
13219 }
13220
13221 char *
13222 SavePart (char *str)
13223 {
13224     static char buf[MSG_SIZ];
13225     char *p;
13226
13227     p = strchr(str, ' ');
13228     if (p == NULL) return str;
13229     strncpy(buf, str, p - str);
13230     buf[p - str] = NULLCHAR;
13231     return buf;
13232 }
13233
13234 #define PGN_MAX_LINE 75
13235
13236 #define PGN_SIDE_WHITE  0
13237 #define PGN_SIDE_BLACK  1
13238
13239 static int
13240 FindFirstMoveOutOfBook (int side)
13241 {
13242     int result = -1;
13243
13244     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13245         int index = backwardMostMove;
13246         int has_book_hit = 0;
13247
13248         if( (index % 2) != side ) {
13249             index++;
13250         }
13251
13252         while( index < forwardMostMove ) {
13253             /* Check to see if engine is in book */
13254             int depth = pvInfoList[index].depth;
13255             int score = pvInfoList[index].score;
13256             int in_book = 0;
13257
13258             if( depth <= 2 ) {
13259                 in_book = 1;
13260             }
13261             else if( score == 0 && depth == 63 ) {
13262                 in_book = 1; /* Zappa */
13263             }
13264             else if( score == 2 && depth == 99 ) {
13265                 in_book = 1; /* Abrok */
13266             }
13267
13268             has_book_hit += in_book;
13269
13270             if( ! in_book ) {
13271                 result = index;
13272
13273                 break;
13274             }
13275
13276             index += 2;
13277         }
13278     }
13279
13280     return result;
13281 }
13282
13283 void
13284 GetOutOfBookInfo (char * buf)
13285 {
13286     int oob[2];
13287     int i;
13288     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13289
13290     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13291     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13292
13293     *buf = '\0';
13294
13295     if( oob[0] >= 0 || oob[1] >= 0 ) {
13296         for( i=0; i<2; i++ ) {
13297             int idx = oob[i];
13298
13299             if( idx >= 0 ) {
13300                 if( i > 0 && oob[0] >= 0 ) {
13301                     strcat( buf, "   " );
13302                 }
13303
13304                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13305                 sprintf( buf+strlen(buf), "%s%.2f",
13306                     pvInfoList[idx].score >= 0 ? "+" : "",
13307                     pvInfoList[idx].score / 100.0 );
13308             }
13309         }
13310     }
13311 }
13312
13313 /* Save game in PGN style and close the file */
13314 int
13315 SaveGamePGN (FILE *f)
13316 {
13317     int i, offset, linelen, newblock;
13318 //    char *movetext;
13319     char numtext[32];
13320     int movelen, numlen, blank;
13321     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13322
13323     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13324
13325     PrintPGNTags(f, &gameInfo);
13326
13327     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13328
13329     if (backwardMostMove > 0 || startedFromSetupPosition) {
13330         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13331         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13332         fprintf(f, "\n{--------------\n");
13333         PrintPosition(f, backwardMostMove);
13334         fprintf(f, "--------------}\n");
13335         free(fen);
13336     }
13337     else {
13338         /* [AS] Out of book annotation */
13339         if( appData.saveOutOfBookInfo ) {
13340             char buf[64];
13341
13342             GetOutOfBookInfo( buf );
13343
13344             if( buf[0] != '\0' ) {
13345                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13346             }
13347         }
13348
13349         fprintf(f, "\n");
13350     }
13351
13352     i = backwardMostMove;
13353     linelen = 0;
13354     newblock = TRUE;
13355
13356     while (i < forwardMostMove) {
13357         /* Print comments preceding this move */
13358         if (commentList[i] != NULL) {
13359             if (linelen > 0) fprintf(f, "\n");
13360             fprintf(f, "%s", commentList[i]);
13361             linelen = 0;
13362             newblock = TRUE;
13363         }
13364
13365         /* Format move number */
13366         if ((i % 2) == 0)
13367           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13368         else
13369           if (newblock)
13370             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13371           else
13372             numtext[0] = NULLCHAR;
13373
13374         numlen = strlen(numtext);
13375         newblock = FALSE;
13376
13377         /* Print move number */
13378         blank = linelen > 0 && numlen > 0;
13379         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13380             fprintf(f, "\n");
13381             linelen = 0;
13382             blank = 0;
13383         }
13384         if (blank) {
13385             fprintf(f, " ");
13386             linelen++;
13387         }
13388         fprintf(f, "%s", numtext);
13389         linelen += numlen;
13390
13391         /* Get move */
13392         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13393         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13394
13395         /* Print move */
13396         blank = linelen > 0 && movelen > 0;
13397         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13398             fprintf(f, "\n");
13399             linelen = 0;
13400             blank = 0;
13401         }
13402         if (blank) {
13403             fprintf(f, " ");
13404             linelen++;
13405         }
13406         fprintf(f, "%s", move_buffer);
13407         linelen += movelen;
13408
13409         /* [AS] Add PV info if present */
13410         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13411             /* [HGM] add time */
13412             char buf[MSG_SIZ]; int seconds;
13413
13414             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13415
13416             if( seconds <= 0)
13417               buf[0] = 0;
13418             else
13419               if( seconds < 30 )
13420                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13421               else
13422                 {
13423                   seconds = (seconds + 4)/10; // round to full seconds
13424                   if( seconds < 60 )
13425                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13426                   else
13427                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13428                 }
13429
13430             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13431                       pvInfoList[i].score >= 0 ? "+" : "",
13432                       pvInfoList[i].score / 100.0,
13433                       pvInfoList[i].depth,
13434                       buf );
13435
13436             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13437
13438             /* Print score/depth */
13439             blank = linelen > 0 && movelen > 0;
13440             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13441                 fprintf(f, "\n");
13442                 linelen = 0;
13443                 blank = 0;
13444             }
13445             if (blank) {
13446                 fprintf(f, " ");
13447                 linelen++;
13448             }
13449             fprintf(f, "%s", move_buffer);
13450             linelen += movelen;
13451         }
13452
13453         i++;
13454     }
13455
13456     /* Start a new line */
13457     if (linelen > 0) fprintf(f, "\n");
13458
13459     /* Print comments after last move */
13460     if (commentList[i] != NULL) {
13461         fprintf(f, "%s\n", commentList[i]);
13462     }
13463
13464     /* Print result */
13465     if (gameInfo.resultDetails != NULL &&
13466         gameInfo.resultDetails[0] != NULLCHAR) {
13467         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13468         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13469            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13470             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13471         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13472     } else {
13473         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13474     }
13475
13476     fclose(f);
13477     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13478     return TRUE;
13479 }
13480
13481 /* Save game in old style and close the file */
13482 int
13483 SaveGameOldStyle (FILE *f)
13484 {
13485     int i, offset;
13486     time_t tm;
13487
13488     tm = time((time_t *) NULL);
13489
13490     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13491     PrintOpponents(f);
13492
13493     if (backwardMostMove > 0 || startedFromSetupPosition) {
13494         fprintf(f, "\n[--------------\n");
13495         PrintPosition(f, backwardMostMove);
13496         fprintf(f, "--------------]\n");
13497     } else {
13498         fprintf(f, "\n");
13499     }
13500
13501     i = backwardMostMove;
13502     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13503
13504     while (i < forwardMostMove) {
13505         if (commentList[i] != NULL) {
13506             fprintf(f, "[%s]\n", commentList[i]);
13507         }
13508
13509         if ((i % 2) == 1) {
13510             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13511             i++;
13512         } else {
13513             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13514             i++;
13515             if (commentList[i] != NULL) {
13516                 fprintf(f, "\n");
13517                 continue;
13518             }
13519             if (i >= forwardMostMove) {
13520                 fprintf(f, "\n");
13521                 break;
13522             }
13523             fprintf(f, "%s\n", parseList[i]);
13524             i++;
13525         }
13526     }
13527
13528     if (commentList[i] != NULL) {
13529         fprintf(f, "[%s]\n", commentList[i]);
13530     }
13531
13532     /* This isn't really the old style, but it's close enough */
13533     if (gameInfo.resultDetails != NULL &&
13534         gameInfo.resultDetails[0] != NULLCHAR) {
13535         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13536                 gameInfo.resultDetails);
13537     } else {
13538         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13539     }
13540
13541     fclose(f);
13542     return TRUE;
13543 }
13544
13545 /* Save the current game to open file f and close the file */
13546 int
13547 SaveGame (FILE *f, int dummy, char *dummy2)
13548 {
13549     if (gameMode == EditPosition) EditPositionDone(TRUE);
13550     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13551     if (appData.oldSaveStyle)
13552       return SaveGameOldStyle(f);
13553     else
13554       return SaveGamePGN(f);
13555 }
13556
13557 /* Save the current position to the given file */
13558 int
13559 SavePositionToFile (char *filename)
13560 {
13561     FILE *f;
13562     char buf[MSG_SIZ];
13563
13564     if (strcmp(filename, "-") == 0) {
13565         return SavePosition(stdout, 0, NULL);
13566     } else {
13567         f = fopen(filename, "a");
13568         if (f == NULL) {
13569             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13570             DisplayError(buf, errno);
13571             return FALSE;
13572         } else {
13573             safeStrCpy(buf, lastMsg, MSG_SIZ);
13574             DisplayMessage(_("Waiting for access to save file"), "");
13575             flock(fileno(f), LOCK_EX); // [HGM] lock
13576             DisplayMessage(_("Saving position"), "");
13577             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13578             SavePosition(f, 0, NULL);
13579             DisplayMessage(buf, "");
13580             return TRUE;
13581         }
13582     }
13583 }
13584
13585 /* Save the current position to the given open file and close the file */
13586 int
13587 SavePosition (FILE *f, int dummy, char *dummy2)
13588 {
13589     time_t tm;
13590     char *fen;
13591
13592     if (gameMode == EditPosition) EditPositionDone(TRUE);
13593     if (appData.oldSaveStyle) {
13594         tm = time((time_t *) NULL);
13595
13596         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13597         PrintOpponents(f);
13598         fprintf(f, "[--------------\n");
13599         PrintPosition(f, currentMove);
13600         fprintf(f, "--------------]\n");
13601     } else {
13602         fen = PositionToFEN(currentMove, NULL, 1);
13603         fprintf(f, "%s\n", fen);
13604         free(fen);
13605     }
13606     fclose(f);
13607     return TRUE;
13608 }
13609
13610 void
13611 ReloadCmailMsgEvent (int unregister)
13612 {
13613 #if !WIN32
13614     static char *inFilename = NULL;
13615     static char *outFilename;
13616     int i;
13617     struct stat inbuf, outbuf;
13618     int status;
13619
13620     /* Any registered moves are unregistered if unregister is set, */
13621     /* i.e. invoked by the signal handler */
13622     if (unregister) {
13623         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13624             cmailMoveRegistered[i] = FALSE;
13625             if (cmailCommentList[i] != NULL) {
13626                 free(cmailCommentList[i]);
13627                 cmailCommentList[i] = NULL;
13628             }
13629         }
13630         nCmailMovesRegistered = 0;
13631     }
13632
13633     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13634         cmailResult[i] = CMAIL_NOT_RESULT;
13635     }
13636     nCmailResults = 0;
13637
13638     if (inFilename == NULL) {
13639         /* Because the filenames are static they only get malloced once  */
13640         /* and they never get freed                                      */
13641         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13642         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13643
13644         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13645         sprintf(outFilename, "%s.out", appData.cmailGameName);
13646     }
13647
13648     status = stat(outFilename, &outbuf);
13649     if (status < 0) {
13650         cmailMailedMove = FALSE;
13651     } else {
13652         status = stat(inFilename, &inbuf);
13653         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13654     }
13655
13656     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13657        counts the games, notes how each one terminated, etc.
13658
13659        It would be nice to remove this kludge and instead gather all
13660        the information while building the game list.  (And to keep it
13661        in the game list nodes instead of having a bunch of fixed-size
13662        parallel arrays.)  Note this will require getting each game's
13663        termination from the PGN tags, as the game list builder does
13664        not process the game moves.  --mann
13665        */
13666     cmailMsgLoaded = TRUE;
13667     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13668
13669     /* Load first game in the file or popup game menu */
13670     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13671
13672 #endif /* !WIN32 */
13673     return;
13674 }
13675
13676 int
13677 RegisterMove ()
13678 {
13679     FILE *f;
13680     char string[MSG_SIZ];
13681
13682     if (   cmailMailedMove
13683         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13684         return TRUE;            /* Allow free viewing  */
13685     }
13686
13687     /* Unregister move to ensure that we don't leave RegisterMove        */
13688     /* with the move registered when the conditions for registering no   */
13689     /* longer hold                                                       */
13690     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13691         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13692         nCmailMovesRegistered --;
13693
13694         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13695           {
13696               free(cmailCommentList[lastLoadGameNumber - 1]);
13697               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13698           }
13699     }
13700
13701     if (cmailOldMove == -1) {
13702         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13703         return FALSE;
13704     }
13705
13706     if (currentMove > cmailOldMove + 1) {
13707         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13708         return FALSE;
13709     }
13710
13711     if (currentMove < cmailOldMove) {
13712         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13713         return FALSE;
13714     }
13715
13716     if (forwardMostMove > currentMove) {
13717         /* Silently truncate extra moves */
13718         TruncateGame();
13719     }
13720
13721     if (   (currentMove == cmailOldMove + 1)
13722         || (   (currentMove == cmailOldMove)
13723             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13724                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13725         if (gameInfo.result != GameUnfinished) {
13726             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13727         }
13728
13729         if (commentList[currentMove] != NULL) {
13730             cmailCommentList[lastLoadGameNumber - 1]
13731               = StrSave(commentList[currentMove]);
13732         }
13733         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13734
13735         if (appData.debugMode)
13736           fprintf(debugFP, "Saving %s for game %d\n",
13737                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13738
13739         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13740
13741         f = fopen(string, "w");
13742         if (appData.oldSaveStyle) {
13743             SaveGameOldStyle(f); /* also closes the file */
13744
13745             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13746             f = fopen(string, "w");
13747             SavePosition(f, 0, NULL); /* also closes the file */
13748         } else {
13749             fprintf(f, "{--------------\n");
13750             PrintPosition(f, currentMove);
13751             fprintf(f, "--------------}\n\n");
13752
13753             SaveGame(f, 0, NULL); /* also closes the file*/
13754         }
13755
13756         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13757         nCmailMovesRegistered ++;
13758     } else if (nCmailGames == 1) {
13759         DisplayError(_("You have not made a move yet"), 0);
13760         return FALSE;
13761     }
13762
13763     return TRUE;
13764 }
13765
13766 void
13767 MailMoveEvent ()
13768 {
13769 #if !WIN32
13770     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13771     FILE *commandOutput;
13772     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13773     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13774     int nBuffers;
13775     int i;
13776     int archived;
13777     char *arcDir;
13778
13779     if (! cmailMsgLoaded) {
13780         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13781         return;
13782     }
13783
13784     if (nCmailGames == nCmailResults) {
13785         DisplayError(_("No unfinished games"), 0);
13786         return;
13787     }
13788
13789 #if CMAIL_PROHIBIT_REMAIL
13790     if (cmailMailedMove) {
13791       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);
13792         DisplayError(msg, 0);
13793         return;
13794     }
13795 #endif
13796
13797     if (! (cmailMailedMove || RegisterMove())) return;
13798
13799     if (   cmailMailedMove
13800         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13801       snprintf(string, MSG_SIZ, partCommandString,
13802                appData.debugMode ? " -v" : "", appData.cmailGameName);
13803         commandOutput = popen(string, "r");
13804
13805         if (commandOutput == NULL) {
13806             DisplayError(_("Failed to invoke cmail"), 0);
13807         } else {
13808             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13809                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13810             }
13811             if (nBuffers > 1) {
13812                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13813                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13814                 nBytes = MSG_SIZ - 1;
13815             } else {
13816                 (void) memcpy(msg, buffer, nBytes);
13817             }
13818             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13819
13820             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13821                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13822
13823                 archived = TRUE;
13824                 for (i = 0; i < nCmailGames; i ++) {
13825                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13826                         archived = FALSE;
13827                     }
13828                 }
13829                 if (   archived
13830                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13831                         != NULL)) {
13832                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13833                            arcDir,
13834                            appData.cmailGameName,
13835                            gameInfo.date);
13836                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13837                     cmailMsgLoaded = FALSE;
13838                 }
13839             }
13840
13841             DisplayInformation(msg);
13842             pclose(commandOutput);
13843         }
13844     } else {
13845         if ((*cmailMsg) != '\0') {
13846             DisplayInformation(cmailMsg);
13847         }
13848     }
13849
13850     return;
13851 #endif /* !WIN32 */
13852 }
13853
13854 char *
13855 CmailMsg ()
13856 {
13857 #if WIN32
13858     return NULL;
13859 #else
13860     int  prependComma = 0;
13861     char number[5];
13862     char string[MSG_SIZ];       /* Space for game-list */
13863     int  i;
13864
13865     if (!cmailMsgLoaded) return "";
13866
13867     if (cmailMailedMove) {
13868       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13869     } else {
13870         /* Create a list of games left */
13871       snprintf(string, MSG_SIZ, "[");
13872         for (i = 0; i < nCmailGames; i ++) {
13873             if (! (   cmailMoveRegistered[i]
13874                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13875                 if (prependComma) {
13876                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13877                 } else {
13878                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13879                     prependComma = 1;
13880                 }
13881
13882                 strcat(string, number);
13883             }
13884         }
13885         strcat(string, "]");
13886
13887         if (nCmailMovesRegistered + nCmailResults == 0) {
13888             switch (nCmailGames) {
13889               case 1:
13890                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13891                 break;
13892
13893               case 2:
13894                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13895                 break;
13896
13897               default:
13898                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13899                          nCmailGames);
13900                 break;
13901             }
13902         } else {
13903             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13904               case 1:
13905                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13906                          string);
13907                 break;
13908
13909               case 0:
13910                 if (nCmailResults == nCmailGames) {
13911                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13912                 } else {
13913                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13914                 }
13915                 break;
13916
13917               default:
13918                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13919                          string);
13920             }
13921         }
13922     }
13923     return cmailMsg;
13924 #endif /* WIN32 */
13925 }
13926
13927 void
13928 ResetGameEvent ()
13929 {
13930     if (gameMode == Training)
13931       SetTrainingModeOff();
13932
13933     Reset(TRUE, TRUE);
13934     cmailMsgLoaded = FALSE;
13935     if (appData.icsActive) {
13936       SendToICS(ics_prefix);
13937       SendToICS("refresh\n");
13938     }
13939 }
13940
13941 void
13942 ExitEvent (int status)
13943 {
13944     exiting++;
13945     if (exiting > 2) {
13946       /* Give up on clean exit */
13947       exit(status);
13948     }
13949     if (exiting > 1) {
13950       /* Keep trying for clean exit */
13951       return;
13952     }
13953
13954     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13955
13956     if (telnetISR != NULL) {
13957       RemoveInputSource(telnetISR);
13958     }
13959     if (icsPR != NoProc) {
13960       DestroyChildProcess(icsPR, TRUE);
13961     }
13962
13963     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13964     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13965
13966     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13967     /* make sure this other one finishes before killing it!                  */
13968     if(endingGame) { int count = 0;
13969         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13970         while(endingGame && count++ < 10) DoSleep(1);
13971         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13972     }
13973
13974     /* Kill off chess programs */
13975     if (first.pr != NoProc) {
13976         ExitAnalyzeMode();
13977
13978         DoSleep( appData.delayBeforeQuit );
13979         SendToProgram("quit\n", &first);
13980         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
13981     }
13982     if (second.pr != NoProc) {
13983         DoSleep( appData.delayBeforeQuit );
13984         SendToProgram("quit\n", &second);
13985         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
13986     }
13987     if (first.isr != NULL) {
13988         RemoveInputSource(first.isr);
13989     }
13990     if (second.isr != NULL) {
13991         RemoveInputSource(second.isr);
13992     }
13993
13994     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13995     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13996
13997     ShutDownFrontEnd();
13998     exit(status);
13999 }
14000
14001 void
14002 PauseEngine (ChessProgramState *cps)
14003 {
14004     SendToProgram("pause\n", cps);
14005     cps->pause = 2;
14006 }
14007
14008 void
14009 UnPauseEngine (ChessProgramState *cps)
14010 {
14011     SendToProgram("resume\n", cps);
14012     cps->pause = 1;
14013 }
14014
14015 void
14016 PauseEvent ()
14017 {
14018     if (appData.debugMode)
14019         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14020     if (pausing) {
14021         pausing = FALSE;
14022         ModeHighlight();
14023         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14024             StartClocks();
14025             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14026                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14027                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14028             }
14029             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14030             HandleMachineMove(stashedInputMove, stalledEngine);
14031             stalledEngine = NULL;
14032             return;
14033         }
14034         if (gameMode == MachinePlaysWhite ||
14035             gameMode == TwoMachinesPlay   ||
14036             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14037             if(first.pause)  UnPauseEngine(&first);
14038             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14039             if(second.pause) UnPauseEngine(&second);
14040             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14041             StartClocks();
14042         } else {
14043             DisplayBothClocks();
14044         }
14045         if (gameMode == PlayFromGameFile) {
14046             if (appData.timeDelay >= 0)
14047                 AutoPlayGameLoop();
14048         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14049             Reset(FALSE, TRUE);
14050             SendToICS(ics_prefix);
14051             SendToICS("refresh\n");
14052         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14053             ForwardInner(forwardMostMove);
14054         }
14055         pauseExamInvalid = FALSE;
14056     } else {
14057         switch (gameMode) {
14058           default:
14059             return;
14060           case IcsExamining:
14061             pauseExamForwardMostMove = forwardMostMove;
14062             pauseExamInvalid = FALSE;
14063             /* fall through */
14064           case IcsObserving:
14065           case IcsPlayingWhite:
14066           case IcsPlayingBlack:
14067             pausing = TRUE;
14068             ModeHighlight();
14069             return;
14070           case PlayFromGameFile:
14071             (void) StopLoadGameTimer();
14072             pausing = TRUE;
14073             ModeHighlight();
14074             break;
14075           case BeginningOfGame:
14076             if (appData.icsActive) return;
14077             /* else fall through */
14078           case MachinePlaysWhite:
14079           case MachinePlaysBlack:
14080           case TwoMachinesPlay:
14081             if (forwardMostMove == 0)
14082               return;           /* don't pause if no one has moved */
14083             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14084                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14085                 if(onMove->pause) {           // thinking engine can be paused
14086                     PauseEngine(onMove);      // do it
14087                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14088                         PauseEngine(onMove->other);
14089                     else
14090                         SendToProgram("easy\n", onMove->other);
14091                     StopClocks();
14092                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14093             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14094                 if(first.pause) {
14095                     PauseEngine(&first);
14096                     StopClocks();
14097                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14098             } else { // human on move, pause pondering by either method
14099                 if(first.pause)
14100                     PauseEngine(&first);
14101                 else if(appData.ponderNextMove)
14102                     SendToProgram("easy\n", &first);
14103                 StopClocks();
14104             }
14105             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14106           case AnalyzeMode:
14107             pausing = TRUE;
14108             ModeHighlight();
14109             break;
14110         }
14111     }
14112 }
14113
14114 void
14115 EditCommentEvent ()
14116 {
14117     char title[MSG_SIZ];
14118
14119     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14120       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14121     } else {
14122       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14123                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14124                parseList[currentMove - 1]);
14125     }
14126
14127     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14128 }
14129
14130
14131 void
14132 EditTagsEvent ()
14133 {
14134     char *tags = PGNTags(&gameInfo);
14135     bookUp = FALSE;
14136     EditTagsPopUp(tags, NULL);
14137     free(tags);
14138 }
14139
14140 void
14141 ToggleSecond ()
14142 {
14143   if(second.analyzing) {
14144     SendToProgram("exit\n", &second);
14145     second.analyzing = FALSE;
14146   } else {
14147     if (second.pr == NoProc) StartChessProgram(&second);
14148     InitChessProgram(&second, FALSE);
14149     FeedMovesToProgram(&second, currentMove);
14150
14151     SendToProgram("analyze\n", &second);
14152     second.analyzing = TRUE;
14153   }
14154 }
14155
14156 /* Toggle ShowThinking */
14157 void
14158 ToggleShowThinking()
14159 {
14160   appData.showThinking = !appData.showThinking;
14161   ShowThinkingEvent();
14162 }
14163
14164 int
14165 AnalyzeModeEvent ()
14166 {
14167     char buf[MSG_SIZ];
14168
14169     if (!first.analysisSupport) {
14170       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14171       DisplayError(buf, 0);
14172       return 0;
14173     }
14174     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14175     if (appData.icsActive) {
14176         if (gameMode != IcsObserving) {
14177           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14178             DisplayError(buf, 0);
14179             /* secure check */
14180             if (appData.icsEngineAnalyze) {
14181                 if (appData.debugMode)
14182                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14183                 ExitAnalyzeMode();
14184                 ModeHighlight();
14185             }
14186             return 0;
14187         }
14188         /* if enable, user wants to disable icsEngineAnalyze */
14189         if (appData.icsEngineAnalyze) {
14190                 ExitAnalyzeMode();
14191                 ModeHighlight();
14192                 return 0;
14193         }
14194         appData.icsEngineAnalyze = TRUE;
14195         if (appData.debugMode)
14196             fprintf(debugFP, "ICS engine analyze starting... \n");
14197     }
14198
14199     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14200     if (appData.noChessProgram || gameMode == AnalyzeMode)
14201       return 0;
14202
14203     if (gameMode != AnalyzeFile) {
14204         if (!appData.icsEngineAnalyze) {
14205                EditGameEvent();
14206                if (gameMode != EditGame) return 0;
14207         }
14208         if (!appData.showThinking) ToggleShowThinking();
14209         ResurrectChessProgram();
14210         SendToProgram("analyze\n", &first);
14211         first.analyzing = TRUE;
14212         /*first.maybeThinking = TRUE;*/
14213         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14214         EngineOutputPopUp();
14215     }
14216     if (!appData.icsEngineAnalyze) {
14217         gameMode = AnalyzeMode;
14218         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14219     }
14220     pausing = FALSE;
14221     ModeHighlight();
14222     SetGameInfo();
14223
14224     StartAnalysisClock();
14225     GetTimeMark(&lastNodeCountTime);
14226     lastNodeCount = 0;
14227     return 1;
14228 }
14229
14230 void
14231 AnalyzeFileEvent ()
14232 {
14233     if (appData.noChessProgram || gameMode == AnalyzeFile)
14234       return;
14235
14236     if (!first.analysisSupport) {
14237       char buf[MSG_SIZ];
14238       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14239       DisplayError(buf, 0);
14240       return;
14241     }
14242
14243     if (gameMode != AnalyzeMode) {
14244         keepInfo = 1; // mere annotating should not alter PGN tags
14245         EditGameEvent();
14246         keepInfo = 0;
14247         if (gameMode != EditGame) return;
14248         if (!appData.showThinking) ToggleShowThinking();
14249         ResurrectChessProgram();
14250         SendToProgram("analyze\n", &first);
14251         first.analyzing = TRUE;
14252         /*first.maybeThinking = TRUE;*/
14253         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14254         EngineOutputPopUp();
14255     }
14256     gameMode = AnalyzeFile;
14257     pausing = FALSE;
14258     ModeHighlight();
14259
14260     StartAnalysisClock();
14261     GetTimeMark(&lastNodeCountTime);
14262     lastNodeCount = 0;
14263     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14264     AnalysisPeriodicEvent(1);
14265 }
14266
14267 void
14268 MachineWhiteEvent ()
14269 {
14270     char buf[MSG_SIZ];
14271     char *bookHit = NULL;
14272
14273     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14274       return;
14275
14276
14277     if (gameMode == PlayFromGameFile ||
14278         gameMode == TwoMachinesPlay  ||
14279         gameMode == Training         ||
14280         gameMode == AnalyzeMode      ||
14281         gameMode == EndOfGame)
14282         EditGameEvent();
14283
14284     if (gameMode == EditPosition)
14285         EditPositionDone(TRUE);
14286
14287     if (!WhiteOnMove(currentMove)) {
14288         DisplayError(_("It is not White's turn"), 0);
14289         return;
14290     }
14291
14292     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14293       ExitAnalyzeMode();
14294
14295     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14296         gameMode == AnalyzeFile)
14297         TruncateGame();
14298
14299     ResurrectChessProgram();    /* in case it isn't running */
14300     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14301         gameMode = MachinePlaysWhite;
14302         ResetClocks();
14303     } else
14304     gameMode = MachinePlaysWhite;
14305     pausing = FALSE;
14306     ModeHighlight();
14307     SetGameInfo();
14308     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14309     DisplayTitle(buf);
14310     if (first.sendName) {
14311       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14312       SendToProgram(buf, &first);
14313     }
14314     if (first.sendTime) {
14315       if (first.useColors) {
14316         SendToProgram("black\n", &first); /*gnu kludge*/
14317       }
14318       SendTimeRemaining(&first, TRUE);
14319     }
14320     if (first.useColors) {
14321       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14322     }
14323     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14324     SetMachineThinkingEnables();
14325     first.maybeThinking = TRUE;
14326     StartClocks();
14327     firstMove = FALSE;
14328
14329     if (appData.autoFlipView && !flipView) {
14330       flipView = !flipView;
14331       DrawPosition(FALSE, NULL);
14332       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14333     }
14334
14335     if(bookHit) { // [HGM] book: simulate book reply
14336         static char bookMove[MSG_SIZ]; // a bit generous?
14337
14338         programStats.nodes = programStats.depth = programStats.time =
14339         programStats.score = programStats.got_only_move = 0;
14340         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14341
14342         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14343         strcat(bookMove, bookHit);
14344         HandleMachineMove(bookMove, &first);
14345     }
14346 }
14347
14348 void
14349 MachineBlackEvent ()
14350 {
14351   char buf[MSG_SIZ];
14352   char *bookHit = NULL;
14353
14354     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14355         return;
14356
14357
14358     if (gameMode == PlayFromGameFile ||
14359         gameMode == TwoMachinesPlay  ||
14360         gameMode == Training         ||
14361         gameMode == AnalyzeMode      ||
14362         gameMode == EndOfGame)
14363         EditGameEvent();
14364
14365     if (gameMode == EditPosition)
14366         EditPositionDone(TRUE);
14367
14368     if (WhiteOnMove(currentMove)) {
14369         DisplayError(_("It is not Black's turn"), 0);
14370         return;
14371     }
14372
14373     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14374       ExitAnalyzeMode();
14375
14376     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14377         gameMode == AnalyzeFile)
14378         TruncateGame();
14379
14380     ResurrectChessProgram();    /* in case it isn't running */
14381     gameMode = MachinePlaysBlack;
14382     pausing = FALSE;
14383     ModeHighlight();
14384     SetGameInfo();
14385     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14386     DisplayTitle(buf);
14387     if (first.sendName) {
14388       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14389       SendToProgram(buf, &first);
14390     }
14391     if (first.sendTime) {
14392       if (first.useColors) {
14393         SendToProgram("white\n", &first); /*gnu kludge*/
14394       }
14395       SendTimeRemaining(&first, FALSE);
14396     }
14397     if (first.useColors) {
14398       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14399     }
14400     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14401     SetMachineThinkingEnables();
14402     first.maybeThinking = TRUE;
14403     StartClocks();
14404
14405     if (appData.autoFlipView && flipView) {
14406       flipView = !flipView;
14407       DrawPosition(FALSE, NULL);
14408       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14409     }
14410     if(bookHit) { // [HGM] book: simulate book reply
14411         static char bookMove[MSG_SIZ]; // a bit generous?
14412
14413         programStats.nodes = programStats.depth = programStats.time =
14414         programStats.score = programStats.got_only_move = 0;
14415         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14416
14417         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14418         strcat(bookMove, bookHit);
14419         HandleMachineMove(bookMove, &first);
14420     }
14421 }
14422
14423
14424 void
14425 DisplayTwoMachinesTitle ()
14426 {
14427     char buf[MSG_SIZ];
14428     if (appData.matchGames > 0) {
14429         if(appData.tourneyFile[0]) {
14430           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14431                    gameInfo.white, _("vs."), gameInfo.black,
14432                    nextGame+1, appData.matchGames+1,
14433                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14434         } else
14435         if (first.twoMachinesColor[0] == 'w') {
14436           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14437                    gameInfo.white, _("vs."),  gameInfo.black,
14438                    first.matchWins, second.matchWins,
14439                    matchGame - 1 - (first.matchWins + second.matchWins));
14440         } else {
14441           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14442                    gameInfo.white, _("vs."), gameInfo.black,
14443                    second.matchWins, first.matchWins,
14444                    matchGame - 1 - (first.matchWins + second.matchWins));
14445         }
14446     } else {
14447       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14448     }
14449     DisplayTitle(buf);
14450 }
14451
14452 void
14453 SettingsMenuIfReady ()
14454 {
14455   if (second.lastPing != second.lastPong) {
14456     DisplayMessage("", _("Waiting for second chess program"));
14457     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14458     return;
14459   }
14460   ThawUI();
14461   DisplayMessage("", "");
14462   SettingsPopUp(&second);
14463 }
14464
14465 int
14466 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14467 {
14468     char buf[MSG_SIZ];
14469     if (cps->pr == NoProc) {
14470         StartChessProgram(cps);
14471         if (cps->protocolVersion == 1) {
14472           retry();
14473           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14474         } else {
14475           /* kludge: allow timeout for initial "feature" command */
14476           if(retry != TwoMachinesEventIfReady) FreezeUI();
14477           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14478           DisplayMessage("", buf);
14479           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14480         }
14481         return 1;
14482     }
14483     return 0;
14484 }
14485
14486 void
14487 TwoMachinesEvent P((void))
14488 {
14489     int i;
14490     char buf[MSG_SIZ];
14491     ChessProgramState *onmove;
14492     char *bookHit = NULL;
14493     static int stalling = 0;
14494     TimeMark now;
14495     long wait;
14496
14497     if (appData.noChessProgram) return;
14498
14499     switch (gameMode) {
14500       case TwoMachinesPlay:
14501         return;
14502       case MachinePlaysWhite:
14503       case MachinePlaysBlack:
14504         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14505             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14506             return;
14507         }
14508         /* fall through */
14509       case BeginningOfGame:
14510       case PlayFromGameFile:
14511       case EndOfGame:
14512         EditGameEvent();
14513         if (gameMode != EditGame) return;
14514         break;
14515       case EditPosition:
14516         EditPositionDone(TRUE);
14517         break;
14518       case AnalyzeMode:
14519       case AnalyzeFile:
14520         ExitAnalyzeMode();
14521         break;
14522       case EditGame:
14523       default:
14524         break;
14525     }
14526
14527 //    forwardMostMove = currentMove;
14528     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14529     startingEngine = TRUE;
14530
14531     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14532
14533     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14534     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14535       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14536       return;
14537     }
14538     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14539
14540     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14541                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14542         startingEngine = FALSE;
14543         DisplayError("second engine does not play this", 0);
14544         return;
14545     }
14546
14547     if(!stalling) {
14548       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14549       SendToProgram("force\n", &second);
14550       stalling = 1;
14551       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14552       return;
14553     }
14554     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14555     if(appData.matchPause>10000 || appData.matchPause<10)
14556                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14557     wait = SubtractTimeMarks(&now, &pauseStart);
14558     if(wait < appData.matchPause) {
14559         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14560         return;
14561     }
14562     // we are now committed to starting the game
14563     stalling = 0;
14564     DisplayMessage("", "");
14565     if (startedFromSetupPosition) {
14566         SendBoard(&second, backwardMostMove);
14567     if (appData.debugMode) {
14568         fprintf(debugFP, "Two Machines\n");
14569     }
14570     }
14571     for (i = backwardMostMove; i < forwardMostMove; i++) {
14572         SendMoveToProgram(i, &second);
14573     }
14574
14575     gameMode = TwoMachinesPlay;
14576     pausing = startingEngine = FALSE;
14577     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14578     SetGameInfo();
14579     DisplayTwoMachinesTitle();
14580     firstMove = TRUE;
14581     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14582         onmove = &first;
14583     } else {
14584         onmove = &second;
14585     }
14586     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14587     SendToProgram(first.computerString, &first);
14588     if (first.sendName) {
14589       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14590       SendToProgram(buf, &first);
14591     }
14592     SendToProgram(second.computerString, &second);
14593     if (second.sendName) {
14594       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14595       SendToProgram(buf, &second);
14596     }
14597
14598     ResetClocks();
14599     if (!first.sendTime || !second.sendTime) {
14600         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14601         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14602     }
14603     if (onmove->sendTime) {
14604       if (onmove->useColors) {
14605         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14606       }
14607       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14608     }
14609     if (onmove->useColors) {
14610       SendToProgram(onmove->twoMachinesColor, onmove);
14611     }
14612     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14613 //    SendToProgram("go\n", onmove);
14614     onmove->maybeThinking = TRUE;
14615     SetMachineThinkingEnables();
14616
14617     StartClocks();
14618
14619     if(bookHit) { // [HGM] book: simulate book reply
14620         static char bookMove[MSG_SIZ]; // a bit generous?
14621
14622         programStats.nodes = programStats.depth = programStats.time =
14623         programStats.score = programStats.got_only_move = 0;
14624         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14625
14626         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14627         strcat(bookMove, bookHit);
14628         savedMessage = bookMove; // args for deferred call
14629         savedState = onmove;
14630         ScheduleDelayedEvent(DeferredBookMove, 1);
14631     }
14632 }
14633
14634 void
14635 TrainingEvent ()
14636 {
14637     if (gameMode == Training) {
14638       SetTrainingModeOff();
14639       gameMode = PlayFromGameFile;
14640       DisplayMessage("", _("Training mode off"));
14641     } else {
14642       gameMode = Training;
14643       animateTraining = appData.animate;
14644
14645       /* make sure we are not already at the end of the game */
14646       if (currentMove < forwardMostMove) {
14647         SetTrainingModeOn();
14648         DisplayMessage("", _("Training mode on"));
14649       } else {
14650         gameMode = PlayFromGameFile;
14651         DisplayError(_("Already at end of game"), 0);
14652       }
14653     }
14654     ModeHighlight();
14655 }
14656
14657 void
14658 IcsClientEvent ()
14659 {
14660     if (!appData.icsActive) return;
14661     switch (gameMode) {
14662       case IcsPlayingWhite:
14663       case IcsPlayingBlack:
14664       case IcsObserving:
14665       case IcsIdle:
14666       case BeginningOfGame:
14667       case IcsExamining:
14668         return;
14669
14670       case EditGame:
14671         break;
14672
14673       case EditPosition:
14674         EditPositionDone(TRUE);
14675         break;
14676
14677       case AnalyzeMode:
14678       case AnalyzeFile:
14679         ExitAnalyzeMode();
14680         break;
14681
14682       default:
14683         EditGameEvent();
14684         break;
14685     }
14686
14687     gameMode = IcsIdle;
14688     ModeHighlight();
14689     return;
14690 }
14691
14692 void
14693 EditGameEvent ()
14694 {
14695     int i;
14696
14697     switch (gameMode) {
14698       case Training:
14699         SetTrainingModeOff();
14700         break;
14701       case MachinePlaysWhite:
14702       case MachinePlaysBlack:
14703       case BeginningOfGame:
14704         SendToProgram("force\n", &first);
14705         SetUserThinkingEnables();
14706         break;
14707       case PlayFromGameFile:
14708         (void) StopLoadGameTimer();
14709         if (gameFileFP != NULL) {
14710             gameFileFP = NULL;
14711         }
14712         break;
14713       case EditPosition:
14714         EditPositionDone(TRUE);
14715         break;
14716       case AnalyzeMode:
14717       case AnalyzeFile:
14718         ExitAnalyzeMode();
14719         SendToProgram("force\n", &first);
14720         break;
14721       case TwoMachinesPlay:
14722         GameEnds(EndOfFile, NULL, GE_PLAYER);
14723         ResurrectChessProgram();
14724         SetUserThinkingEnables();
14725         break;
14726       case EndOfGame:
14727         ResurrectChessProgram();
14728         break;
14729       case IcsPlayingBlack:
14730       case IcsPlayingWhite:
14731         DisplayError(_("Warning: You are still playing a game"), 0);
14732         break;
14733       case IcsObserving:
14734         DisplayError(_("Warning: You are still observing a game"), 0);
14735         break;
14736       case IcsExamining:
14737         DisplayError(_("Warning: You are still examining a game"), 0);
14738         break;
14739       case IcsIdle:
14740         break;
14741       case EditGame:
14742       default:
14743         return;
14744     }
14745
14746     pausing = FALSE;
14747     StopClocks();
14748     first.offeredDraw = second.offeredDraw = 0;
14749
14750     if (gameMode == PlayFromGameFile) {
14751         whiteTimeRemaining = timeRemaining[0][currentMove];
14752         blackTimeRemaining = timeRemaining[1][currentMove];
14753         DisplayTitle("");
14754     }
14755
14756     if (gameMode == MachinePlaysWhite ||
14757         gameMode == MachinePlaysBlack ||
14758         gameMode == TwoMachinesPlay ||
14759         gameMode == EndOfGame) {
14760         i = forwardMostMove;
14761         while (i > currentMove) {
14762             SendToProgram("undo\n", &first);
14763             i--;
14764         }
14765         if(!adjustedClock) {
14766         whiteTimeRemaining = timeRemaining[0][currentMove];
14767         blackTimeRemaining = timeRemaining[1][currentMove];
14768         DisplayBothClocks();
14769         }
14770         if (whiteFlag || blackFlag) {
14771             whiteFlag = blackFlag = 0;
14772         }
14773         DisplayTitle("");
14774     }
14775
14776     gameMode = EditGame;
14777     ModeHighlight();
14778     SetGameInfo();
14779 }
14780
14781
14782 void
14783 EditPositionEvent ()
14784 {
14785     if (gameMode == EditPosition) {
14786         EditGameEvent();
14787         return;
14788     }
14789
14790     EditGameEvent();
14791     if (gameMode != EditGame) return;
14792
14793     gameMode = EditPosition;
14794     ModeHighlight();
14795     SetGameInfo();
14796     if (currentMove > 0)
14797       CopyBoard(boards[0], boards[currentMove]);
14798
14799     blackPlaysFirst = !WhiteOnMove(currentMove);
14800     ResetClocks();
14801     currentMove = forwardMostMove = backwardMostMove = 0;
14802     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14803     DisplayMove(-1);
14804     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14805 }
14806
14807 void
14808 ExitAnalyzeMode ()
14809 {
14810     /* [DM] icsEngineAnalyze - possible call from other functions */
14811     if (appData.icsEngineAnalyze) {
14812         appData.icsEngineAnalyze = FALSE;
14813
14814         DisplayMessage("",_("Close ICS engine analyze..."));
14815     }
14816     if (first.analysisSupport && first.analyzing) {
14817       SendToBoth("exit\n");
14818       first.analyzing = second.analyzing = FALSE;
14819     }
14820     thinkOutput[0] = NULLCHAR;
14821 }
14822
14823 void
14824 EditPositionDone (Boolean fakeRights)
14825 {
14826     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14827
14828     startedFromSetupPosition = TRUE;
14829     InitChessProgram(&first, FALSE);
14830     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14831       boards[0][EP_STATUS] = EP_NONE;
14832       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14833       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14834         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14835         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14836       } else boards[0][CASTLING][2] = NoRights;
14837       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14838         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14839         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14840       } else boards[0][CASTLING][5] = NoRights;
14841       if(gameInfo.variant == VariantSChess) {
14842         int i;
14843         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14844           boards[0][VIRGIN][i] = 0;
14845           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14846           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14847         }
14848       }
14849     }
14850     SendToProgram("force\n", &first);
14851     if (blackPlaysFirst) {
14852         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14853         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14854         currentMove = forwardMostMove = backwardMostMove = 1;
14855         CopyBoard(boards[1], boards[0]);
14856     } else {
14857         currentMove = forwardMostMove = backwardMostMove = 0;
14858     }
14859     SendBoard(&first, forwardMostMove);
14860     if (appData.debugMode) {
14861         fprintf(debugFP, "EditPosDone\n");
14862     }
14863     DisplayTitle("");
14864     DisplayMessage("", "");
14865     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14866     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14867     gameMode = EditGame;
14868     ModeHighlight();
14869     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14870     ClearHighlights(); /* [AS] */
14871 }
14872
14873 /* Pause for `ms' milliseconds */
14874 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14875 void
14876 TimeDelay (long ms)
14877 {
14878     TimeMark m1, m2;
14879
14880     GetTimeMark(&m1);
14881     do {
14882         GetTimeMark(&m2);
14883     } while (SubtractTimeMarks(&m2, &m1) < ms);
14884 }
14885
14886 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14887 void
14888 SendMultiLineToICS (char *buf)
14889 {
14890     char temp[MSG_SIZ+1], *p;
14891     int len;
14892
14893     len = strlen(buf);
14894     if (len > MSG_SIZ)
14895       len = MSG_SIZ;
14896
14897     strncpy(temp, buf, len);
14898     temp[len] = 0;
14899
14900     p = temp;
14901     while (*p) {
14902         if (*p == '\n' || *p == '\r')
14903           *p = ' ';
14904         ++p;
14905     }
14906
14907     strcat(temp, "\n");
14908     SendToICS(temp);
14909     SendToPlayer(temp, strlen(temp));
14910 }
14911
14912 void
14913 SetWhiteToPlayEvent ()
14914 {
14915     if (gameMode == EditPosition) {
14916         blackPlaysFirst = FALSE;
14917         DisplayBothClocks();    /* works because currentMove is 0 */
14918     } else if (gameMode == IcsExamining) {
14919         SendToICS(ics_prefix);
14920         SendToICS("tomove white\n");
14921     }
14922 }
14923
14924 void
14925 SetBlackToPlayEvent ()
14926 {
14927     if (gameMode == EditPosition) {
14928         blackPlaysFirst = TRUE;
14929         currentMove = 1;        /* kludge */
14930         DisplayBothClocks();
14931         currentMove = 0;
14932     } else if (gameMode == IcsExamining) {
14933         SendToICS(ics_prefix);
14934         SendToICS("tomove black\n");
14935     }
14936 }
14937
14938 void
14939 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14940 {
14941     char buf[MSG_SIZ];
14942     ChessSquare piece = boards[0][y][x];
14943     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14944     static int lastVariant;
14945
14946     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14947
14948     switch (selection) {
14949       case ClearBoard:
14950         CopyBoard(currentBoard, boards[0]);
14951         CopyBoard(menuBoard, initialPosition);
14952         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14953             SendToICS(ics_prefix);
14954             SendToICS("bsetup clear\n");
14955         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14956             SendToICS(ics_prefix);
14957             SendToICS("clearboard\n");
14958         } else {
14959             int nonEmpty = 0;
14960             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14961                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14962                 for (y = 0; y < BOARD_HEIGHT; y++) {
14963                     if (gameMode == IcsExamining) {
14964                         if (boards[currentMove][y][x] != EmptySquare) {
14965                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14966                                     AAA + x, ONE + y);
14967                             SendToICS(buf);
14968                         }
14969                     } else {
14970                         if(boards[0][y][x] != p) nonEmpty++;
14971                         boards[0][y][x] = p;
14972                     }
14973                 }
14974                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14975             }
14976             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14977                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14978                     ChessSquare p = menuBoard[0][x];
14979                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14980                     p = menuBoard[BOARD_HEIGHT-1][x];
14981                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14982                 }
14983                 DisplayMessage("Clicking clock again restores position", "");
14984                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14985                 if(!nonEmpty) { // asked to clear an empty board
14986                     CopyBoard(boards[0], menuBoard);
14987                 } else
14988                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14989                     CopyBoard(boards[0], initialPosition);
14990                 } else
14991                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14992                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14993                     CopyBoard(boards[0], erasedBoard);
14994                 } else
14995                     CopyBoard(erasedBoard, currentBoard);
14996
14997             }
14998         }
14999         if (gameMode == EditPosition) {
15000             DrawPosition(FALSE, boards[0]);
15001         }
15002         break;
15003
15004       case WhitePlay:
15005         SetWhiteToPlayEvent();
15006         break;
15007
15008       case BlackPlay:
15009         SetBlackToPlayEvent();
15010         break;
15011
15012       case EmptySquare:
15013         if (gameMode == IcsExamining) {
15014             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15015             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15016             SendToICS(buf);
15017         } else {
15018             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15019                 if(x == BOARD_LEFT-2) {
15020                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15021                     boards[0][y][1] = 0;
15022                 } else
15023                 if(x == BOARD_RGHT+1) {
15024                     if(y >= gameInfo.holdingsSize) break;
15025                     boards[0][y][BOARD_WIDTH-2] = 0;
15026                 } else break;
15027             }
15028             boards[0][y][x] = EmptySquare;
15029             DrawPosition(FALSE, boards[0]);
15030         }
15031         break;
15032
15033       case PromotePiece:
15034         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15035            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15036             selection = (ChessSquare) (PROMOTED piece);
15037         } else if(piece == EmptySquare) selection = WhiteSilver;
15038         else selection = (ChessSquare)((int)piece - 1);
15039         goto defaultlabel;
15040
15041       case DemotePiece:
15042         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15043            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15044             selection = (ChessSquare) (DEMOTED piece);
15045         } else if(piece == EmptySquare) selection = BlackSilver;
15046         else selection = (ChessSquare)((int)piece + 1);
15047         goto defaultlabel;
15048
15049       case WhiteQueen:
15050       case BlackQueen:
15051         if(gameInfo.variant == VariantShatranj ||
15052            gameInfo.variant == VariantXiangqi  ||
15053            gameInfo.variant == VariantCourier  ||
15054            gameInfo.variant == VariantASEAN    ||
15055            gameInfo.variant == VariantMakruk     )
15056             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15057         goto defaultlabel;
15058
15059       case WhiteKing:
15060       case BlackKing:
15061         if(gameInfo.variant == VariantXiangqi)
15062             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15063         if(gameInfo.variant == VariantKnightmate)
15064             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15065       default:
15066         defaultlabel:
15067         if (gameMode == IcsExamining) {
15068             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15069             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15070                      PieceToChar(selection), AAA + x, ONE + y);
15071             SendToICS(buf);
15072         } else {
15073             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15074                 int n;
15075                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15076                     n = PieceToNumber(selection - BlackPawn);
15077                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15078                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15079                     boards[0][BOARD_HEIGHT-1-n][1]++;
15080                 } else
15081                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15082                     n = PieceToNumber(selection);
15083                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15084                     boards[0][n][BOARD_WIDTH-1] = selection;
15085                     boards[0][n][BOARD_WIDTH-2]++;
15086                 }
15087             } else
15088             boards[0][y][x] = selection;
15089             DrawPosition(TRUE, boards[0]);
15090             ClearHighlights();
15091             fromX = fromY = -1;
15092         }
15093         break;
15094     }
15095 }
15096
15097
15098 void
15099 DropMenuEvent (ChessSquare selection, int x, int y)
15100 {
15101     ChessMove moveType;
15102
15103     switch (gameMode) {
15104       case IcsPlayingWhite:
15105       case MachinePlaysBlack:
15106         if (!WhiteOnMove(currentMove)) {
15107             DisplayMoveError(_("It is Black's turn"));
15108             return;
15109         }
15110         moveType = WhiteDrop;
15111         break;
15112       case IcsPlayingBlack:
15113       case MachinePlaysWhite:
15114         if (WhiteOnMove(currentMove)) {
15115             DisplayMoveError(_("It is White's turn"));
15116             return;
15117         }
15118         moveType = BlackDrop;
15119         break;
15120       case EditGame:
15121         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15122         break;
15123       default:
15124         return;
15125     }
15126
15127     if (moveType == BlackDrop && selection < BlackPawn) {
15128       selection = (ChessSquare) ((int) selection
15129                                  + (int) BlackPawn - (int) WhitePawn);
15130     }
15131     if (boards[currentMove][y][x] != EmptySquare) {
15132         DisplayMoveError(_("That square is occupied"));
15133         return;
15134     }
15135
15136     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15137 }
15138
15139 void
15140 AcceptEvent ()
15141 {
15142     /* Accept a pending offer of any kind from opponent */
15143
15144     if (appData.icsActive) {
15145         SendToICS(ics_prefix);
15146         SendToICS("accept\n");
15147     } else if (cmailMsgLoaded) {
15148         if (currentMove == cmailOldMove &&
15149             commentList[cmailOldMove] != NULL &&
15150             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15151                    "Black offers a draw" : "White offers a draw")) {
15152             TruncateGame();
15153             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15154             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15155         } else {
15156             DisplayError(_("There is no pending offer on this move"), 0);
15157             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15158         }
15159     } else {
15160         /* Not used for offers from chess program */
15161     }
15162 }
15163
15164 void
15165 DeclineEvent ()
15166 {
15167     /* Decline a pending offer of any kind from opponent */
15168
15169     if (appData.icsActive) {
15170         SendToICS(ics_prefix);
15171         SendToICS("decline\n");
15172     } else if (cmailMsgLoaded) {
15173         if (currentMove == cmailOldMove &&
15174             commentList[cmailOldMove] != NULL &&
15175             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15176                    "Black offers a draw" : "White offers a draw")) {
15177 #ifdef NOTDEF
15178             AppendComment(cmailOldMove, "Draw declined", TRUE);
15179             DisplayComment(cmailOldMove - 1, "Draw declined");
15180 #endif /*NOTDEF*/
15181         } else {
15182             DisplayError(_("There is no pending offer on this move"), 0);
15183         }
15184     } else {
15185         /* Not used for offers from chess program */
15186     }
15187 }
15188
15189 void
15190 RematchEvent ()
15191 {
15192     /* Issue ICS rematch command */
15193     if (appData.icsActive) {
15194         SendToICS(ics_prefix);
15195         SendToICS("rematch\n");
15196     }
15197 }
15198
15199 void
15200 CallFlagEvent ()
15201 {
15202     /* Call your opponent's flag (claim a win on time) */
15203     if (appData.icsActive) {
15204         SendToICS(ics_prefix);
15205         SendToICS("flag\n");
15206     } else {
15207         switch (gameMode) {
15208           default:
15209             return;
15210           case MachinePlaysWhite:
15211             if (whiteFlag) {
15212                 if (blackFlag)
15213                   GameEnds(GameIsDrawn, "Both players ran out of time",
15214                            GE_PLAYER);
15215                 else
15216                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15217             } else {
15218                 DisplayError(_("Your opponent is not out of time"), 0);
15219             }
15220             break;
15221           case MachinePlaysBlack:
15222             if (blackFlag) {
15223                 if (whiteFlag)
15224                   GameEnds(GameIsDrawn, "Both players ran out of time",
15225                            GE_PLAYER);
15226                 else
15227                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15228             } else {
15229                 DisplayError(_("Your opponent is not out of time"), 0);
15230             }
15231             break;
15232         }
15233     }
15234 }
15235
15236 void
15237 ClockClick (int which)
15238 {       // [HGM] code moved to back-end from winboard.c
15239         if(which) { // black clock
15240           if (gameMode == EditPosition || gameMode == IcsExamining) {
15241             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15242             SetBlackToPlayEvent();
15243           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15244           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15245           } else if (shiftKey) {
15246             AdjustClock(which, -1);
15247           } else if (gameMode == IcsPlayingWhite ||
15248                      gameMode == MachinePlaysBlack) {
15249             CallFlagEvent();
15250           }
15251         } else { // white clock
15252           if (gameMode == EditPosition || gameMode == IcsExamining) {
15253             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15254             SetWhiteToPlayEvent();
15255           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15256           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15257           } else if (shiftKey) {
15258             AdjustClock(which, -1);
15259           } else if (gameMode == IcsPlayingBlack ||
15260                    gameMode == MachinePlaysWhite) {
15261             CallFlagEvent();
15262           }
15263         }
15264 }
15265
15266 void
15267 DrawEvent ()
15268 {
15269     /* Offer draw or accept pending draw offer from opponent */
15270
15271     if (appData.icsActive) {
15272         /* Note: tournament rules require draw offers to be
15273            made after you make your move but before you punch
15274            your clock.  Currently ICS doesn't let you do that;
15275            instead, you immediately punch your clock after making
15276            a move, but you can offer a draw at any time. */
15277
15278         SendToICS(ics_prefix);
15279         SendToICS("draw\n");
15280         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15281     } else if (cmailMsgLoaded) {
15282         if (currentMove == cmailOldMove &&
15283             commentList[cmailOldMove] != NULL &&
15284             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15285                    "Black offers a draw" : "White offers a draw")) {
15286             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15287             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15288         } else if (currentMove == cmailOldMove + 1) {
15289             char *offer = WhiteOnMove(cmailOldMove) ?
15290               "White offers a draw" : "Black offers a draw";
15291             AppendComment(currentMove, offer, TRUE);
15292             DisplayComment(currentMove - 1, offer);
15293             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15294         } else {
15295             DisplayError(_("You must make your move before offering a draw"), 0);
15296             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15297         }
15298     } else if (first.offeredDraw) {
15299         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15300     } else {
15301         if (first.sendDrawOffers) {
15302             SendToProgram("draw\n", &first);
15303             userOfferedDraw = TRUE;
15304         }
15305     }
15306 }
15307
15308 void
15309 AdjournEvent ()
15310 {
15311     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15312
15313     if (appData.icsActive) {
15314         SendToICS(ics_prefix);
15315         SendToICS("adjourn\n");
15316     } else {
15317         /* Currently GNU Chess doesn't offer or accept Adjourns */
15318     }
15319 }
15320
15321
15322 void
15323 AbortEvent ()
15324 {
15325     /* Offer Abort or accept pending Abort offer from opponent */
15326
15327     if (appData.icsActive) {
15328         SendToICS(ics_prefix);
15329         SendToICS("abort\n");
15330     } else {
15331         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15332     }
15333 }
15334
15335 void
15336 ResignEvent ()
15337 {
15338     /* Resign.  You can do this even if it's not your turn. */
15339
15340     if (appData.icsActive) {
15341         SendToICS(ics_prefix);
15342         SendToICS("resign\n");
15343     } else {
15344         switch (gameMode) {
15345           case MachinePlaysWhite:
15346             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15347             break;
15348           case MachinePlaysBlack:
15349             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15350             break;
15351           case EditGame:
15352             if (cmailMsgLoaded) {
15353                 TruncateGame();
15354                 if (WhiteOnMove(cmailOldMove)) {
15355                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15356                 } else {
15357                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15358                 }
15359                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15360             }
15361             break;
15362           default:
15363             break;
15364         }
15365     }
15366 }
15367
15368
15369 void
15370 StopObservingEvent ()
15371 {
15372     /* Stop observing current games */
15373     SendToICS(ics_prefix);
15374     SendToICS("unobserve\n");
15375 }
15376
15377 void
15378 StopExaminingEvent ()
15379 {
15380     /* Stop observing current game */
15381     SendToICS(ics_prefix);
15382     SendToICS("unexamine\n");
15383 }
15384
15385 void
15386 ForwardInner (int target)
15387 {
15388     int limit; int oldSeekGraphUp = seekGraphUp;
15389
15390     if (appData.debugMode)
15391         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15392                 target, currentMove, forwardMostMove);
15393
15394     if (gameMode == EditPosition)
15395       return;
15396
15397     seekGraphUp = FALSE;
15398     MarkTargetSquares(1);
15399
15400     if (gameMode == PlayFromGameFile && !pausing)
15401       PauseEvent();
15402
15403     if (gameMode == IcsExamining && pausing)
15404       limit = pauseExamForwardMostMove;
15405     else
15406       limit = forwardMostMove;
15407
15408     if (target > limit) target = limit;
15409
15410     if (target > 0 && moveList[target - 1][0]) {
15411         int fromX, fromY, toX, toY;
15412         toX = moveList[target - 1][2] - AAA;
15413         toY = moveList[target - 1][3] - ONE;
15414         if (moveList[target - 1][1] == '@') {
15415             if (appData.highlightLastMove) {
15416                 SetHighlights(-1, -1, toX, toY);
15417             }
15418         } else {
15419             fromX = moveList[target - 1][0] - AAA;
15420             fromY = moveList[target - 1][1] - ONE;
15421             if (target == currentMove + 1) {
15422                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15423             }
15424             if (appData.highlightLastMove) {
15425                 SetHighlights(fromX, fromY, toX, toY);
15426             }
15427         }
15428     }
15429     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15430         gameMode == Training || gameMode == PlayFromGameFile ||
15431         gameMode == AnalyzeFile) {
15432         while (currentMove < target) {
15433             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15434             SendMoveToProgram(currentMove++, &first);
15435         }
15436     } else {
15437         currentMove = target;
15438     }
15439
15440     if (gameMode == EditGame || gameMode == EndOfGame) {
15441         whiteTimeRemaining = timeRemaining[0][currentMove];
15442         blackTimeRemaining = timeRemaining[1][currentMove];
15443     }
15444     DisplayBothClocks();
15445     DisplayMove(currentMove - 1);
15446     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15447     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15448     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15449         DisplayComment(currentMove - 1, commentList[currentMove]);
15450     }
15451     ClearMap(); // [HGM] exclude: invalidate map
15452 }
15453
15454
15455 void
15456 ForwardEvent ()
15457 {
15458     if (gameMode == IcsExamining && !pausing) {
15459         SendToICS(ics_prefix);
15460         SendToICS("forward\n");
15461     } else {
15462         ForwardInner(currentMove + 1);
15463     }
15464 }
15465
15466 void
15467 ToEndEvent ()
15468 {
15469     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15470         /* to optimze, we temporarily turn off analysis mode while we feed
15471          * the remaining moves to the engine. Otherwise we get analysis output
15472          * after each move.
15473          */
15474         if (first.analysisSupport) {
15475           SendToProgram("exit\nforce\n", &first);
15476           first.analyzing = FALSE;
15477         }
15478     }
15479
15480     if (gameMode == IcsExamining && !pausing) {
15481         SendToICS(ics_prefix);
15482         SendToICS("forward 999999\n");
15483     } else {
15484         ForwardInner(forwardMostMove);
15485     }
15486
15487     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15488         /* we have fed all the moves, so reactivate analysis mode */
15489         SendToProgram("analyze\n", &first);
15490         first.analyzing = TRUE;
15491         /*first.maybeThinking = TRUE;*/
15492         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15493     }
15494 }
15495
15496 void
15497 BackwardInner (int target)
15498 {
15499     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15500
15501     if (appData.debugMode)
15502         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15503                 target, currentMove, forwardMostMove);
15504
15505     if (gameMode == EditPosition) return;
15506     seekGraphUp = FALSE;
15507     MarkTargetSquares(1);
15508     if (currentMove <= backwardMostMove) {
15509         ClearHighlights();
15510         DrawPosition(full_redraw, boards[currentMove]);
15511         return;
15512     }
15513     if (gameMode == PlayFromGameFile && !pausing)
15514       PauseEvent();
15515
15516     if (moveList[target][0]) {
15517         int fromX, fromY, toX, toY;
15518         toX = moveList[target][2] - AAA;
15519         toY = moveList[target][3] - ONE;
15520         if (moveList[target][1] == '@') {
15521             if (appData.highlightLastMove) {
15522                 SetHighlights(-1, -1, toX, toY);
15523             }
15524         } else {
15525             fromX = moveList[target][0] - AAA;
15526             fromY = moveList[target][1] - ONE;
15527             if (target == currentMove - 1) {
15528                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15529             }
15530             if (appData.highlightLastMove) {
15531                 SetHighlights(fromX, fromY, toX, toY);
15532             }
15533         }
15534     }
15535     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15536         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15537         while (currentMove > target) {
15538             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15539                 // null move cannot be undone. Reload program with move history before it.
15540                 int i;
15541                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15542                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15543                 }
15544                 SendBoard(&first, i);
15545               if(second.analyzing) SendBoard(&second, i);
15546                 for(currentMove=i; currentMove<target; currentMove++) {
15547                     SendMoveToProgram(currentMove, &first);
15548                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15549                 }
15550                 break;
15551             }
15552             SendToBoth("undo\n");
15553             currentMove--;
15554         }
15555     } else {
15556         currentMove = target;
15557     }
15558
15559     if (gameMode == EditGame || gameMode == EndOfGame) {
15560         whiteTimeRemaining = timeRemaining[0][currentMove];
15561         blackTimeRemaining = timeRemaining[1][currentMove];
15562     }
15563     DisplayBothClocks();
15564     DisplayMove(currentMove - 1);
15565     DrawPosition(full_redraw, boards[currentMove]);
15566     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15567     // [HGM] PV info: routine tests if comment empty
15568     DisplayComment(currentMove - 1, commentList[currentMove]);
15569     ClearMap(); // [HGM] exclude: invalidate map
15570 }
15571
15572 void
15573 BackwardEvent ()
15574 {
15575     if (gameMode == IcsExamining && !pausing) {
15576         SendToICS(ics_prefix);
15577         SendToICS("backward\n");
15578     } else {
15579         BackwardInner(currentMove - 1);
15580     }
15581 }
15582
15583 void
15584 ToStartEvent ()
15585 {
15586     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15587         /* to optimize, we temporarily turn off analysis mode while we undo
15588          * all the moves. Otherwise we get analysis output after each undo.
15589          */
15590         if (first.analysisSupport) {
15591           SendToProgram("exit\nforce\n", &first);
15592           first.analyzing = FALSE;
15593         }
15594     }
15595
15596     if (gameMode == IcsExamining && !pausing) {
15597         SendToICS(ics_prefix);
15598         SendToICS("backward 999999\n");
15599     } else {
15600         BackwardInner(backwardMostMove);
15601     }
15602
15603     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15604         /* we have fed all the moves, so reactivate analysis mode */
15605         SendToProgram("analyze\n", &first);
15606         first.analyzing = TRUE;
15607         /*first.maybeThinking = TRUE;*/
15608         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15609     }
15610 }
15611
15612 void
15613 ToNrEvent (int to)
15614 {
15615   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15616   if (to >= forwardMostMove) to = forwardMostMove;
15617   if (to <= backwardMostMove) to = backwardMostMove;
15618   if (to < currentMove) {
15619     BackwardInner(to);
15620   } else {
15621     ForwardInner(to);
15622   }
15623 }
15624
15625 void
15626 RevertEvent (Boolean annotate)
15627 {
15628     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15629         return;
15630     }
15631     if (gameMode != IcsExamining) {
15632         DisplayError(_("You are not examining a game"), 0);
15633         return;
15634     }
15635     if (pausing) {
15636         DisplayError(_("You can't revert while pausing"), 0);
15637         return;
15638     }
15639     SendToICS(ics_prefix);
15640     SendToICS("revert\n");
15641 }
15642
15643 void
15644 RetractMoveEvent ()
15645 {
15646     switch (gameMode) {
15647       case MachinePlaysWhite:
15648       case MachinePlaysBlack:
15649         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15650             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15651             return;
15652         }
15653         if (forwardMostMove < 2) return;
15654         currentMove = forwardMostMove = forwardMostMove - 2;
15655         whiteTimeRemaining = timeRemaining[0][currentMove];
15656         blackTimeRemaining = timeRemaining[1][currentMove];
15657         DisplayBothClocks();
15658         DisplayMove(currentMove - 1);
15659         ClearHighlights();/*!! could figure this out*/
15660         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15661         SendToProgram("remove\n", &first);
15662         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15663         break;
15664
15665       case BeginningOfGame:
15666       default:
15667         break;
15668
15669       case IcsPlayingWhite:
15670       case IcsPlayingBlack:
15671         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15672             SendToICS(ics_prefix);
15673             SendToICS("takeback 2\n");
15674         } else {
15675             SendToICS(ics_prefix);
15676             SendToICS("takeback 1\n");
15677         }
15678         break;
15679     }
15680 }
15681
15682 void
15683 MoveNowEvent ()
15684 {
15685     ChessProgramState *cps;
15686
15687     switch (gameMode) {
15688       case MachinePlaysWhite:
15689         if (!WhiteOnMove(forwardMostMove)) {
15690             DisplayError(_("It is your turn"), 0);
15691             return;
15692         }
15693         cps = &first;
15694         break;
15695       case MachinePlaysBlack:
15696         if (WhiteOnMove(forwardMostMove)) {
15697             DisplayError(_("It is your turn"), 0);
15698             return;
15699         }
15700         cps = &first;
15701         break;
15702       case TwoMachinesPlay:
15703         if (WhiteOnMove(forwardMostMove) ==
15704             (first.twoMachinesColor[0] == 'w')) {
15705             cps = &first;
15706         } else {
15707             cps = &second;
15708         }
15709         break;
15710       case BeginningOfGame:
15711       default:
15712         return;
15713     }
15714     SendToProgram("?\n", cps);
15715 }
15716
15717 void
15718 TruncateGameEvent ()
15719 {
15720     EditGameEvent();
15721     if (gameMode != EditGame) return;
15722     TruncateGame();
15723 }
15724
15725 void
15726 TruncateGame ()
15727 {
15728     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15729     if (forwardMostMove > currentMove) {
15730         if (gameInfo.resultDetails != NULL) {
15731             free(gameInfo.resultDetails);
15732             gameInfo.resultDetails = NULL;
15733             gameInfo.result = GameUnfinished;
15734         }
15735         forwardMostMove = currentMove;
15736         HistorySet(parseList, backwardMostMove, forwardMostMove,
15737                    currentMove-1);
15738     }
15739 }
15740
15741 void
15742 HintEvent ()
15743 {
15744     if (appData.noChessProgram) return;
15745     switch (gameMode) {
15746       case MachinePlaysWhite:
15747         if (WhiteOnMove(forwardMostMove)) {
15748             DisplayError(_("Wait until your turn."), 0);
15749             return;
15750         }
15751         break;
15752       case BeginningOfGame:
15753       case MachinePlaysBlack:
15754         if (!WhiteOnMove(forwardMostMove)) {
15755             DisplayError(_("Wait until your turn."), 0);
15756             return;
15757         }
15758         break;
15759       default:
15760         DisplayError(_("No hint available"), 0);
15761         return;
15762     }
15763     SendToProgram("hint\n", &first);
15764     hintRequested = TRUE;
15765 }
15766
15767 void
15768 CreateBookEvent ()
15769 {
15770     ListGame * lg = (ListGame *) gameList.head;
15771     FILE *f, *g;
15772     int nItem;
15773     static int secondTime = FALSE;
15774
15775     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15776         DisplayError(_("Game list not loaded or empty"), 0);
15777         return;
15778     }
15779
15780     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15781         fclose(g);
15782         secondTime++;
15783         DisplayNote(_("Book file exists! Try again for overwrite."));
15784         return;
15785     }
15786
15787     creatingBook = TRUE;
15788     secondTime = FALSE;
15789
15790     /* Get list size */
15791     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15792         LoadGame(f, nItem, "", TRUE);
15793         AddGameToBook(TRUE);
15794         lg = (ListGame *) lg->node.succ;
15795     }
15796
15797     creatingBook = FALSE;
15798     FlushBook();
15799 }
15800
15801 void
15802 BookEvent ()
15803 {
15804     if (appData.noChessProgram) return;
15805     switch (gameMode) {
15806       case MachinePlaysWhite:
15807         if (WhiteOnMove(forwardMostMove)) {
15808             DisplayError(_("Wait until your turn."), 0);
15809             return;
15810         }
15811         break;
15812       case BeginningOfGame:
15813       case MachinePlaysBlack:
15814         if (!WhiteOnMove(forwardMostMove)) {
15815             DisplayError(_("Wait until your turn."), 0);
15816             return;
15817         }
15818         break;
15819       case EditPosition:
15820         EditPositionDone(TRUE);
15821         break;
15822       case TwoMachinesPlay:
15823         return;
15824       default:
15825         break;
15826     }
15827     SendToProgram("bk\n", &first);
15828     bookOutput[0] = NULLCHAR;
15829     bookRequested = TRUE;
15830 }
15831
15832 void
15833 AboutGameEvent ()
15834 {
15835     char *tags = PGNTags(&gameInfo);
15836     TagsPopUp(tags, CmailMsg());
15837     free(tags);
15838 }
15839
15840 /* end button procedures */
15841
15842 void
15843 PrintPosition (FILE *fp, int move)
15844 {
15845     int i, j;
15846
15847     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15848         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15849             char c = PieceToChar(boards[move][i][j]);
15850             fputc(c == 'x' ? '.' : c, fp);
15851             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15852         }
15853     }
15854     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15855       fprintf(fp, "white to play\n");
15856     else
15857       fprintf(fp, "black to play\n");
15858 }
15859
15860 void
15861 PrintOpponents (FILE *fp)
15862 {
15863     if (gameInfo.white != NULL) {
15864         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15865     } else {
15866         fprintf(fp, "\n");
15867     }
15868 }
15869
15870 /* Find last component of program's own name, using some heuristics */
15871 void
15872 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15873 {
15874     char *p, *q, c;
15875     int local = (strcmp(host, "localhost") == 0);
15876     while (!local && (p = strchr(prog, ';')) != NULL) {
15877         p++;
15878         while (*p == ' ') p++;
15879         prog = p;
15880     }
15881     if (*prog == '"' || *prog == '\'') {
15882         q = strchr(prog + 1, *prog);
15883     } else {
15884         q = strchr(prog, ' ');
15885     }
15886     if (q == NULL) q = prog + strlen(prog);
15887     p = q;
15888     while (p >= prog && *p != '/' && *p != '\\') p--;
15889     p++;
15890     if(p == prog && *p == '"') p++;
15891     c = *q; *q = 0;
15892     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15893     memcpy(buf, p, q - p);
15894     buf[q - p] = NULLCHAR;
15895     if (!local) {
15896         strcat(buf, "@");
15897         strcat(buf, host);
15898     }
15899 }
15900
15901 char *
15902 TimeControlTagValue ()
15903 {
15904     char buf[MSG_SIZ];
15905     if (!appData.clockMode) {
15906       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15907     } else if (movesPerSession > 0) {
15908       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15909     } else if (timeIncrement == 0) {
15910       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15911     } else {
15912       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15913     }
15914     return StrSave(buf);
15915 }
15916
15917 void
15918 SetGameInfo ()
15919 {
15920     /* This routine is used only for certain modes */
15921     VariantClass v = gameInfo.variant;
15922     ChessMove r = GameUnfinished;
15923     char *p = NULL;
15924
15925     if(keepInfo) return;
15926
15927     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15928         r = gameInfo.result;
15929         p = gameInfo.resultDetails;
15930         gameInfo.resultDetails = NULL;
15931     }
15932     ClearGameInfo(&gameInfo);
15933     gameInfo.variant = v;
15934
15935     switch (gameMode) {
15936       case MachinePlaysWhite:
15937         gameInfo.event = StrSave( appData.pgnEventHeader );
15938         gameInfo.site = StrSave(HostName());
15939         gameInfo.date = PGNDate();
15940         gameInfo.round = StrSave("-");
15941         gameInfo.white = StrSave(first.tidy);
15942         gameInfo.black = StrSave(UserName());
15943         gameInfo.timeControl = TimeControlTagValue();
15944         break;
15945
15946       case MachinePlaysBlack:
15947         gameInfo.event = StrSave( appData.pgnEventHeader );
15948         gameInfo.site = StrSave(HostName());
15949         gameInfo.date = PGNDate();
15950         gameInfo.round = StrSave("-");
15951         gameInfo.white = StrSave(UserName());
15952         gameInfo.black = StrSave(first.tidy);
15953         gameInfo.timeControl = TimeControlTagValue();
15954         break;
15955
15956       case TwoMachinesPlay:
15957         gameInfo.event = StrSave( appData.pgnEventHeader );
15958         gameInfo.site = StrSave(HostName());
15959         gameInfo.date = PGNDate();
15960         if (roundNr > 0) {
15961             char buf[MSG_SIZ];
15962             snprintf(buf, MSG_SIZ, "%d", roundNr);
15963             gameInfo.round = StrSave(buf);
15964         } else {
15965             gameInfo.round = StrSave("-");
15966         }
15967         if (first.twoMachinesColor[0] == 'w') {
15968             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15969             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15970         } else {
15971             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15972             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15973         }
15974         gameInfo.timeControl = TimeControlTagValue();
15975         break;
15976
15977       case EditGame:
15978         gameInfo.event = StrSave("Edited game");
15979         gameInfo.site = StrSave(HostName());
15980         gameInfo.date = PGNDate();
15981         gameInfo.round = StrSave("-");
15982         gameInfo.white = StrSave("-");
15983         gameInfo.black = StrSave("-");
15984         gameInfo.result = r;
15985         gameInfo.resultDetails = p;
15986         break;
15987
15988       case EditPosition:
15989         gameInfo.event = StrSave("Edited position");
15990         gameInfo.site = StrSave(HostName());
15991         gameInfo.date = PGNDate();
15992         gameInfo.round = StrSave("-");
15993         gameInfo.white = StrSave("-");
15994         gameInfo.black = StrSave("-");
15995         break;
15996
15997       case IcsPlayingWhite:
15998       case IcsPlayingBlack:
15999       case IcsObserving:
16000       case IcsExamining:
16001         break;
16002
16003       case PlayFromGameFile:
16004         gameInfo.event = StrSave("Game from non-PGN file");
16005         gameInfo.site = StrSave(HostName());
16006         gameInfo.date = PGNDate();
16007         gameInfo.round = StrSave("-");
16008         gameInfo.white = StrSave("?");
16009         gameInfo.black = StrSave("?");
16010         break;
16011
16012       default:
16013         break;
16014     }
16015 }
16016
16017 void
16018 ReplaceComment (int index, char *text)
16019 {
16020     int len;
16021     char *p;
16022     float score;
16023
16024     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16025        pvInfoList[index-1].depth == len &&
16026        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16027        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16028     while (*text == '\n') text++;
16029     len = strlen(text);
16030     while (len > 0 && text[len - 1] == '\n') len--;
16031
16032     if (commentList[index] != NULL)
16033       free(commentList[index]);
16034
16035     if (len == 0) {
16036         commentList[index] = NULL;
16037         return;
16038     }
16039   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16040       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16041       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16042     commentList[index] = (char *) malloc(len + 2);
16043     strncpy(commentList[index], text, len);
16044     commentList[index][len] = '\n';
16045     commentList[index][len + 1] = NULLCHAR;
16046   } else {
16047     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16048     char *p;
16049     commentList[index] = (char *) malloc(len + 7);
16050     safeStrCpy(commentList[index], "{\n", 3);
16051     safeStrCpy(commentList[index]+2, text, len+1);
16052     commentList[index][len+2] = NULLCHAR;
16053     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16054     strcat(commentList[index], "\n}\n");
16055   }
16056 }
16057
16058 void
16059 CrushCRs (char *text)
16060 {
16061   char *p = text;
16062   char *q = text;
16063   char ch;
16064
16065   do {
16066     ch = *p++;
16067     if (ch == '\r') continue;
16068     *q++ = ch;
16069   } while (ch != '\0');
16070 }
16071
16072 void
16073 AppendComment (int index, char *text, Boolean addBraces)
16074 /* addBraces  tells if we should add {} */
16075 {
16076     int oldlen, len;
16077     char *old;
16078
16079 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16080     if(addBraces == 3) addBraces = 0; else // force appending literally
16081     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16082
16083     CrushCRs(text);
16084     while (*text == '\n') text++;
16085     len = strlen(text);
16086     while (len > 0 && text[len - 1] == '\n') len--;
16087     text[len] = NULLCHAR;
16088
16089     if (len == 0) return;
16090
16091     if (commentList[index] != NULL) {
16092       Boolean addClosingBrace = addBraces;
16093         old = commentList[index];
16094         oldlen = strlen(old);
16095         while(commentList[index][oldlen-1] ==  '\n')
16096           commentList[index][--oldlen] = NULLCHAR;
16097         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16098         safeStrCpy(commentList[index], old, oldlen + len + 6);
16099         free(old);
16100         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16101         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16102           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16103           while (*text == '\n') { text++; len--; }
16104           commentList[index][--oldlen] = NULLCHAR;
16105       }
16106         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16107         else          strcat(commentList[index], "\n");
16108         strcat(commentList[index], text);
16109         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16110         else          strcat(commentList[index], "\n");
16111     } else {
16112         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16113         if(addBraces)
16114           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16115         else commentList[index][0] = NULLCHAR;
16116         strcat(commentList[index], text);
16117         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16118         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16119     }
16120 }
16121
16122 static char *
16123 FindStr (char * text, char * sub_text)
16124 {
16125     char * result = strstr( text, sub_text );
16126
16127     if( result != NULL ) {
16128         result += strlen( sub_text );
16129     }
16130
16131     return result;
16132 }
16133
16134 /* [AS] Try to extract PV info from PGN comment */
16135 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16136 char *
16137 GetInfoFromComment (int index, char * text)
16138 {
16139     char * sep = text, *p;
16140
16141     if( text != NULL && index > 0 ) {
16142         int score = 0;
16143         int depth = 0;
16144         int time = -1, sec = 0, deci;
16145         char * s_eval = FindStr( text, "[%eval " );
16146         char * s_emt = FindStr( text, "[%emt " );
16147 #if 0
16148         if( s_eval != NULL || s_emt != NULL ) {
16149 #else
16150         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16151 #endif
16152             /* New style */
16153             char delim;
16154
16155             if( s_eval != NULL ) {
16156                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16157                     return text;
16158                 }
16159
16160                 if( delim != ']' ) {
16161                     return text;
16162                 }
16163             }
16164
16165             if( s_emt != NULL ) {
16166             }
16167                 return text;
16168         }
16169         else {
16170             /* We expect something like: [+|-]nnn.nn/dd */
16171             int score_lo = 0;
16172
16173             if(*text != '{') return text; // [HGM] braces: must be normal comment
16174
16175             sep = strchr( text, '/' );
16176             if( sep == NULL || sep < (text+4) ) {
16177                 return text;
16178             }
16179
16180             p = text;
16181             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16182             if(p[1] == '(') { // comment starts with PV
16183                p = strchr(p, ')'); // locate end of PV
16184                if(p == NULL || sep < p+5) return text;
16185                // at this point we have something like "{(.*) +0.23/6 ..."
16186                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16187                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16188                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16189             }
16190             time = -1; sec = -1; deci = -1;
16191             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16192                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16193                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16194                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16195                 return text;
16196             }
16197
16198             if( score_lo < 0 || score_lo >= 100 ) {
16199                 return text;
16200             }
16201
16202             if(sec >= 0) time = 600*time + 10*sec; else
16203             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16204
16205             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16206
16207             /* [HGM] PV time: now locate end of PV info */
16208             while( *++sep >= '0' && *sep <= '9'); // strip depth
16209             if(time >= 0)
16210             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16211             if(sec >= 0)
16212             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16213             if(deci >= 0)
16214             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16215             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16216         }
16217
16218         if( depth <= 0 ) {
16219             return text;
16220         }
16221
16222         if( time < 0 ) {
16223             time = -1;
16224         }
16225
16226         pvInfoList[index-1].depth = depth;
16227         pvInfoList[index-1].score = score;
16228         pvInfoList[index-1].time  = 10*time; // centi-sec
16229         if(*sep == '}') *sep = 0; else *--sep = '{';
16230         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16231     }
16232     return sep;
16233 }
16234
16235 void
16236 SendToProgram (char *message, ChessProgramState *cps)
16237 {
16238     int count, outCount, error;
16239     char buf[MSG_SIZ];
16240
16241     if (cps->pr == NoProc) return;
16242     Attention(cps);
16243
16244     if (appData.debugMode) {
16245         TimeMark now;
16246         GetTimeMark(&now);
16247         fprintf(debugFP, "%ld >%-6s: %s",
16248                 SubtractTimeMarks(&now, &programStartTime),
16249                 cps->which, message);
16250         if(serverFP)
16251             fprintf(serverFP, "%ld >%-6s: %s",
16252                 SubtractTimeMarks(&now, &programStartTime),
16253                 cps->which, message), fflush(serverFP);
16254     }
16255
16256     count = strlen(message);
16257     outCount = OutputToProcess(cps->pr, message, count, &error);
16258     if (outCount < count && !exiting
16259                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16260       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16261       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16262         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16263             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16264                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16265                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16266                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16267             } else {
16268                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16269                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16270                 gameInfo.result = res;
16271             }
16272             gameInfo.resultDetails = StrSave(buf);
16273         }
16274         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16275         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16276     }
16277 }
16278
16279 void
16280 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16281 {
16282     char *end_str;
16283     char buf[MSG_SIZ];
16284     ChessProgramState *cps = (ChessProgramState *)closure;
16285
16286     if (isr != cps->isr) return; /* Killed intentionally */
16287     if (count <= 0) {
16288         if (count == 0) {
16289             RemoveInputSource(cps->isr);
16290             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16291                     _(cps->which), cps->program);
16292             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16293             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16294                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16295                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16296                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16297                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16298                 } else {
16299                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16300                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16301                     gameInfo.result = res;
16302                 }
16303                 gameInfo.resultDetails = StrSave(buf);
16304             }
16305             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16306             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16307         } else {
16308             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16309                     _(cps->which), cps->program);
16310             RemoveInputSource(cps->isr);
16311
16312             /* [AS] Program is misbehaving badly... kill it */
16313             if( count == -2 ) {
16314                 DestroyChildProcess( cps->pr, 9 );
16315                 cps->pr = NoProc;
16316             }
16317
16318             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16319         }
16320         return;
16321     }
16322
16323     if ((end_str = strchr(message, '\r')) != NULL)
16324       *end_str = NULLCHAR;
16325     if ((end_str = strchr(message, '\n')) != NULL)
16326       *end_str = NULLCHAR;
16327
16328     if (appData.debugMode) {
16329         TimeMark now; int print = 1;
16330         char *quote = ""; char c; int i;
16331
16332         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16333                 char start = message[0];
16334                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16335                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16336                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16337                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16338                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16339                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16340                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16341                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16342                    sscanf(message, "hint: %c", &c)!=1 &&
16343                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16344                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16345                     print = (appData.engineComments >= 2);
16346                 }
16347                 message[0] = start; // restore original message
16348         }
16349         if(print) {
16350                 GetTimeMark(&now);
16351                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16352                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16353                         quote,
16354                         message);
16355                 if(serverFP)
16356                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16357                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16358                         quote,
16359                         message), fflush(serverFP);
16360         }
16361     }
16362
16363     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16364     if (appData.icsEngineAnalyze) {
16365         if (strstr(message, "whisper") != NULL ||
16366              strstr(message, "kibitz") != NULL ||
16367             strstr(message, "tellics") != NULL) return;
16368     }
16369
16370     HandleMachineMove(message, cps);
16371 }
16372
16373
16374 void
16375 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16376 {
16377     char buf[MSG_SIZ];
16378     int seconds;
16379
16380     if( timeControl_2 > 0 ) {
16381         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16382             tc = timeControl_2;
16383         }
16384     }
16385     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16386     inc /= cps->timeOdds;
16387     st  /= cps->timeOdds;
16388
16389     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16390
16391     if (st > 0) {
16392       /* Set exact time per move, normally using st command */
16393       if (cps->stKludge) {
16394         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16395         seconds = st % 60;
16396         if (seconds == 0) {
16397           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16398         } else {
16399           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16400         }
16401       } else {
16402         snprintf(buf, MSG_SIZ, "st %d\n", st);
16403       }
16404     } else {
16405       /* Set conventional or incremental time control, using level command */
16406       if (seconds == 0) {
16407         /* Note old gnuchess bug -- minutes:seconds used to not work.
16408            Fixed in later versions, but still avoid :seconds
16409            when seconds is 0. */
16410         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16411       } else {
16412         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16413                  seconds, inc/1000.);
16414       }
16415     }
16416     SendToProgram(buf, cps);
16417
16418     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16419     /* Orthogonally, limit search to given depth */
16420     if (sd > 0) {
16421       if (cps->sdKludge) {
16422         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16423       } else {
16424         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16425       }
16426       SendToProgram(buf, cps);
16427     }
16428
16429     if(cps->nps >= 0) { /* [HGM] nps */
16430         if(cps->supportsNPS == FALSE)
16431           cps->nps = -1; // don't use if engine explicitly says not supported!
16432         else {
16433           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16434           SendToProgram(buf, cps);
16435         }
16436     }
16437 }
16438
16439 ChessProgramState *
16440 WhitePlayer ()
16441 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16442 {
16443     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16444        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16445         return &second;
16446     return &first;
16447 }
16448
16449 void
16450 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16451 {
16452     char message[MSG_SIZ];
16453     long time, otime;
16454
16455     /* Note: this routine must be called when the clocks are stopped
16456        or when they have *just* been set or switched; otherwise
16457        it will be off by the time since the current tick started.
16458     */
16459     if (machineWhite) {
16460         time = whiteTimeRemaining / 10;
16461         otime = blackTimeRemaining / 10;
16462     } else {
16463         time = blackTimeRemaining / 10;
16464         otime = whiteTimeRemaining / 10;
16465     }
16466     /* [HGM] translate opponent's time by time-odds factor */
16467     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16468
16469     if (time <= 0) time = 1;
16470     if (otime <= 0) otime = 1;
16471
16472     snprintf(message, MSG_SIZ, "time %ld\n", time);
16473     SendToProgram(message, cps);
16474
16475     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16476     SendToProgram(message, cps);
16477 }
16478
16479 char *
16480 EngineDefinedVariant (ChessProgramState *cps, int n)
16481 {   // return name of n-th unknown variant that engine supports
16482     static char buf[MSG_SIZ];
16483     char *p, *s = cps->variants;
16484     if(!s) return NULL;
16485     do { // parse string from variants feature
16486       VariantClass v;
16487         p = strchr(s, ',');
16488         if(p) *p = NULLCHAR;
16489       v = StringToVariant(s);
16490       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16491         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16492             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16493         }
16494         if(p) *p++ = ',';
16495         if(n < 0) return buf;
16496     } while(s = p);
16497     return NULL;
16498 }
16499
16500 int
16501 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16502 {
16503   char buf[MSG_SIZ];
16504   int len = strlen(name);
16505   int val;
16506
16507   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16508     (*p) += len + 1;
16509     sscanf(*p, "%d", &val);
16510     *loc = (val != 0);
16511     while (**p && **p != ' ')
16512       (*p)++;
16513     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16514     SendToProgram(buf, cps);
16515     return TRUE;
16516   }
16517   return FALSE;
16518 }
16519
16520 int
16521 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16522 {
16523   char buf[MSG_SIZ];
16524   int len = strlen(name);
16525   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16526     (*p) += len + 1;
16527     sscanf(*p, "%d", loc);
16528     while (**p && **p != ' ') (*p)++;
16529     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16530     SendToProgram(buf, cps);
16531     return TRUE;
16532   }
16533   return FALSE;
16534 }
16535
16536 int
16537 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16538 {
16539   char buf[MSG_SIZ];
16540   int len = strlen(name);
16541   if (strncmp((*p), name, len) == 0
16542       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16543     (*p) += len + 2;
16544     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16545     sscanf(*p, "%[^\"]", *loc);
16546     while (**p && **p != '\"') (*p)++;
16547     if (**p == '\"') (*p)++;
16548     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16549     SendToProgram(buf, cps);
16550     return TRUE;
16551   }
16552   return FALSE;
16553 }
16554
16555 int
16556 ParseOption (Option *opt, ChessProgramState *cps)
16557 // [HGM] options: process the string that defines an engine option, and determine
16558 // name, type, default value, and allowed value range
16559 {
16560         char *p, *q, buf[MSG_SIZ];
16561         int n, min = (-1)<<31, max = 1<<31, def;
16562
16563         if(p = strstr(opt->name, " -spin ")) {
16564             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16565             if(max < min) max = min; // enforce consistency
16566             if(def < min) def = min;
16567             if(def > max) def = max;
16568             opt->value = def;
16569             opt->min = min;
16570             opt->max = max;
16571             opt->type = Spin;
16572         } else if((p = strstr(opt->name, " -slider "))) {
16573             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16574             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16575             if(max < min) max = min; // enforce consistency
16576             if(def < min) def = min;
16577             if(def > max) def = max;
16578             opt->value = def;
16579             opt->min = min;
16580             opt->max = max;
16581             opt->type = Spin; // Slider;
16582         } else if((p = strstr(opt->name, " -string "))) {
16583             opt->textValue = p+9;
16584             opt->type = TextBox;
16585         } else if((p = strstr(opt->name, " -file "))) {
16586             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16587             opt->textValue = p+7;
16588             opt->type = FileName; // FileName;
16589         } else if((p = strstr(opt->name, " -path "))) {
16590             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16591             opt->textValue = p+7;
16592             opt->type = PathName; // PathName;
16593         } else if(p = strstr(opt->name, " -check ")) {
16594             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16595             opt->value = (def != 0);
16596             opt->type = CheckBox;
16597         } else if(p = strstr(opt->name, " -combo ")) {
16598             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16599             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16600             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16601             opt->value = n = 0;
16602             while(q = StrStr(q, " /// ")) {
16603                 n++; *q = 0;    // count choices, and null-terminate each of them
16604                 q += 5;
16605                 if(*q == '*') { // remember default, which is marked with * prefix
16606                     q++;
16607                     opt->value = n;
16608                 }
16609                 cps->comboList[cps->comboCnt++] = q;
16610             }
16611             cps->comboList[cps->comboCnt++] = NULL;
16612             opt->max = n + 1;
16613             opt->type = ComboBox;
16614         } else if(p = strstr(opt->name, " -button")) {
16615             opt->type = Button;
16616         } else if(p = strstr(opt->name, " -save")) {
16617             opt->type = SaveButton;
16618         } else return FALSE;
16619         *p = 0; // terminate option name
16620         // now look if the command-line options define a setting for this engine option.
16621         if(cps->optionSettings && cps->optionSettings[0])
16622             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16623         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16624           snprintf(buf, MSG_SIZ, "option %s", p);
16625                 if(p = strstr(buf, ",")) *p = 0;
16626                 if(q = strchr(buf, '=')) switch(opt->type) {
16627                     case ComboBox:
16628                         for(n=0; n<opt->max; n++)
16629                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16630                         break;
16631                     case TextBox:
16632                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16633                         break;
16634                     case Spin:
16635                     case CheckBox:
16636                         opt->value = atoi(q+1);
16637                     default:
16638                         break;
16639                 }
16640                 strcat(buf, "\n");
16641                 SendToProgram(buf, cps);
16642         }
16643         return TRUE;
16644 }
16645
16646 void
16647 FeatureDone (ChessProgramState *cps, int val)
16648 {
16649   DelayedEventCallback cb = GetDelayedEvent();
16650   if ((cb == InitBackEnd3 && cps == &first) ||
16651       (cb == SettingsMenuIfReady && cps == &second) ||
16652       (cb == LoadEngine) ||
16653       (cb == TwoMachinesEventIfReady)) {
16654     CancelDelayedEvent();
16655     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16656   }
16657   cps->initDone = val;
16658   if(val) cps->reload = FALSE;
16659 }
16660
16661 /* Parse feature command from engine */
16662 void
16663 ParseFeatures (char *args, ChessProgramState *cps)
16664 {
16665   char *p = args;
16666   char *q = NULL;
16667   int val;
16668   char buf[MSG_SIZ];
16669
16670   for (;;) {
16671     while (*p == ' ') p++;
16672     if (*p == NULLCHAR) return;
16673
16674     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16675     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16676     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16677     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16678     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16679     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16680     if (BoolFeature(&p, "reuse", &val, cps)) {
16681       /* Engine can disable reuse, but can't enable it if user said no */
16682       if (!val) cps->reuse = FALSE;
16683       continue;
16684     }
16685     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16686     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16687       if (gameMode == TwoMachinesPlay) {
16688         DisplayTwoMachinesTitle();
16689       } else {
16690         DisplayTitle("");
16691       }
16692       continue;
16693     }
16694     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16695     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16696     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16697     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16698     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16699     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16700     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16701     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16702     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16703     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16704     if (IntFeature(&p, "done", &val, cps)) {
16705       FeatureDone(cps, val);
16706       continue;
16707     }
16708     /* Added by Tord: */
16709     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16710     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16711     /* End of additions by Tord */
16712
16713     /* [HGM] added features: */
16714     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16715     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16716     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16717     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16718     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16719     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16720     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16721     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16722         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16723         FREE(cps->option[cps->nrOptions].name);
16724         cps->option[cps->nrOptions].name = q; q = NULL;
16725         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16726           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16727             SendToProgram(buf, cps);
16728             continue;
16729         }
16730         if(cps->nrOptions >= MAX_OPTIONS) {
16731             cps->nrOptions--;
16732             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16733             DisplayError(buf, 0);
16734         }
16735         continue;
16736     }
16737     /* End of additions by HGM */
16738
16739     /* unknown feature: complain and skip */
16740     q = p;
16741     while (*q && *q != '=') q++;
16742     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16743     SendToProgram(buf, cps);
16744     p = q;
16745     if (*p == '=') {
16746       p++;
16747       if (*p == '\"') {
16748         p++;
16749         while (*p && *p != '\"') p++;
16750         if (*p == '\"') p++;
16751       } else {
16752         while (*p && *p != ' ') p++;
16753       }
16754     }
16755   }
16756
16757 }
16758
16759 void
16760 PeriodicUpdatesEvent (int newState)
16761 {
16762     if (newState == appData.periodicUpdates)
16763       return;
16764
16765     appData.periodicUpdates=newState;
16766
16767     /* Display type changes, so update it now */
16768 //    DisplayAnalysis();
16769
16770     /* Get the ball rolling again... */
16771     if (newState) {
16772         AnalysisPeriodicEvent(1);
16773         StartAnalysisClock();
16774     }
16775 }
16776
16777 void
16778 PonderNextMoveEvent (int newState)
16779 {
16780     if (newState == appData.ponderNextMove) return;
16781     if (gameMode == EditPosition) EditPositionDone(TRUE);
16782     if (newState) {
16783         SendToProgram("hard\n", &first);
16784         if (gameMode == TwoMachinesPlay) {
16785             SendToProgram("hard\n", &second);
16786         }
16787     } else {
16788         SendToProgram("easy\n", &first);
16789         thinkOutput[0] = NULLCHAR;
16790         if (gameMode == TwoMachinesPlay) {
16791             SendToProgram("easy\n", &second);
16792         }
16793     }
16794     appData.ponderNextMove = newState;
16795 }
16796
16797 void
16798 NewSettingEvent (int option, int *feature, char *command, int value)
16799 {
16800     char buf[MSG_SIZ];
16801
16802     if (gameMode == EditPosition) EditPositionDone(TRUE);
16803     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16804     if(feature == NULL || *feature) SendToProgram(buf, &first);
16805     if (gameMode == TwoMachinesPlay) {
16806         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16807     }
16808 }
16809
16810 void
16811 ShowThinkingEvent ()
16812 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16813 {
16814     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16815     int newState = appData.showThinking
16816         // [HGM] thinking: other features now need thinking output as well
16817         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16818
16819     if (oldState == newState) return;
16820     oldState = newState;
16821     if (gameMode == EditPosition) EditPositionDone(TRUE);
16822     if (oldState) {
16823         SendToProgram("post\n", &first);
16824         if (gameMode == TwoMachinesPlay) {
16825             SendToProgram("post\n", &second);
16826         }
16827     } else {
16828         SendToProgram("nopost\n", &first);
16829         thinkOutput[0] = NULLCHAR;
16830         if (gameMode == TwoMachinesPlay) {
16831             SendToProgram("nopost\n", &second);
16832         }
16833     }
16834 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16835 }
16836
16837 void
16838 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16839 {
16840   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16841   if (pr == NoProc) return;
16842   AskQuestion(title, question, replyPrefix, pr);
16843 }
16844
16845 void
16846 TypeInEvent (char firstChar)
16847 {
16848     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16849         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16850         gameMode == AnalyzeMode || gameMode == EditGame ||
16851         gameMode == EditPosition || gameMode == IcsExamining ||
16852         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16853         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16854                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16855                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16856         gameMode == Training) PopUpMoveDialog(firstChar);
16857 }
16858
16859 void
16860 TypeInDoneEvent (char *move)
16861 {
16862         Board board;
16863         int n, fromX, fromY, toX, toY;
16864         char promoChar;
16865         ChessMove moveType;
16866
16867         // [HGM] FENedit
16868         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16869                 EditPositionPasteFEN(move);
16870                 return;
16871         }
16872         // [HGM] movenum: allow move number to be typed in any mode
16873         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16874           ToNrEvent(2*n-1);
16875           return;
16876         }
16877         // undocumented kludge: allow command-line option to be typed in!
16878         // (potentially fatal, and does not implement the effect of the option.)
16879         // should only be used for options that are values on which future decisions will be made,
16880         // and definitely not on options that would be used during initialization.
16881         if(strstr(move, "!!! -") == move) {
16882             ParseArgsFromString(move+4);
16883             return;
16884         }
16885
16886       if (gameMode != EditGame && currentMove != forwardMostMove &&
16887         gameMode != Training) {
16888         DisplayMoveError(_("Displayed move is not current"));
16889       } else {
16890         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16891           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16892         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16893         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16894           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16895           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16896         } else {
16897           DisplayMoveError(_("Could not parse move"));
16898         }
16899       }
16900 }
16901
16902 void
16903 DisplayMove (int moveNumber)
16904 {
16905     char message[MSG_SIZ];
16906     char res[MSG_SIZ];
16907     char cpThinkOutput[MSG_SIZ];
16908
16909     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16910
16911     if (moveNumber == forwardMostMove - 1 ||
16912         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16913
16914         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16915
16916         if (strchr(cpThinkOutput, '\n')) {
16917             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16918         }
16919     } else {
16920         *cpThinkOutput = NULLCHAR;
16921     }
16922
16923     /* [AS] Hide thinking from human user */
16924     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16925         *cpThinkOutput = NULLCHAR;
16926         if( thinkOutput[0] != NULLCHAR ) {
16927             int i;
16928
16929             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16930                 cpThinkOutput[i] = '.';
16931             }
16932             cpThinkOutput[i] = NULLCHAR;
16933             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16934         }
16935     }
16936
16937     if (moveNumber == forwardMostMove - 1 &&
16938         gameInfo.resultDetails != NULL) {
16939         if (gameInfo.resultDetails[0] == NULLCHAR) {
16940           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16941         } else {
16942           snprintf(res, MSG_SIZ, " {%s} %s",
16943                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16944         }
16945     } else {
16946         res[0] = NULLCHAR;
16947     }
16948
16949     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16950         DisplayMessage(res, cpThinkOutput);
16951     } else {
16952       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16953                 WhiteOnMove(moveNumber) ? " " : ".. ",
16954                 parseList[moveNumber], res);
16955         DisplayMessage(message, cpThinkOutput);
16956     }
16957 }
16958
16959 void
16960 DisplayComment (int moveNumber, char *text)
16961 {
16962     char title[MSG_SIZ];
16963
16964     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16965       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16966     } else {
16967       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16968               WhiteOnMove(moveNumber) ? " " : ".. ",
16969               parseList[moveNumber]);
16970     }
16971     if (text != NULL && (appData.autoDisplayComment || commentUp))
16972         CommentPopUp(title, text);
16973 }
16974
16975 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16976  * might be busy thinking or pondering.  It can be omitted if your
16977  * gnuchess is configured to stop thinking immediately on any user
16978  * input.  However, that gnuchess feature depends on the FIONREAD
16979  * ioctl, which does not work properly on some flavors of Unix.
16980  */
16981 void
16982 Attention (ChessProgramState *cps)
16983 {
16984 #if ATTENTION
16985     if (!cps->useSigint) return;
16986     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16987     switch (gameMode) {
16988       case MachinePlaysWhite:
16989       case MachinePlaysBlack:
16990       case TwoMachinesPlay:
16991       case IcsPlayingWhite:
16992       case IcsPlayingBlack:
16993       case AnalyzeMode:
16994       case AnalyzeFile:
16995         /* Skip if we know it isn't thinking */
16996         if (!cps->maybeThinking) return;
16997         if (appData.debugMode)
16998           fprintf(debugFP, "Interrupting %s\n", cps->which);
16999         InterruptChildProcess(cps->pr);
17000         cps->maybeThinking = FALSE;
17001         break;
17002       default:
17003         break;
17004     }
17005 #endif /*ATTENTION*/
17006 }
17007
17008 int
17009 CheckFlags ()
17010 {
17011     if (whiteTimeRemaining <= 0) {
17012         if (!whiteFlag) {
17013             whiteFlag = TRUE;
17014             if (appData.icsActive) {
17015                 if (appData.autoCallFlag &&
17016                     gameMode == IcsPlayingBlack && !blackFlag) {
17017                   SendToICS(ics_prefix);
17018                   SendToICS("flag\n");
17019                 }
17020             } else {
17021                 if (blackFlag) {
17022                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17023                 } else {
17024                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17025                     if (appData.autoCallFlag) {
17026                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17027                         return TRUE;
17028                     }
17029                 }
17030             }
17031         }
17032     }
17033     if (blackTimeRemaining <= 0) {
17034         if (!blackFlag) {
17035             blackFlag = TRUE;
17036             if (appData.icsActive) {
17037                 if (appData.autoCallFlag &&
17038                     gameMode == IcsPlayingWhite && !whiteFlag) {
17039                   SendToICS(ics_prefix);
17040                   SendToICS("flag\n");
17041                 }
17042             } else {
17043                 if (whiteFlag) {
17044                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17045                 } else {
17046                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17047                     if (appData.autoCallFlag) {
17048                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17049                         return TRUE;
17050                     }
17051                 }
17052             }
17053         }
17054     }
17055     return FALSE;
17056 }
17057
17058 void
17059 CheckTimeControl ()
17060 {
17061     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17062         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17063
17064     /*
17065      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17066      */
17067     if ( !WhiteOnMove(forwardMostMove) ) {
17068         /* White made time control */
17069         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17070         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17071         /* [HGM] time odds: correct new time quota for time odds! */
17072                                             / WhitePlayer()->timeOdds;
17073         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17074     } else {
17075         lastBlack -= blackTimeRemaining;
17076         /* Black made time control */
17077         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17078                                             / WhitePlayer()->other->timeOdds;
17079         lastWhite = whiteTimeRemaining;
17080     }
17081 }
17082
17083 void
17084 DisplayBothClocks ()
17085 {
17086     int wom = gameMode == EditPosition ?
17087       !blackPlaysFirst : WhiteOnMove(currentMove);
17088     DisplayWhiteClock(whiteTimeRemaining, wom);
17089     DisplayBlackClock(blackTimeRemaining, !wom);
17090 }
17091
17092
17093 /* Timekeeping seems to be a portability nightmare.  I think everyone
17094    has ftime(), but I'm really not sure, so I'm including some ifdefs
17095    to use other calls if you don't.  Clocks will be less accurate if
17096    you have neither ftime nor gettimeofday.
17097 */
17098
17099 /* VS 2008 requires the #include outside of the function */
17100 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17101 #include <sys/timeb.h>
17102 #endif
17103
17104 /* Get the current time as a TimeMark */
17105 void
17106 GetTimeMark (TimeMark *tm)
17107 {
17108 #if HAVE_GETTIMEOFDAY
17109
17110     struct timeval timeVal;
17111     struct timezone timeZone;
17112
17113     gettimeofday(&timeVal, &timeZone);
17114     tm->sec = (long) timeVal.tv_sec;
17115     tm->ms = (int) (timeVal.tv_usec / 1000L);
17116
17117 #else /*!HAVE_GETTIMEOFDAY*/
17118 #if HAVE_FTIME
17119
17120 // include <sys/timeb.h> / moved to just above start of function
17121     struct timeb timeB;
17122
17123     ftime(&timeB);
17124     tm->sec = (long) timeB.time;
17125     tm->ms = (int) timeB.millitm;
17126
17127 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17128     tm->sec = (long) time(NULL);
17129     tm->ms = 0;
17130 #endif
17131 #endif
17132 }
17133
17134 /* Return the difference in milliseconds between two
17135    time marks.  We assume the difference will fit in a long!
17136 */
17137 long
17138 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17139 {
17140     return 1000L*(tm2->sec - tm1->sec) +
17141            (long) (tm2->ms - tm1->ms);
17142 }
17143
17144
17145 /*
17146  * Code to manage the game clocks.
17147  *
17148  * In tournament play, black starts the clock and then white makes a move.
17149  * We give the human user a slight advantage if he is playing white---the
17150  * clocks don't run until he makes his first move, so it takes zero time.
17151  * Also, we don't account for network lag, so we could get out of sync
17152  * with GNU Chess's clock -- but then, referees are always right.
17153  */
17154
17155 static TimeMark tickStartTM;
17156 static long intendedTickLength;
17157
17158 long
17159 NextTickLength (long timeRemaining)
17160 {
17161     long nominalTickLength, nextTickLength;
17162
17163     if (timeRemaining > 0L && timeRemaining <= 10000L)
17164       nominalTickLength = 100L;
17165     else
17166       nominalTickLength = 1000L;
17167     nextTickLength = timeRemaining % nominalTickLength;
17168     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17169
17170     return nextTickLength;
17171 }
17172
17173 /* Adjust clock one minute up or down */
17174 void
17175 AdjustClock (Boolean which, int dir)
17176 {
17177     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17178     if(which) blackTimeRemaining += 60000*dir;
17179     else      whiteTimeRemaining += 60000*dir;
17180     DisplayBothClocks();
17181     adjustedClock = TRUE;
17182 }
17183
17184 /* Stop clocks and reset to a fresh time control */
17185 void
17186 ResetClocks ()
17187 {
17188     (void) StopClockTimer();
17189     if (appData.icsActive) {
17190         whiteTimeRemaining = blackTimeRemaining = 0;
17191     } else if (searchTime) {
17192         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17193         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17194     } else { /* [HGM] correct new time quote for time odds */
17195         whiteTC = blackTC = fullTimeControlString;
17196         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17197         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17198     }
17199     if (whiteFlag || blackFlag) {
17200         DisplayTitle("");
17201         whiteFlag = blackFlag = FALSE;
17202     }
17203     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17204     DisplayBothClocks();
17205     adjustedClock = FALSE;
17206 }
17207
17208 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17209
17210 /* Decrement running clock by amount of time that has passed */
17211 void
17212 DecrementClocks ()
17213 {
17214     long timeRemaining;
17215     long lastTickLength, fudge;
17216     TimeMark now;
17217
17218     if (!appData.clockMode) return;
17219     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17220
17221     GetTimeMark(&now);
17222
17223     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17224
17225     /* Fudge if we woke up a little too soon */
17226     fudge = intendedTickLength - lastTickLength;
17227     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17228
17229     if (WhiteOnMove(forwardMostMove)) {
17230         if(whiteNPS >= 0) lastTickLength = 0;
17231         timeRemaining = whiteTimeRemaining -= lastTickLength;
17232         if(timeRemaining < 0 && !appData.icsActive) {
17233             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17234             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17235                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17236                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17237             }
17238         }
17239         DisplayWhiteClock(whiteTimeRemaining - fudge,
17240                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17241     } else {
17242         if(blackNPS >= 0) lastTickLength = 0;
17243         timeRemaining = blackTimeRemaining -= lastTickLength;
17244         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17245             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17246             if(suddenDeath) {
17247                 blackStartMove = forwardMostMove;
17248                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17249             }
17250         }
17251         DisplayBlackClock(blackTimeRemaining - fudge,
17252                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17253     }
17254     if (CheckFlags()) return;
17255
17256     if(twoBoards) { // count down secondary board's clocks as well
17257         activePartnerTime -= lastTickLength;
17258         partnerUp = 1;
17259         if(activePartner == 'W')
17260             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17261         else
17262             DisplayBlackClock(activePartnerTime, TRUE);
17263         partnerUp = 0;
17264     }
17265
17266     tickStartTM = now;
17267     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17268     StartClockTimer(intendedTickLength);
17269
17270     /* if the time remaining has fallen below the alarm threshold, sound the
17271      * alarm. if the alarm has sounded and (due to a takeback or time control
17272      * with increment) the time remaining has increased to a level above the
17273      * threshold, reset the alarm so it can sound again.
17274      */
17275
17276     if (appData.icsActive && appData.icsAlarm) {
17277
17278         /* make sure we are dealing with the user's clock */
17279         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17280                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17281            )) return;
17282
17283         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17284             alarmSounded = FALSE;
17285         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17286             PlayAlarmSound();
17287             alarmSounded = TRUE;
17288         }
17289     }
17290 }
17291
17292
17293 /* A player has just moved, so stop the previously running
17294    clock and (if in clock mode) start the other one.
17295    We redisplay both clocks in case we're in ICS mode, because
17296    ICS gives us an update to both clocks after every move.
17297    Note that this routine is called *after* forwardMostMove
17298    is updated, so the last fractional tick must be subtracted
17299    from the color that is *not* on move now.
17300 */
17301 void
17302 SwitchClocks (int newMoveNr)
17303 {
17304     long lastTickLength;
17305     TimeMark now;
17306     int flagged = FALSE;
17307
17308     GetTimeMark(&now);
17309
17310     if (StopClockTimer() && appData.clockMode) {
17311         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17312         if (!WhiteOnMove(forwardMostMove)) {
17313             if(blackNPS >= 0) lastTickLength = 0;
17314             blackTimeRemaining -= lastTickLength;
17315            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17316 //         if(pvInfoList[forwardMostMove].time == -1)
17317                  pvInfoList[forwardMostMove].time =               // use GUI time
17318                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17319         } else {
17320            if(whiteNPS >= 0) lastTickLength = 0;
17321            whiteTimeRemaining -= lastTickLength;
17322            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17323 //         if(pvInfoList[forwardMostMove].time == -1)
17324                  pvInfoList[forwardMostMove].time =
17325                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17326         }
17327         flagged = CheckFlags();
17328     }
17329     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17330     CheckTimeControl();
17331
17332     if (flagged || !appData.clockMode) return;
17333
17334     switch (gameMode) {
17335       case MachinePlaysBlack:
17336       case MachinePlaysWhite:
17337       case BeginningOfGame:
17338         if (pausing) return;
17339         break;
17340
17341       case EditGame:
17342       case PlayFromGameFile:
17343       case IcsExamining:
17344         return;
17345
17346       default:
17347         break;
17348     }
17349
17350     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17351         if(WhiteOnMove(forwardMostMove))
17352              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17353         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17354     }
17355
17356     tickStartTM = now;
17357     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17358       whiteTimeRemaining : blackTimeRemaining);
17359     StartClockTimer(intendedTickLength);
17360 }
17361
17362
17363 /* Stop both clocks */
17364 void
17365 StopClocks ()
17366 {
17367     long lastTickLength;
17368     TimeMark now;
17369
17370     if (!StopClockTimer()) return;
17371     if (!appData.clockMode) return;
17372
17373     GetTimeMark(&now);
17374
17375     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17376     if (WhiteOnMove(forwardMostMove)) {
17377         if(whiteNPS >= 0) lastTickLength = 0;
17378         whiteTimeRemaining -= lastTickLength;
17379         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17380     } else {
17381         if(blackNPS >= 0) lastTickLength = 0;
17382         blackTimeRemaining -= lastTickLength;
17383         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17384     }
17385     CheckFlags();
17386 }
17387
17388 /* Start clock of player on move.  Time may have been reset, so
17389    if clock is already running, stop and restart it. */
17390 void
17391 StartClocks ()
17392 {
17393     (void) StopClockTimer(); /* in case it was running already */
17394     DisplayBothClocks();
17395     if (CheckFlags()) return;
17396
17397     if (!appData.clockMode) return;
17398     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17399
17400     GetTimeMark(&tickStartTM);
17401     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17402       whiteTimeRemaining : blackTimeRemaining);
17403
17404    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17405     whiteNPS = blackNPS = -1;
17406     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17407        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17408         whiteNPS = first.nps;
17409     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17410        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17411         blackNPS = first.nps;
17412     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17413         whiteNPS = second.nps;
17414     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17415         blackNPS = second.nps;
17416     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17417
17418     StartClockTimer(intendedTickLength);
17419 }
17420
17421 char *
17422 TimeString (long ms)
17423 {
17424     long second, minute, hour, day;
17425     char *sign = "";
17426     static char buf[32];
17427
17428     if (ms > 0 && ms <= 9900) {
17429       /* convert milliseconds to tenths, rounding up */
17430       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17431
17432       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17433       return buf;
17434     }
17435
17436     /* convert milliseconds to seconds, rounding up */
17437     /* use floating point to avoid strangeness of integer division
17438        with negative dividends on many machines */
17439     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17440
17441     if (second < 0) {
17442         sign = "-";
17443         second = -second;
17444     }
17445
17446     day = second / (60 * 60 * 24);
17447     second = second % (60 * 60 * 24);
17448     hour = second / (60 * 60);
17449     second = second % (60 * 60);
17450     minute = second / 60;
17451     second = second % 60;
17452
17453     if (day > 0)
17454       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17455               sign, day, hour, minute, second);
17456     else if (hour > 0)
17457       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17458     else
17459       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17460
17461     return buf;
17462 }
17463
17464
17465 /*
17466  * This is necessary because some C libraries aren't ANSI C compliant yet.
17467  */
17468 char *
17469 StrStr (char *string, char *match)
17470 {
17471     int i, length;
17472
17473     length = strlen(match);
17474
17475     for (i = strlen(string) - length; i >= 0; i--, string++)
17476       if (!strncmp(match, string, length))
17477         return string;
17478
17479     return NULL;
17480 }
17481
17482 char *
17483 StrCaseStr (char *string, char *match)
17484 {
17485     int i, j, length;
17486
17487     length = strlen(match);
17488
17489     for (i = strlen(string) - length; i >= 0; i--, string++) {
17490         for (j = 0; j < length; j++) {
17491             if (ToLower(match[j]) != ToLower(string[j]))
17492               break;
17493         }
17494         if (j == length) return string;
17495     }
17496
17497     return NULL;
17498 }
17499
17500 #ifndef _amigados
17501 int
17502 StrCaseCmp (char *s1, char *s2)
17503 {
17504     char c1, c2;
17505
17506     for (;;) {
17507         c1 = ToLower(*s1++);
17508         c2 = ToLower(*s2++);
17509         if (c1 > c2) return 1;
17510         if (c1 < c2) return -1;
17511         if (c1 == NULLCHAR) return 0;
17512     }
17513 }
17514
17515
17516 int
17517 ToLower (int c)
17518 {
17519     return isupper(c) ? tolower(c) : c;
17520 }
17521
17522
17523 int
17524 ToUpper (int c)
17525 {
17526     return islower(c) ? toupper(c) : c;
17527 }
17528 #endif /* !_amigados    */
17529
17530 char *
17531 StrSave (char *s)
17532 {
17533   char *ret;
17534
17535   if ((ret = (char *) malloc(strlen(s) + 1)))
17536     {
17537       safeStrCpy(ret, s, strlen(s)+1);
17538     }
17539   return ret;
17540 }
17541
17542 char *
17543 StrSavePtr (char *s, char **savePtr)
17544 {
17545     if (*savePtr) {
17546         free(*savePtr);
17547     }
17548     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17549       safeStrCpy(*savePtr, s, strlen(s)+1);
17550     }
17551     return(*savePtr);
17552 }
17553
17554 char *
17555 PGNDate ()
17556 {
17557     time_t clock;
17558     struct tm *tm;
17559     char buf[MSG_SIZ];
17560
17561     clock = time((time_t *)NULL);
17562     tm = localtime(&clock);
17563     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17564             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17565     return StrSave(buf);
17566 }
17567
17568
17569 char *
17570 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17571 {
17572     int i, j, fromX, fromY, toX, toY;
17573     int whiteToPlay;
17574     char buf[MSG_SIZ];
17575     char *p, *q;
17576     int emptycount;
17577     ChessSquare piece;
17578
17579     whiteToPlay = (gameMode == EditPosition) ?
17580       !blackPlaysFirst : (move % 2 == 0);
17581     p = buf;
17582
17583     /* Piece placement data */
17584     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17585         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17586         emptycount = 0;
17587         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17588             if (boards[move][i][j] == EmptySquare) {
17589                 emptycount++;
17590             } else { ChessSquare piece = boards[move][i][j];
17591                 if (emptycount > 0) {
17592                     if(emptycount<10) /* [HGM] can be >= 10 */
17593                         *p++ = '0' + emptycount;
17594                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17595                     emptycount = 0;
17596                 }
17597                 if(PieceToChar(piece) == '+') {
17598                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17599                     *p++ = '+';
17600                     piece = (ChessSquare)(DEMOTED piece);
17601                 }
17602                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17603                 if(p[-1] == '~') {
17604                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17605                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17606                     *p++ = '~';
17607                 }
17608             }
17609         }
17610         if (emptycount > 0) {
17611             if(emptycount<10) /* [HGM] can be >= 10 */
17612                 *p++ = '0' + emptycount;
17613             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17614             emptycount = 0;
17615         }
17616         *p++ = '/';
17617     }
17618     *(p - 1) = ' ';
17619
17620     /* [HGM] print Crazyhouse or Shogi holdings */
17621     if( gameInfo.holdingsWidth ) {
17622         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17623         q = p;
17624         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17625             piece = boards[move][i][BOARD_WIDTH-1];
17626             if( piece != EmptySquare )
17627               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17628                   *p++ = PieceToChar(piece);
17629         }
17630         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17631             piece = boards[move][BOARD_HEIGHT-i-1][0];
17632             if( piece != EmptySquare )
17633               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17634                   *p++ = PieceToChar(piece);
17635         }
17636
17637         if( q == p ) *p++ = '-';
17638         *p++ = ']';
17639         *p++ = ' ';
17640     }
17641
17642     /* Active color */
17643     *p++ = whiteToPlay ? 'w' : 'b';
17644     *p++ = ' ';
17645
17646   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17647     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17648   } else {
17649   if(nrCastlingRights) {
17650      q = p;
17651      if(appData.fischerCastling) {
17652        /* [HGM] write directly from rights */
17653            if(boards[move][CASTLING][2] != NoRights &&
17654               boards[move][CASTLING][0] != NoRights   )
17655                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17656            if(boards[move][CASTLING][2] != NoRights &&
17657               boards[move][CASTLING][1] != NoRights   )
17658                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17659            if(boards[move][CASTLING][5] != NoRights &&
17660               boards[move][CASTLING][3] != NoRights   )
17661                 *p++ = boards[move][CASTLING][3] + AAA;
17662            if(boards[move][CASTLING][5] != NoRights &&
17663               boards[move][CASTLING][4] != NoRights   )
17664                 *p++ = boards[move][CASTLING][4] + AAA;
17665      } else {
17666
17667         /* [HGM] write true castling rights */
17668         if( nrCastlingRights == 6 ) {
17669             int q, k=0;
17670             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17671                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17672             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17673                  boards[move][CASTLING][2] != NoRights  );
17674             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17675                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17676                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17677                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17678                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17679             }
17680             if(q) *p++ = 'Q';
17681             k = 0;
17682             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17683                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17684             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17685                  boards[move][CASTLING][5] != NoRights  );
17686             if(gameInfo.variant == VariantSChess) {
17687                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17688                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17689                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17690                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17691             }
17692             if(q) *p++ = 'q';
17693         }
17694      }
17695      if (q == p) *p++ = '-'; /* No castling rights */
17696      *p++ = ' ';
17697   }
17698
17699   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17700      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17701      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17702     /* En passant target square */
17703     if (move > backwardMostMove) {
17704         fromX = moveList[move - 1][0] - AAA;
17705         fromY = moveList[move - 1][1] - ONE;
17706         toX = moveList[move - 1][2] - AAA;
17707         toY = moveList[move - 1][3] - ONE;
17708         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17709             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17710             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17711             fromX == toX) {
17712             /* 2-square pawn move just happened */
17713             *p++ = toX + AAA;
17714             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17715         } else {
17716             *p++ = '-';
17717         }
17718     } else if(move == backwardMostMove) {
17719         // [HGM] perhaps we should always do it like this, and forget the above?
17720         if((signed char)boards[move][EP_STATUS] >= 0) {
17721             *p++ = boards[move][EP_STATUS] + AAA;
17722             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17723         } else {
17724             *p++ = '-';
17725         }
17726     } else {
17727         *p++ = '-';
17728     }
17729     *p++ = ' ';
17730   }
17731   }
17732
17733     if(moveCounts)
17734     {   int i = 0, j=move;
17735
17736         /* [HGM] find reversible plies */
17737         if (appData.debugMode) { int k;
17738             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17739             for(k=backwardMostMove; k<=forwardMostMove; k++)
17740                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17741
17742         }
17743
17744         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17745         if( j == backwardMostMove ) i += initialRulePlies;
17746         sprintf(p, "%d ", i);
17747         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17748
17749         /* Fullmove number */
17750         sprintf(p, "%d", (move / 2) + 1);
17751     } else *--p = NULLCHAR;
17752
17753     return StrSave(buf);
17754 }
17755
17756 Boolean
17757 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17758 {
17759     int i, j, k, w=0, subst=0, shuffle=0;
17760     char *p, c;
17761     int emptycount, virgin[BOARD_FILES];
17762     ChessSquare piece;
17763
17764     p = fen;
17765
17766     /* Piece placement data */
17767     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17768         j = 0;
17769         for (;;) {
17770             if (*p == '/' || *p == ' ' || *p == '[' ) {
17771                 if(j > w) w = j;
17772                 emptycount = gameInfo.boardWidth - j;
17773                 while (emptycount--)
17774                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17775                 if (*p == '/') p++;
17776                 else if(autoSize) { // we stumbled unexpectedly into end of board
17777                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17778                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17779                     }
17780                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17781                 }
17782                 break;
17783 #if(BOARD_FILES >= 10)
17784             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17785                 p++; emptycount=10;
17786                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17787                 while (emptycount--)
17788                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17789 #endif
17790             } else if (*p == '*') {
17791                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17792             } else if (isdigit(*p)) {
17793                 emptycount = *p++ - '0';
17794                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17795                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17796                 while (emptycount--)
17797                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17798             } else if (*p == '<') {
17799                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17800                 else if (i != 0 || !shuffle) return FALSE;
17801                 p++;
17802             } else if (shuffle && *p == '>') {
17803                 p++; // for now ignore closing shuffle range, and assume rank-end
17804             } else if (*p == '?') {
17805                 if (j >= gameInfo.boardWidth) return FALSE;
17806                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17807                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17808             } else if (*p == '+' || isalpha(*p)) {
17809                 if (j >= gameInfo.boardWidth) return FALSE;
17810                 if(*p=='+') {
17811                     piece = CharToPiece(*++p);
17812                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17813                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17814                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17815                 } else piece = CharToPiece(*p++);
17816
17817                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17818                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17819                     piece = (ChessSquare) (PROMOTED piece);
17820                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17821                     p++;
17822                 }
17823                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17824             } else {
17825                 return FALSE;
17826             }
17827         }
17828     }
17829     while (*p == '/' || *p == ' ') p++;
17830
17831     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17832
17833     /* [HGM] by default clear Crazyhouse holdings, if present */
17834     if(gameInfo.holdingsWidth) {
17835        for(i=0; i<BOARD_HEIGHT; i++) {
17836            board[i][0]             = EmptySquare; /* black holdings */
17837            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17838            board[i][1]             = (ChessSquare) 0; /* black counts */
17839            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17840        }
17841     }
17842
17843     /* [HGM] look for Crazyhouse holdings here */
17844     while(*p==' ') p++;
17845     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17846         int swap=0, wcnt=0, bcnt=0;
17847         if(*p == '[') p++;
17848         if(*p == '<') swap++, p++;
17849         if(*p == '-' ) p++; /* empty holdings */ else {
17850             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17851             /* if we would allow FEN reading to set board size, we would   */
17852             /* have to add holdings and shift the board read so far here   */
17853             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17854                 p++;
17855                 if((int) piece >= (int) BlackPawn ) {
17856                     i = (int)piece - (int)BlackPawn;
17857                     i = PieceToNumber((ChessSquare)i);
17858                     if( i >= gameInfo.holdingsSize ) return FALSE;
17859                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17860                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17861                     bcnt++;
17862                 } else {
17863                     i = (int)piece - (int)WhitePawn;
17864                     i = PieceToNumber((ChessSquare)i);
17865                     if( i >= gameInfo.holdingsSize ) return FALSE;
17866                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17867                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17868                     wcnt++;
17869                 }
17870             }
17871             if(subst) { // substitute back-rank question marks by holdings pieces
17872                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17873                     int k, m, n = bcnt + 1;
17874                     if(board[0][j] == ClearBoard) {
17875                         if(!wcnt) return FALSE;
17876                         n = rand() % wcnt;
17877                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17878                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17879                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17880                             break;
17881                         }
17882                     }
17883                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17884                         if(!bcnt) return FALSE;
17885                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17886                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17887                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17888                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17889                             break;
17890                         }
17891                     }
17892                 }
17893                 subst = 0;
17894             }
17895         }
17896         if(*p == ']') p++;
17897     }
17898
17899     if(subst) return FALSE; // substitution requested, but no holdings
17900     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
17901
17902     while(*p == ' ') p++;
17903
17904     /* Active color */
17905     c = *p++;
17906     if(appData.colorNickNames) {
17907       if( c == appData.colorNickNames[0] ) c = 'w'; else
17908       if( c == appData.colorNickNames[1] ) c = 'b';
17909     }
17910     switch (c) {
17911       case 'w':
17912         *blackPlaysFirst = FALSE;
17913         break;
17914       case 'b':
17915         *blackPlaysFirst = TRUE;
17916         break;
17917       default:
17918         return FALSE;
17919     }
17920
17921     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17922     /* return the extra info in global variiables             */
17923
17924     /* set defaults in case FEN is incomplete */
17925     board[EP_STATUS] = EP_UNKNOWN;
17926     for(i=0; i<nrCastlingRights; i++ ) {
17927         board[CASTLING][i] =
17928             appData.fischerCastling ? NoRights : initialRights[i];
17929     }   /* assume possible unless obviously impossible */
17930     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17931     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17932     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17933                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17934     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17935     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17936     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17937                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17938     FENrulePlies = 0;
17939
17940     while(*p==' ') p++;
17941     if(nrCastlingRights) {
17942       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17943       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17944           /* castling indicator present, so default becomes no castlings */
17945           for(i=0; i<nrCastlingRights; i++ ) {
17946                  board[CASTLING][i] = NoRights;
17947           }
17948       }
17949       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17950              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17951              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17952              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17953         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17954
17955         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17956             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17957             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17958         }
17959         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17960             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17961         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17962                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17963         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17964                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17965         switch(c) {
17966           case'K':
17967               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17968               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17969               board[CASTLING][2] = whiteKingFile;
17970               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17971               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17972               break;
17973           case'Q':
17974               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17975               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17976               board[CASTLING][2] = whiteKingFile;
17977               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17978               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17979               break;
17980           case'k':
17981               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17982               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17983               board[CASTLING][5] = blackKingFile;
17984               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17985               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17986               break;
17987           case'q':
17988               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17989               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17990               board[CASTLING][5] = blackKingFile;
17991               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17992               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17993           case '-':
17994               break;
17995           default: /* FRC castlings */
17996               if(c >= 'a') { /* black rights */
17997                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17998                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17999                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18000                   if(i == BOARD_RGHT) break;
18001                   board[CASTLING][5] = i;
18002                   c -= AAA;
18003                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18004                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18005                   if(c > i)
18006                       board[CASTLING][3] = c;
18007                   else
18008                       board[CASTLING][4] = c;
18009               } else { /* white rights */
18010                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18011                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18012                     if(board[0][i] == WhiteKing) break;
18013                   if(i == BOARD_RGHT) break;
18014                   board[CASTLING][2] = i;
18015                   c -= AAA - 'a' + 'A';
18016                   if(board[0][c] >= WhiteKing) break;
18017                   if(c > i)
18018                       board[CASTLING][0] = c;
18019                   else
18020                       board[CASTLING][1] = c;
18021               }
18022         }
18023       }
18024       for(i=0; i<nrCastlingRights; i++)
18025         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18026       if(gameInfo.variant == VariantSChess)
18027         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18028     if (appData.debugMode) {
18029         fprintf(debugFP, "FEN castling rights:");
18030         for(i=0; i<nrCastlingRights; i++)
18031         fprintf(debugFP, " %d", board[CASTLING][i]);
18032         fprintf(debugFP, "\n");
18033     }
18034
18035       while(*p==' ') p++;
18036     }
18037
18038     /* read e.p. field in games that know e.p. capture */
18039     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18040        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18041        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18042       if(*p=='-') {
18043         p++; board[EP_STATUS] = EP_NONE;
18044       } else {
18045          char c = *p++ - AAA;
18046
18047          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18048          if(*p >= '0' && *p <='9') p++;
18049          board[EP_STATUS] = c;
18050       }
18051     }
18052
18053
18054     if(sscanf(p, "%d", &i) == 1) {
18055         FENrulePlies = i; /* 50-move ply counter */
18056         /* (The move number is still ignored)    */
18057     }
18058
18059     return TRUE;
18060 }
18061
18062 void
18063 EditPositionPasteFEN (char *fen)
18064 {
18065   if (fen != NULL) {
18066     Board initial_position;
18067
18068     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18069       DisplayError(_("Bad FEN position in clipboard"), 0);
18070       return ;
18071     } else {
18072       int savedBlackPlaysFirst = blackPlaysFirst;
18073       EditPositionEvent();
18074       blackPlaysFirst = savedBlackPlaysFirst;
18075       CopyBoard(boards[0], initial_position);
18076       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18077       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18078       DisplayBothClocks();
18079       DrawPosition(FALSE, boards[currentMove]);
18080     }
18081   }
18082 }
18083
18084 static char cseq[12] = "\\   ";
18085
18086 Boolean
18087 set_cont_sequence (char *new_seq)
18088 {
18089     int len;
18090     Boolean ret;
18091
18092     // handle bad attempts to set the sequence
18093         if (!new_seq)
18094                 return 0; // acceptable error - no debug
18095
18096     len = strlen(new_seq);
18097     ret = (len > 0) && (len < sizeof(cseq));
18098     if (ret)
18099       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18100     else if (appData.debugMode)
18101       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18102     return ret;
18103 }
18104
18105 /*
18106     reformat a source message so words don't cross the width boundary.  internal
18107     newlines are not removed.  returns the wrapped size (no null character unless
18108     included in source message).  If dest is NULL, only calculate the size required
18109     for the dest buffer.  lp argument indicats line position upon entry, and it's
18110     passed back upon exit.
18111 */
18112 int
18113 wrap (char *dest, char *src, int count, int width, int *lp)
18114 {
18115     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18116
18117     cseq_len = strlen(cseq);
18118     old_line = line = *lp;
18119     ansi = len = clen = 0;
18120
18121     for (i=0; i < count; i++)
18122     {
18123         if (src[i] == '\033')
18124             ansi = 1;
18125
18126         // if we hit the width, back up
18127         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18128         {
18129             // store i & len in case the word is too long
18130             old_i = i, old_len = len;
18131
18132             // find the end of the last word
18133             while (i && src[i] != ' ' && src[i] != '\n')
18134             {
18135                 i--;
18136                 len--;
18137             }
18138
18139             // word too long?  restore i & len before splitting it
18140             if ((old_i-i+clen) >= width)
18141             {
18142                 i = old_i;
18143                 len = old_len;
18144             }
18145
18146             // extra space?
18147             if (i && src[i-1] == ' ')
18148                 len--;
18149
18150             if (src[i] != ' ' && src[i] != '\n')
18151             {
18152                 i--;
18153                 if (len)
18154                     len--;
18155             }
18156
18157             // now append the newline and continuation sequence
18158             if (dest)
18159                 dest[len] = '\n';
18160             len++;
18161             if (dest)
18162                 strncpy(dest+len, cseq, cseq_len);
18163             len += cseq_len;
18164             line = cseq_len;
18165             clen = cseq_len;
18166             continue;
18167         }
18168
18169         if (dest)
18170             dest[len] = src[i];
18171         len++;
18172         if (!ansi)
18173             line++;
18174         if (src[i] == '\n')
18175             line = 0;
18176         if (src[i] == 'm')
18177             ansi = 0;
18178     }
18179     if (dest && appData.debugMode)
18180     {
18181         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18182             count, width, line, len, *lp);
18183         show_bytes(debugFP, src, count);
18184         fprintf(debugFP, "\ndest: ");
18185         show_bytes(debugFP, dest, len);
18186         fprintf(debugFP, "\n");
18187     }
18188     *lp = dest ? line : old_line;
18189
18190     return len;
18191 }
18192
18193 // [HGM] vari: routines for shelving variations
18194 Boolean modeRestore = FALSE;
18195
18196 void
18197 PushInner (int firstMove, int lastMove)
18198 {
18199         int i, j, nrMoves = lastMove - firstMove;
18200
18201         // push current tail of game on stack
18202         savedResult[storedGames] = gameInfo.result;
18203         savedDetails[storedGames] = gameInfo.resultDetails;
18204         gameInfo.resultDetails = NULL;
18205         savedFirst[storedGames] = firstMove;
18206         savedLast [storedGames] = lastMove;
18207         savedFramePtr[storedGames] = framePtr;
18208         framePtr -= nrMoves; // reserve space for the boards
18209         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18210             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18211             for(j=0; j<MOVE_LEN; j++)
18212                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18213             for(j=0; j<2*MOVE_LEN; j++)
18214                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18215             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18216             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18217             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18218             pvInfoList[firstMove+i-1].depth = 0;
18219             commentList[framePtr+i] = commentList[firstMove+i];
18220             commentList[firstMove+i] = NULL;
18221         }
18222
18223         storedGames++;
18224         forwardMostMove = firstMove; // truncate game so we can start variation
18225 }
18226
18227 void
18228 PushTail (int firstMove, int lastMove)
18229 {
18230         if(appData.icsActive) { // only in local mode
18231                 forwardMostMove = currentMove; // mimic old ICS behavior
18232                 return;
18233         }
18234         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18235
18236         PushInner(firstMove, lastMove);
18237         if(storedGames == 1) GreyRevert(FALSE);
18238         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18239 }
18240
18241 void
18242 PopInner (Boolean annotate)
18243 {
18244         int i, j, nrMoves;
18245         char buf[8000], moveBuf[20];
18246
18247         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18248         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18249         nrMoves = savedLast[storedGames] - currentMove;
18250         if(annotate) {
18251                 int cnt = 10;
18252                 if(!WhiteOnMove(currentMove))
18253                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18254                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18255                 for(i=currentMove; i<forwardMostMove; i++) {
18256                         if(WhiteOnMove(i))
18257                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18258                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18259                         strcat(buf, moveBuf);
18260                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18261                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18262                 }
18263                 strcat(buf, ")");
18264         }
18265         for(i=1; i<=nrMoves; i++) { // copy last variation back
18266             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18267             for(j=0; j<MOVE_LEN; j++)
18268                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18269             for(j=0; j<2*MOVE_LEN; j++)
18270                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18271             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18272             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18273             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18274             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18275             commentList[currentMove+i] = commentList[framePtr+i];
18276             commentList[framePtr+i] = NULL;
18277         }
18278         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18279         framePtr = savedFramePtr[storedGames];
18280         gameInfo.result = savedResult[storedGames];
18281         if(gameInfo.resultDetails != NULL) {
18282             free(gameInfo.resultDetails);
18283       }
18284         gameInfo.resultDetails = savedDetails[storedGames];
18285         forwardMostMove = currentMove + nrMoves;
18286 }
18287
18288 Boolean
18289 PopTail (Boolean annotate)
18290 {
18291         if(appData.icsActive) return FALSE; // only in local mode
18292         if(!storedGames) return FALSE; // sanity
18293         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18294
18295         PopInner(annotate);
18296         if(currentMove < forwardMostMove) ForwardEvent(); else
18297         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18298
18299         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18300         return TRUE;
18301 }
18302
18303 void
18304 CleanupTail ()
18305 {       // remove all shelved variations
18306         int i;
18307         for(i=0; i<storedGames; i++) {
18308             if(savedDetails[i])
18309                 free(savedDetails[i]);
18310             savedDetails[i] = NULL;
18311         }
18312         for(i=framePtr; i<MAX_MOVES; i++) {
18313                 if(commentList[i]) free(commentList[i]);
18314                 commentList[i] = NULL;
18315         }
18316         framePtr = MAX_MOVES-1;
18317         storedGames = 0;
18318 }
18319
18320 void
18321 LoadVariation (int index, char *text)
18322 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18323         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18324         int level = 0, move;
18325
18326         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18327         // first find outermost bracketing variation
18328         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18329             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18330                 if(*p == '{') wait = '}'; else
18331                 if(*p == '[') wait = ']'; else
18332                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18333                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18334             }
18335             if(*p == wait) wait = NULLCHAR; // closing ]} found
18336             p++;
18337         }
18338         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18339         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18340         end[1] = NULLCHAR; // clip off comment beyond variation
18341         ToNrEvent(currentMove-1);
18342         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18343         // kludge: use ParsePV() to append variation to game
18344         move = currentMove;
18345         ParsePV(start, TRUE, TRUE);
18346         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18347         ClearPremoveHighlights();
18348         CommentPopDown();
18349         ToNrEvent(currentMove+1);
18350 }
18351
18352 void
18353 LoadTheme ()
18354 {
18355     char *p, *q, buf[MSG_SIZ];
18356     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18357         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18358         ParseArgsFromString(buf);
18359         ActivateTheme(TRUE); // also redo colors
18360         return;
18361     }
18362     p = nickName;
18363     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18364     {
18365         int len;
18366         q = appData.themeNames;
18367         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18368       if(appData.useBitmaps) {
18369         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18370                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18371                 appData.liteBackTextureMode,
18372                 appData.darkBackTextureMode );
18373       } else {
18374         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18375                 Col2Text(2),   // lightSquareColor
18376                 Col2Text(3) ); // darkSquareColor
18377       }
18378       if(appData.useBorder) {
18379         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18380                 appData.border);
18381       } else {
18382         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18383       }
18384       if(appData.useFont) {
18385         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18386                 appData.renderPiecesWithFont,
18387                 appData.fontToPieceTable,
18388                 Col2Text(9),    // appData.fontBackColorWhite
18389                 Col2Text(10) ); // appData.fontForeColorBlack
18390       } else {
18391         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18392                 appData.pieceDirectory);
18393         if(!appData.pieceDirectory[0])
18394           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18395                 Col2Text(0),   // whitePieceColor
18396                 Col2Text(1) ); // blackPieceColor
18397       }
18398       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18399                 Col2Text(4),   // highlightSquareColor
18400                 Col2Text(5) ); // premoveHighlightColor
18401         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18402         if(insert != q) insert[-1] = NULLCHAR;
18403         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18404         if(q)   free(q);
18405     }
18406     ActivateTheme(FALSE);
18407 }