f05359c9e2896e21f6833449c1de04ba739f5e8c
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298
299 /* States for ics_getting_history */
300 #define H_FALSE 0
301 #define H_REQUESTED 1
302 #define H_GOT_REQ_HEADER 2
303 #define H_GOT_UNREQ_HEADER 3
304 #define H_GETTING_MOVES 4
305 #define H_GOT_UNWANTED_HEADER 5
306
307 /* whosays values for GameEnds */
308 #define GE_ICS 0
309 #define GE_ENGINE 1
310 #define GE_PLAYER 2
311 #define GE_FILE 3
312 #define GE_XBOARD 4
313 #define GE_ENGINE1 5
314 #define GE_ENGINE2 6
315
316 /* Maximum number of games in a cmail message */
317 #define CMAIL_MAX_GAMES 20
318
319 /* Different types of move when calling RegisterMove */
320 #define CMAIL_MOVE   0
321 #define CMAIL_RESIGN 1
322 #define CMAIL_DRAW   2
323 #define CMAIL_ACCEPT 3
324
325 /* Different types of result to remember for each game */
326 #define CMAIL_NOT_RESULT 0
327 #define CMAIL_OLD_RESULT 1
328 #define CMAIL_NEW_RESULT 2
329
330 /* Telnet protocol constants */
331 #define TN_WILL 0373
332 #define TN_WONT 0374
333 #define TN_DO   0375
334 #define TN_DONT 0376
335 #define TN_IAC  0377
336 #define TN_ECHO 0001
337 #define TN_SGA  0003
338 #define TN_PORT 23
339
340 char*
341 safeStrCpy (char *dst, const char *src, size_t count)
342 { // [HGM] made safe
343   int i;
344   assert( dst != NULL );
345   assert( src != NULL );
346   assert( count > 0 );
347
348   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
349   if(  i == count && dst[count-1] != NULLCHAR)
350     {
351       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
352       if(appData.debugMode)
353         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
354     }
355
356   return dst;
357 }
358
359 /* Some compiler can't cast u64 to double
360  * This function do the job for us:
361
362  * We use the highest bit for cast, this only
363  * works if the highest bit is not
364  * in use (This should not happen)
365  *
366  * We used this for all compiler
367  */
368 double
369 u64ToDouble (u64 value)
370 {
371   double r;
372   u64 tmp = value & u64Const(0x7fffffffffffffff);
373   r = (double)(s64)tmp;
374   if (value & u64Const(0x8000000000000000))
375        r +=  9.2233720368547758080e18; /* 2^63 */
376  return r;
377 }
378
379 /* Fake up flags for now, as we aren't keeping track of castling
380    availability yet. [HGM] Change of logic: the flag now only
381    indicates the type of castlings allowed by the rule of the game.
382    The actual rights themselves are maintained in the array
383    castlingRights, as part of the game history, and are not probed
384    by this function.
385  */
386 int
387 PosFlags (index)
388 {
389   int flags = F_ALL_CASTLE_OK;
390   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
391   switch (gameInfo.variant) {
392   case VariantSuicide:
393     flags &= ~F_ALL_CASTLE_OK;
394   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
395     flags |= F_IGNORE_CHECK;
396   case VariantLosers:
397     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
398     break;
399   case VariantAtomic:
400     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
401     break;
402   case VariantKriegspiel:
403     flags |= F_KRIEGSPIEL_CAPTURE;
404     break;
405   case VariantCapaRandom:
406   case VariantFischeRandom:
407     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
408   case VariantNoCastle:
409   case VariantShatranj:
410   case VariantCourier:
411   case VariantMakruk:
412   case VariantASEAN:
413   case VariantGrand:
414     flags &= ~F_ALL_CASTLE_OK;
415     break;
416   case VariantChu:
417   case VariantChuChess:
418   case VariantLion:
419     flags |= F_NULL_MOVE;
420     break;
421   default:
422     break;
423   }
424   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
425   return flags;
426 }
427
428 FILE *gameFileFP, *debugFP, *serverFP;
429 char *currentDebugFile; // [HGM] debug split: to remember name
430
431 /*
432     [AS] Note: sometimes, the sscanf() function is used to parse the input
433     into a fixed-size buffer. Because of this, we must be prepared to
434     receive strings as long as the size of the input buffer, which is currently
435     set to 4K for Windows and 8K for the rest.
436     So, we must either allocate sufficiently large buffers here, or
437     reduce the size of the input buffer in the input reading part.
438 */
439
440 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
441 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
442 char thinkOutput1[MSG_SIZ*10];
443
444 ChessProgramState first, second, pairing;
445
446 /* premove variables */
447 int premoveToX = 0;
448 int premoveToY = 0;
449 int premoveFromX = 0;
450 int premoveFromY = 0;
451 int premovePromoChar = 0;
452 int gotPremove = 0;
453 Boolean alarmSounded;
454 /* end premove variables */
455
456 char *ics_prefix = "$";
457 enum ICS_TYPE ics_type = ICS_GENERIC;
458
459 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
460 int pauseExamForwardMostMove = 0;
461 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
462 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
463 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
464 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
465 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
466 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
467 int whiteFlag = FALSE, blackFlag = FALSE;
468 int userOfferedDraw = FALSE;
469 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
470 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
471 int cmailMoveType[CMAIL_MAX_GAMES];
472 long ics_clock_paused = 0;
473 ProcRef icsPR = NoProc, cmailPR = NoProc;
474 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
475 GameMode gameMode = BeginningOfGame;
476 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
477 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
478 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
479 int hiddenThinkOutputState = 0; /* [AS] */
480 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
481 int adjudicateLossPlies = 6;
482 char white_holding[64], black_holding[64];
483 TimeMark lastNodeCountTime;
484 long lastNodeCount=0;
485 int shiftKey, controlKey; // [HGM] set by mouse handler
486
487 int have_sent_ICS_logon = 0;
488 int movesPerSession;
489 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
490 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
491 Boolean adjustedClock;
492 long timeControl_2; /* [AS] Allow separate time controls */
493 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
494 long timeRemaining[2][MAX_MOVES];
495 int matchGame = 0, nextGame = 0, roundNr = 0;
496 Boolean waitingForGame = FALSE, startingEngine = FALSE;
497 TimeMark programStartTime, pauseStart;
498 char ics_handle[MSG_SIZ];
499 int have_set_title = 0;
500
501 /* animateTraining preserves the state of appData.animate
502  * when Training mode is activated. This allows the
503  * response to be animated when appData.animate == TRUE and
504  * appData.animateDragging == TRUE.
505  */
506 Boolean animateTraining;
507
508 GameInfo gameInfo;
509
510 AppData appData;
511
512 Board boards[MAX_MOVES];
513 /* [HGM] Following 7 needed for accurate legality tests: */
514 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
515 signed char  initialRights[BOARD_FILES];
516 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
517 int   initialRulePlies, FENrulePlies;
518 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
519 int loadFlag = 0;
520 Boolean shuffleOpenings;
521 int mute; // mute all sounds
522
523 // [HGM] vari: next 12 to save and restore variations
524 #define MAX_VARIATIONS 10
525 int framePtr = MAX_MOVES-1; // points to free stack entry
526 int storedGames = 0;
527 int savedFirst[MAX_VARIATIONS];
528 int savedLast[MAX_VARIATIONS];
529 int savedFramePtr[MAX_VARIATIONS];
530 char *savedDetails[MAX_VARIATIONS];
531 ChessMove savedResult[MAX_VARIATIONS];
532
533 void PushTail P((int firstMove, int lastMove));
534 Boolean PopTail P((Boolean annotate));
535 void PushInner P((int firstMove, int lastMove));
536 void PopInner P((Boolean annotate));
537 void CleanupTail P((void));
538
539 ChessSquare  FIDEArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
543         BlackKing, BlackBishop, BlackKnight, BlackRook }
544 };
545
546 ChessSquare twoKingsArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
548         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
550         BlackKing, BlackKing, BlackKnight, BlackRook }
551 };
552
553 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
554     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
555         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
556     { BlackRook, BlackMan, BlackBishop, BlackQueen,
557         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
558 };
559
560 ChessSquare SpartanArray[2][BOARD_FILES] = {
561     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
562         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
563     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
564         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
565 };
566
567 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
568     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
569         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
570     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
571         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
572 };
573
574 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
575     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
576         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
577     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
578         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
579 };
580
581 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
582     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
583         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
584     { BlackRook, BlackKnight, BlackMan, BlackFerz,
585         BlackKing, BlackMan, BlackKnight, BlackRook }
586 };
587
588 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
589     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
590         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
591     { BlackRook, BlackKnight, BlackMan, BlackFerz,
592         BlackKing, BlackMan, BlackKnight, BlackRook }
593 };
594
595 ChessSquare  lionArray[2][BOARD_FILES] = {
596     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
597         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
598     { BlackRook, BlackLion, BlackBishop, BlackQueen,
599         BlackKing, BlackBishop, BlackKnight, BlackRook }
600 };
601
602
603 #if (BOARD_FILES>=10)
604 ChessSquare ShogiArray[2][BOARD_FILES] = {
605     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
606         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
607     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
608         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
609 };
610
611 ChessSquare XiangqiArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
613         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
615         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
616 };
617
618 ChessSquare CapablancaArray[2][BOARD_FILES] = {
619     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
620         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
621     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
622         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
623 };
624
625 ChessSquare GreatArray[2][BOARD_FILES] = {
626     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
627         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
628     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
629         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
630 };
631
632 ChessSquare JanusArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
634         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
635     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
636         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
637 };
638
639 ChessSquare GrandArray[2][BOARD_FILES] = {
640     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
641         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
642     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
643         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
644 };
645
646 ChessSquare ChuChessArray[2][BOARD_FILES] = {
647     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
648         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
649     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
650         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
651 };
652
653 #ifdef GOTHIC
654 ChessSquare GothicArray[2][BOARD_FILES] = {
655     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
656         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
657     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
658         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
659 };
660 #else // !GOTHIC
661 #define GothicArray CapablancaArray
662 #endif // !GOTHIC
663
664 #ifdef FALCON
665 ChessSquare FalconArray[2][BOARD_FILES] = {
666     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
667         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
668     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
669         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
670 };
671 #else // !FALCON
672 #define FalconArray CapablancaArray
673 #endif // !FALCON
674
675 #else // !(BOARD_FILES>=10)
676 #define XiangqiPosition FIDEArray
677 #define CapablancaArray FIDEArray
678 #define GothicArray FIDEArray
679 #define GreatArray FIDEArray
680 #endif // !(BOARD_FILES>=10)
681
682 #if (BOARD_FILES>=12)
683 ChessSquare CourierArray[2][BOARD_FILES] = {
684     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
685         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
686     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
687         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
688 };
689 ChessSquare ChuArray[6][BOARD_FILES] = {
690     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
691       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
692     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
693       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
694     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
695       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
696     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
697       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
698     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
699       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
700     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
701       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
702 };
703 #else // !(BOARD_FILES>=12)
704 #define CourierArray CapablancaArray
705 #define ChuArray CapablancaArray
706 #endif // !(BOARD_FILES>=12)
707
708
709 Board initialPosition;
710
711
712 /* Convert str to a rating. Checks for special cases of "----",
713
714    "++++", etc. Also strips ()'s */
715 int
716 string_to_rating (char *str)
717 {
718   while(*str && !isdigit(*str)) ++str;
719   if (!*str)
720     return 0;   /* One of the special "no rating" cases */
721   else
722     return atoi(str);
723 }
724
725 void
726 ClearProgramStats ()
727 {
728     /* Init programStats */
729     programStats.movelist[0] = 0;
730     programStats.depth = 0;
731     programStats.nr_moves = 0;
732     programStats.moves_left = 0;
733     programStats.nodes = 0;
734     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
735     programStats.score = 0;
736     programStats.got_only_move = 0;
737     programStats.got_fail = 0;
738     programStats.line_is_book = 0;
739 }
740
741 void
742 CommonEngineInit ()
743 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
744     if (appData.firstPlaysBlack) {
745         first.twoMachinesColor = "black\n";
746         second.twoMachinesColor = "white\n";
747     } else {
748         first.twoMachinesColor = "white\n";
749         second.twoMachinesColor = "black\n";
750     }
751
752     first.other = &second;
753     second.other = &first;
754
755     { float norm = 1;
756         if(appData.timeOddsMode) {
757             norm = appData.timeOdds[0];
758             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
759         }
760         first.timeOdds  = appData.timeOdds[0]/norm;
761         second.timeOdds = appData.timeOdds[1]/norm;
762     }
763
764     if(programVersion) free(programVersion);
765     if (appData.noChessProgram) {
766         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
767         sprintf(programVersion, "%s", PACKAGE_STRING);
768     } else {
769       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
770       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
771       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
772     }
773 }
774
775 void
776 UnloadEngine (ChessProgramState *cps)
777 {
778         /* Kill off first chess program */
779         if (cps->isr != NULL)
780           RemoveInputSource(cps->isr);
781         cps->isr = NULL;
782
783         if (cps->pr != NoProc) {
784             ExitAnalyzeMode();
785             DoSleep( appData.delayBeforeQuit );
786             SendToProgram("quit\n", cps);
787             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
788         }
789         cps->pr = NoProc;
790         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
791 }
792
793 void
794 ClearOptions (ChessProgramState *cps)
795 {
796     int i;
797     cps->nrOptions = cps->comboCnt = 0;
798     for(i=0; i<MAX_OPTIONS; i++) {
799         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
800         cps->option[i].textValue = 0;
801     }
802 }
803
804 char *engineNames[] = {
805   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
806      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
807 N_("first"),
808   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("second")
811 };
812
813 void
814 InitEngine (ChessProgramState *cps, int n)
815 {   // [HGM] all engine initialiation put in a function that does one engine
816
817     ClearOptions(cps);
818
819     cps->which = engineNames[n];
820     cps->maybeThinking = FALSE;
821     cps->pr = NoProc;
822     cps->isr = NULL;
823     cps->sendTime = 2;
824     cps->sendDrawOffers = 1;
825
826     cps->program = appData.chessProgram[n];
827     cps->host = appData.host[n];
828     cps->dir = appData.directory[n];
829     cps->initString = appData.engInitString[n];
830     cps->computerString = appData.computerString[n];
831     cps->useSigint  = TRUE;
832     cps->useSigterm = TRUE;
833     cps->reuse = appData.reuse[n];
834     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
835     cps->useSetboard = FALSE;
836     cps->useSAN = FALSE;
837     cps->usePing = FALSE;
838     cps->lastPing = 0;
839     cps->lastPong = 0;
840     cps->usePlayother = FALSE;
841     cps->useColors = TRUE;
842     cps->useUsermove = FALSE;
843     cps->sendICS = FALSE;
844     cps->sendName = appData.icsActive;
845     cps->sdKludge = FALSE;
846     cps->stKludge = FALSE;
847     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
848     TidyProgramName(cps->program, cps->host, cps->tidy);
849     cps->matchWins = 0;
850     ASSIGN(cps->variants, appData.variant);
851     cps->analysisSupport = 2; /* detect */
852     cps->analyzing = FALSE;
853     cps->initDone = FALSE;
854     cps->reload = FALSE;
855     cps->pseudo = appData.pseudo[n];
856
857     /* New features added by Tord: */
858     cps->useFEN960 = FALSE;
859     cps->useOOCastle = TRUE;
860     /* End of new features added by Tord. */
861     cps->fenOverride  = appData.fenOverride[n];
862
863     /* [HGM] time odds: set factor for each machine */
864     cps->timeOdds  = appData.timeOdds[n];
865
866     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
867     cps->accumulateTC = appData.accumulateTC[n];
868     cps->maxNrOfSessions = 1;
869
870     /* [HGM] debug */
871     cps->debug = FALSE;
872
873     cps->drawDepth = appData.drawDepth[n];
874     cps->supportsNPS = UNKNOWN;
875     cps->memSize = FALSE;
876     cps->maxCores = FALSE;
877     ASSIGN(cps->egtFormats, "");
878
879     /* [HGM] options */
880     cps->optionSettings  = appData.engOptions[n];
881
882     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
883     cps->isUCI = appData.isUCI[n]; /* [AS] */
884     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
885     cps->highlight = 0;
886
887     if (appData.protocolVersion[n] > PROTOVER
888         || appData.protocolVersion[n] < 1)
889       {
890         char buf[MSG_SIZ];
891         int len;
892
893         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
894                        appData.protocolVersion[n]);
895         if( (len >= MSG_SIZ) && appData.debugMode )
896           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
897
898         DisplayFatalError(buf, 0, 2);
899       }
900     else
901       {
902         cps->protocolVersion = appData.protocolVersion[n];
903       }
904
905     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
906     ParseFeatures(appData.featureDefaults, cps);
907 }
908
909 ChessProgramState *savCps;
910
911 GameMode oldMode;
912
913 void
914 LoadEngine ()
915 {
916     int i;
917     if(WaitForEngine(savCps, LoadEngine)) return;
918     CommonEngineInit(); // recalculate time odds
919     if(gameInfo.variant != StringToVariant(appData.variant)) {
920         // we changed variant when loading the engine; this forces us to reset
921         Reset(TRUE, savCps != &first);
922         oldMode = BeginningOfGame; // to prevent restoring old mode
923     }
924     InitChessProgram(savCps, FALSE);
925     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
926     DisplayMessage("", "");
927     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
928     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
929     ThawUI();
930     SetGNUMode();
931     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
932 }
933
934 void
935 ReplaceEngine (ChessProgramState *cps, int n)
936 {
937     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
938     keepInfo = 1;
939     if(oldMode != BeginningOfGame) EditGameEvent();
940     keepInfo = 0;
941     UnloadEngine(cps);
942     appData.noChessProgram = FALSE;
943     appData.clockMode = TRUE;
944     InitEngine(cps, n);
945     UpdateLogos(TRUE);
946     if(n) return; // only startup first engine immediately; second can wait
947     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
948     LoadEngine();
949 }
950
951 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
952 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
953
954 static char resetOptions[] =
955         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
956         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
957         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
958         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
959
960 void
961 FloatToFront(char **list, char *engineLine)
962 {
963     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
964     int i=0;
965     if(appData.recentEngines <= 0) return;
966     TidyProgramName(engineLine, "localhost", tidy+1);
967     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
968     strncpy(buf+1, *list, MSG_SIZ-50);
969     if(p = strstr(buf, tidy)) { // tidy name appears in list
970         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
971         while(*p++ = *++q); // squeeze out
972     }
973     strcat(tidy, buf+1); // put list behind tidy name
974     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
975     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
976     ASSIGN(*list, tidy+1);
977 }
978
979 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
980
981 void
982 Load (ChessProgramState *cps, int i)
983 {
984     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
985     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
986         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
987         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
988         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
989         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
990         appData.firstProtocolVersion = PROTOVER;
991         ParseArgsFromString(buf);
992         SwapEngines(i);
993         ReplaceEngine(cps, i);
994         FloatToFront(&appData.recentEngineList, engineLine);
995         return;
996     }
997     p = engineName;
998     while(q = strchr(p, SLASH)) p = q+1;
999     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1000     if(engineDir[0] != NULLCHAR) {
1001         ASSIGN(appData.directory[i], engineDir); p = engineName;
1002     } else if(p != engineName) { // derive directory from engine path, when not given
1003         p[-1] = 0;
1004         ASSIGN(appData.directory[i], engineName);
1005         p[-1] = SLASH;
1006         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1007     } else { ASSIGN(appData.directory[i], "."); }
1008     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1009     if(params[0]) {
1010         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1011         snprintf(command, MSG_SIZ, "%s %s", p, params);
1012         p = command;
1013     }
1014     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1015     ASSIGN(appData.chessProgram[i], p);
1016     appData.isUCI[i] = isUCI;
1017     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1018     appData.hasOwnBookUCI[i] = hasBook;
1019     if(!nickName[0]) useNick = FALSE;
1020     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1021     if(addToList) {
1022         int len;
1023         char quote;
1024         q = firstChessProgramNames;
1025         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1026         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1027         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1028                         quote, p, quote, appData.directory[i],
1029                         useNick ? " -fn \"" : "",
1030                         useNick ? nickName : "",
1031                         useNick ? "\"" : "",
1032                         v1 ? " -firstProtocolVersion 1" : "",
1033                         hasBook ? "" : " -fNoOwnBookUCI",
1034                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1035                         storeVariant ? " -variant " : "",
1036                         storeVariant ? VariantName(gameInfo.variant) : "");
1037         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1038         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1039         if(insert != q) insert[-1] = NULLCHAR;
1040         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1041         if(q)   free(q);
1042         FloatToFront(&appData.recentEngineList, buf);
1043     }
1044     ReplaceEngine(cps, i);
1045 }
1046
1047 void
1048 InitTimeControls ()
1049 {
1050     int matched, min, sec;
1051     /*
1052      * Parse timeControl resource
1053      */
1054     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1055                           appData.movesPerSession)) {
1056         char buf[MSG_SIZ];
1057         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1058         DisplayFatalError(buf, 0, 2);
1059     }
1060
1061     /*
1062      * Parse searchTime resource
1063      */
1064     if (*appData.searchTime != NULLCHAR) {
1065         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1066         if (matched == 1) {
1067             searchTime = min * 60;
1068         } else if (matched == 2) {
1069             searchTime = min * 60 + sec;
1070         } else {
1071             char buf[MSG_SIZ];
1072             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1073             DisplayFatalError(buf, 0, 2);
1074         }
1075     }
1076 }
1077
1078 void
1079 InitBackEnd1 ()
1080 {
1081
1082     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1083     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1084
1085     GetTimeMark(&programStartTime);
1086     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1087     appData.seedBase = random() + (random()<<15);
1088     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1089
1090     ClearProgramStats();
1091     programStats.ok_to_send = 1;
1092     programStats.seen_stat = 0;
1093
1094     /*
1095      * Initialize game list
1096      */
1097     ListNew(&gameList);
1098
1099
1100     /*
1101      * Internet chess server status
1102      */
1103     if (appData.icsActive) {
1104         appData.matchMode = FALSE;
1105         appData.matchGames = 0;
1106 #if ZIPPY
1107         appData.noChessProgram = !appData.zippyPlay;
1108 #else
1109         appData.zippyPlay = FALSE;
1110         appData.zippyTalk = FALSE;
1111         appData.noChessProgram = TRUE;
1112 #endif
1113         if (*appData.icsHelper != NULLCHAR) {
1114             appData.useTelnet = TRUE;
1115             appData.telnetProgram = appData.icsHelper;
1116         }
1117     } else {
1118         appData.zippyTalk = appData.zippyPlay = FALSE;
1119     }
1120
1121     /* [AS] Initialize pv info list [HGM] and game state */
1122     {
1123         int i, j;
1124
1125         for( i=0; i<=framePtr; i++ ) {
1126             pvInfoList[i].depth = -1;
1127             boards[i][EP_STATUS] = EP_NONE;
1128             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1129         }
1130     }
1131
1132     InitTimeControls();
1133
1134     /* [AS] Adjudication threshold */
1135     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1136
1137     InitEngine(&first, 0);
1138     InitEngine(&second, 1);
1139     CommonEngineInit();
1140
1141     pairing.which = "pairing"; // pairing engine
1142     pairing.pr = NoProc;
1143     pairing.isr = NULL;
1144     pairing.program = appData.pairingEngine;
1145     pairing.host = "localhost";
1146     pairing.dir = ".";
1147
1148     if (appData.icsActive) {
1149         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1150     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1151         appData.clockMode = FALSE;
1152         first.sendTime = second.sendTime = 0;
1153     }
1154
1155 #if ZIPPY
1156     /* Override some settings from environment variables, for backward
1157        compatibility.  Unfortunately it's not feasible to have the env
1158        vars just set defaults, at least in xboard.  Ugh.
1159     */
1160     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1161       ZippyInit();
1162     }
1163 #endif
1164
1165     if (!appData.icsActive) {
1166       char buf[MSG_SIZ];
1167       int len;
1168
1169       /* Check for variants that are supported only in ICS mode,
1170          or not at all.  Some that are accepted here nevertheless
1171          have bugs; see comments below.
1172       */
1173       VariantClass variant = StringToVariant(appData.variant);
1174       switch (variant) {
1175       case VariantBughouse:     /* need four players and two boards */
1176       case VariantKriegspiel:   /* need to hide pieces and move details */
1177         /* case VariantFischeRandom: (Fabien: moved below) */
1178         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1179         if( (len >= MSG_SIZ) && appData.debugMode )
1180           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1181
1182         DisplayFatalError(buf, 0, 2);
1183         return;
1184
1185       case VariantUnknown:
1186       case VariantLoadable:
1187       case Variant29:
1188       case Variant30:
1189       case Variant31:
1190       case Variant32:
1191       case Variant33:
1192       case Variant34:
1193       case Variant35:
1194       case Variant36:
1195       default:
1196         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1197         if( (len >= MSG_SIZ) && appData.debugMode )
1198           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1199
1200         DisplayFatalError(buf, 0, 2);
1201         return;
1202
1203       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1204       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1205       case VariantGothic:     /* [HGM] should work */
1206       case VariantCapablanca: /* [HGM] should work */
1207       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1208       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1209       case VariantChu:        /* [HGM] experimental */
1210       case VariantKnightmate: /* [HGM] should work */
1211       case VariantCylinder:   /* [HGM] untested */
1212       case VariantFalcon:     /* [HGM] untested */
1213       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1214                                  offboard interposition not understood */
1215       case VariantNormal:     /* definitely works! */
1216       case VariantWildCastle: /* pieces not automatically shuffled */
1217       case VariantNoCastle:   /* pieces not automatically shuffled */
1218       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1219       case VariantLosers:     /* should work except for win condition,
1220                                  and doesn't know captures are mandatory */
1221       case VariantSuicide:    /* should work except for win condition,
1222                                  and doesn't know captures are mandatory */
1223       case VariantGiveaway:   /* should work except for win condition,
1224                                  and doesn't know captures are mandatory */
1225       case VariantTwoKings:   /* should work */
1226       case VariantAtomic:     /* should work except for win condition */
1227       case Variant3Check:     /* should work except for win condition */
1228       case VariantShatranj:   /* should work except for all win conditions */
1229       case VariantMakruk:     /* should work except for draw countdown */
1230       case VariantASEAN :     /* should work except for draw countdown */
1231       case VariantBerolina:   /* might work if TestLegality is off */
1232       case VariantCapaRandom: /* should work */
1233       case VariantJanus:      /* should work */
1234       case VariantSuper:      /* experimental */
1235       case VariantGreat:      /* experimental, requires legality testing to be off */
1236       case VariantSChess:     /* S-Chess, should work */
1237       case VariantGrand:      /* should work */
1238       case VariantSpartan:    /* should work */
1239       case VariantLion:       /* should work */
1240       case VariantChuChess:   /* should work */
1241         break;
1242       }
1243     }
1244
1245 }
1246
1247 int
1248 NextIntegerFromString (char ** str, long * value)
1249 {
1250     int result = -1;
1251     char * s = *str;
1252
1253     while( *s == ' ' || *s == '\t' ) {
1254         s++;
1255     }
1256
1257     *value = 0;
1258
1259     if( *s >= '0' && *s <= '9' ) {
1260         while( *s >= '0' && *s <= '9' ) {
1261             *value = *value * 10 + (*s - '0');
1262             s++;
1263         }
1264
1265         result = 0;
1266     }
1267
1268     *str = s;
1269
1270     return result;
1271 }
1272
1273 int
1274 NextTimeControlFromString (char ** str, long * value)
1275 {
1276     long temp;
1277     int result = NextIntegerFromString( str, &temp );
1278
1279     if( result == 0 ) {
1280         *value = temp * 60; /* Minutes */
1281         if( **str == ':' ) {
1282             (*str)++;
1283             result = NextIntegerFromString( str, &temp );
1284             *value += temp; /* Seconds */
1285         }
1286     }
1287
1288     return result;
1289 }
1290
1291 int
1292 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1293 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1294     int result = -1, type = 0; long temp, temp2;
1295
1296     if(**str != ':') return -1; // old params remain in force!
1297     (*str)++;
1298     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1299     if( NextIntegerFromString( str, &temp ) ) return -1;
1300     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1301
1302     if(**str != '/') {
1303         /* time only: incremental or sudden-death time control */
1304         if(**str == '+') { /* increment follows; read it */
1305             (*str)++;
1306             if(**str == '!') type = *(*str)++; // Bronstein TC
1307             if(result = NextIntegerFromString( str, &temp2)) return -1;
1308             *inc = temp2 * 1000;
1309             if(**str == '.') { // read fraction of increment
1310                 char *start = ++(*str);
1311                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1312                 temp2 *= 1000;
1313                 while(start++ < *str) temp2 /= 10;
1314                 *inc += temp2;
1315             }
1316         } else *inc = 0;
1317         *moves = 0; *tc = temp * 1000; *incType = type;
1318         return 0;
1319     }
1320
1321     (*str)++; /* classical time control */
1322     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1323
1324     if(result == 0) {
1325         *moves = temp;
1326         *tc    = temp2 * 1000;
1327         *inc   = 0;
1328         *incType = type;
1329     }
1330     return result;
1331 }
1332
1333 int
1334 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1335 {   /* [HGM] get time to add from the multi-session time-control string */
1336     int incType, moves=1; /* kludge to force reading of first session */
1337     long time, increment;
1338     char *s = tcString;
1339
1340     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1341     do {
1342         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1343         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1344         if(movenr == -1) return time;    /* last move before new session     */
1345         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1346         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1347         if(!moves) return increment;     /* current session is incremental   */
1348         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1349     } while(movenr >= -1);               /* try again for next session       */
1350
1351     return 0; // no new time quota on this move
1352 }
1353
1354 int
1355 ParseTimeControl (char *tc, float ti, int mps)
1356 {
1357   long tc1;
1358   long tc2;
1359   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1360   int min, sec=0;
1361
1362   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1363   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1364       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1365   if(ti > 0) {
1366
1367     if(mps)
1368       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1369     else
1370       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1371   } else {
1372     if(mps)
1373       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1374     else
1375       snprintf(buf, MSG_SIZ, ":%s", mytc);
1376   }
1377   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1378
1379   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1380     return FALSE;
1381   }
1382
1383   if( *tc == '/' ) {
1384     /* Parse second time control */
1385     tc++;
1386
1387     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1388       return FALSE;
1389     }
1390
1391     if( tc2 == 0 ) {
1392       return FALSE;
1393     }
1394
1395     timeControl_2 = tc2 * 1000;
1396   }
1397   else {
1398     timeControl_2 = 0;
1399   }
1400
1401   if( tc1 == 0 ) {
1402     return FALSE;
1403   }
1404
1405   timeControl = tc1 * 1000;
1406
1407   if (ti >= 0) {
1408     timeIncrement = ti * 1000;  /* convert to ms */
1409     movesPerSession = 0;
1410   } else {
1411     timeIncrement = 0;
1412     movesPerSession = mps;
1413   }
1414   return TRUE;
1415 }
1416
1417 void
1418 InitBackEnd2 ()
1419 {
1420     if (appData.debugMode) {
1421 #    ifdef __GIT_VERSION
1422       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1423 #    else
1424       fprintf(debugFP, "Version: %s\n", programVersion);
1425 #    endif
1426     }
1427     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1428
1429     set_cont_sequence(appData.wrapContSeq);
1430     if (appData.matchGames > 0) {
1431         appData.matchMode = TRUE;
1432     } else if (appData.matchMode) {
1433         appData.matchGames = 1;
1434     }
1435     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1436         appData.matchGames = appData.sameColorGames;
1437     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1438         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1439         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1440     }
1441     Reset(TRUE, FALSE);
1442     if (appData.noChessProgram || first.protocolVersion == 1) {
1443       InitBackEnd3();
1444     } else {
1445       /* kludge: allow timeout for initial "feature" commands */
1446       FreezeUI();
1447       DisplayMessage("", _("Starting chess program"));
1448       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1449     }
1450 }
1451
1452 int
1453 CalculateIndex (int index, int gameNr)
1454 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1455     int res;
1456     if(index > 0) return index; // fixed nmber
1457     if(index == 0) return 1;
1458     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1459     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1460     return res;
1461 }
1462
1463 int
1464 LoadGameOrPosition (int gameNr)
1465 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1466     if (*appData.loadGameFile != NULLCHAR) {
1467         if (!LoadGameFromFile(appData.loadGameFile,
1468                 CalculateIndex(appData.loadGameIndex, gameNr),
1469                               appData.loadGameFile, FALSE)) {
1470             DisplayFatalError(_("Bad game file"), 0, 1);
1471             return 0;
1472         }
1473     } else if (*appData.loadPositionFile != NULLCHAR) {
1474         if (!LoadPositionFromFile(appData.loadPositionFile,
1475                 CalculateIndex(appData.loadPositionIndex, gameNr),
1476                                   appData.loadPositionFile)) {
1477             DisplayFatalError(_("Bad position file"), 0, 1);
1478             return 0;
1479         }
1480     }
1481     return 1;
1482 }
1483
1484 void
1485 ReserveGame (int gameNr, char resChar)
1486 {
1487     FILE *tf = fopen(appData.tourneyFile, "r+");
1488     char *p, *q, c, buf[MSG_SIZ];
1489     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1490     safeStrCpy(buf, lastMsg, MSG_SIZ);
1491     DisplayMessage(_("Pick new game"), "");
1492     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1493     ParseArgsFromFile(tf);
1494     p = q = appData.results;
1495     if(appData.debugMode) {
1496       char *r = appData.participants;
1497       fprintf(debugFP, "results = '%s'\n", p);
1498       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1499       fprintf(debugFP, "\n");
1500     }
1501     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1502     nextGame = q - p;
1503     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1504     safeStrCpy(q, p, strlen(p) + 2);
1505     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1506     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1507     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1508         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1509         q[nextGame] = '*';
1510     }
1511     fseek(tf, -(strlen(p)+4), SEEK_END);
1512     c = fgetc(tf);
1513     if(c != '"') // depending on DOS or Unix line endings we can be one off
1514          fseek(tf, -(strlen(p)+2), SEEK_END);
1515     else fseek(tf, -(strlen(p)+3), SEEK_END);
1516     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1517     DisplayMessage(buf, "");
1518     free(p); appData.results = q;
1519     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1520        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1521       int round = appData.defaultMatchGames * appData.tourneyType;
1522       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1523          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1524         UnloadEngine(&first);  // next game belongs to other pairing;
1525         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1526     }
1527     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1528 }
1529
1530 void
1531 MatchEvent (int mode)
1532 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1533         int dummy;
1534         if(matchMode) { // already in match mode: switch it off
1535             abortMatch = TRUE;
1536             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1537             return;
1538         }
1539 //      if(gameMode != BeginningOfGame) {
1540 //          DisplayError(_("You can only start a match from the initial position."), 0);
1541 //          return;
1542 //      }
1543         abortMatch = FALSE;
1544         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1545         /* Set up machine vs. machine match */
1546         nextGame = 0;
1547         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1548         if(appData.tourneyFile[0]) {
1549             ReserveGame(-1, 0);
1550             if(nextGame > appData.matchGames) {
1551                 char buf[MSG_SIZ];
1552                 if(strchr(appData.results, '*') == NULL) {
1553                     FILE *f;
1554                     appData.tourneyCycles++;
1555                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1556                         fclose(f);
1557                         NextTourneyGame(-1, &dummy);
1558                         ReserveGame(-1, 0);
1559                         if(nextGame <= appData.matchGames) {
1560                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1561                             matchMode = mode;
1562                             ScheduleDelayedEvent(NextMatchGame, 10000);
1563                             return;
1564                         }
1565                     }
1566                 }
1567                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1568                 DisplayError(buf, 0);
1569                 appData.tourneyFile[0] = 0;
1570                 return;
1571             }
1572         } else
1573         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1574             DisplayFatalError(_("Can't have a match with no chess programs"),
1575                               0, 2);
1576             return;
1577         }
1578         matchMode = mode;
1579         matchGame = roundNr = 1;
1580         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1581         NextMatchGame();
1582 }
1583
1584 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1585
1586 void
1587 InitBackEnd3 P((void))
1588 {
1589     GameMode initialMode;
1590     char buf[MSG_SIZ];
1591     int err, len;
1592
1593     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1594        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1595         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1596        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1597        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1598         char c, *q = first.variants, *p = strchr(q, ',');
1599         if(p) *p = NULLCHAR;
1600         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1601             int w, h, s;
1602             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1603                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1604             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1605             Reset(TRUE, FALSE);         // and re-initialize
1606         }
1607         if(p) *p = ',';
1608     }
1609
1610     InitChessProgram(&first, startedFromSetupPosition);
1611
1612     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1613         free(programVersion);
1614         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1615         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1616         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1617     }
1618
1619     if (appData.icsActive) {
1620 #ifdef WIN32
1621         /* [DM] Make a console window if needed [HGM] merged ifs */
1622         ConsoleCreate();
1623 #endif
1624         err = establish();
1625         if (err != 0)
1626           {
1627             if (*appData.icsCommPort != NULLCHAR)
1628               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1629                              appData.icsCommPort);
1630             else
1631               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1632                         appData.icsHost, appData.icsPort);
1633
1634             if( (len >= MSG_SIZ) && appData.debugMode )
1635               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1636
1637             DisplayFatalError(buf, err, 1);
1638             return;
1639         }
1640         SetICSMode();
1641         telnetISR =
1642           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1643         fromUserISR =
1644           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1645         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1646             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1647     } else if (appData.noChessProgram) {
1648         SetNCPMode();
1649     } else {
1650         SetGNUMode();
1651     }
1652
1653     if (*appData.cmailGameName != NULLCHAR) {
1654         SetCmailMode();
1655         OpenLoopback(&cmailPR);
1656         cmailISR =
1657           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1658     }
1659
1660     ThawUI();
1661     DisplayMessage("", "");
1662     if (StrCaseCmp(appData.initialMode, "") == 0) {
1663       initialMode = BeginningOfGame;
1664       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1665         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1666         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1667         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1668         ModeHighlight();
1669       }
1670     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1671       initialMode = TwoMachinesPlay;
1672     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1673       initialMode = AnalyzeFile;
1674     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1675       initialMode = AnalyzeMode;
1676     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1677       initialMode = MachinePlaysWhite;
1678     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1679       initialMode = MachinePlaysBlack;
1680     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1681       initialMode = EditGame;
1682     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1683       initialMode = EditPosition;
1684     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1685       initialMode = Training;
1686     } else {
1687       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1688       if( (len >= MSG_SIZ) && appData.debugMode )
1689         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1690
1691       DisplayFatalError(buf, 0, 2);
1692       return;
1693     }
1694
1695     if (appData.matchMode) {
1696         if(appData.tourneyFile[0]) { // start tourney from command line
1697             FILE *f;
1698             if(f = fopen(appData.tourneyFile, "r")) {
1699                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1700                 fclose(f);
1701                 appData.clockMode = TRUE;
1702                 SetGNUMode();
1703             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1704         }
1705         MatchEvent(TRUE);
1706     } else if (*appData.cmailGameName != NULLCHAR) {
1707         /* Set up cmail mode */
1708         ReloadCmailMsgEvent(TRUE);
1709     } else {
1710         /* Set up other modes */
1711         if (initialMode == AnalyzeFile) {
1712           if (*appData.loadGameFile == NULLCHAR) {
1713             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1714             return;
1715           }
1716         }
1717         if (*appData.loadGameFile != NULLCHAR) {
1718             (void) LoadGameFromFile(appData.loadGameFile,
1719                                     appData.loadGameIndex,
1720                                     appData.loadGameFile, TRUE);
1721         } else if (*appData.loadPositionFile != NULLCHAR) {
1722             (void) LoadPositionFromFile(appData.loadPositionFile,
1723                                         appData.loadPositionIndex,
1724                                         appData.loadPositionFile);
1725             /* [HGM] try to make self-starting even after FEN load */
1726             /* to allow automatic setup of fairy variants with wtm */
1727             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1728                 gameMode = BeginningOfGame;
1729                 setboardSpoiledMachineBlack = 1;
1730             }
1731             /* [HGM] loadPos: make that every new game uses the setup */
1732             /* from file as long as we do not switch variant          */
1733             if(!blackPlaysFirst) {
1734                 startedFromPositionFile = TRUE;
1735                 CopyBoard(filePosition, boards[0]);
1736             }
1737         }
1738         if (initialMode == AnalyzeMode) {
1739           if (appData.noChessProgram) {
1740             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1741             return;
1742           }
1743           if (appData.icsActive) {
1744             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1745             return;
1746           }
1747           AnalyzeModeEvent();
1748         } else if (initialMode == AnalyzeFile) {
1749           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1750           ShowThinkingEvent();
1751           AnalyzeFileEvent();
1752           AnalysisPeriodicEvent(1);
1753         } else if (initialMode == MachinePlaysWhite) {
1754           if (appData.noChessProgram) {
1755             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1756                               0, 2);
1757             return;
1758           }
1759           if (appData.icsActive) {
1760             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1761                               0, 2);
1762             return;
1763           }
1764           MachineWhiteEvent();
1765         } else if (initialMode == MachinePlaysBlack) {
1766           if (appData.noChessProgram) {
1767             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1768                               0, 2);
1769             return;
1770           }
1771           if (appData.icsActive) {
1772             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1773                               0, 2);
1774             return;
1775           }
1776           MachineBlackEvent();
1777         } else if (initialMode == TwoMachinesPlay) {
1778           if (appData.noChessProgram) {
1779             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1780                               0, 2);
1781             return;
1782           }
1783           if (appData.icsActive) {
1784             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1785                               0, 2);
1786             return;
1787           }
1788           TwoMachinesEvent();
1789         } else if (initialMode == EditGame) {
1790           EditGameEvent();
1791         } else if (initialMode == EditPosition) {
1792           EditPositionEvent();
1793         } else if (initialMode == Training) {
1794           if (*appData.loadGameFile == NULLCHAR) {
1795             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1796             return;
1797           }
1798           TrainingEvent();
1799         }
1800     }
1801 }
1802
1803 void
1804 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1805 {
1806     DisplayBook(current+1);
1807
1808     MoveHistorySet( movelist, first, last, current, pvInfoList );
1809
1810     EvalGraphSet( first, last, current, pvInfoList );
1811
1812     MakeEngineOutputTitle();
1813 }
1814
1815 /*
1816  * Establish will establish a contact to a remote host.port.
1817  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1818  *  used to talk to the host.
1819  * Returns 0 if okay, error code if not.
1820  */
1821 int
1822 establish ()
1823 {
1824     char buf[MSG_SIZ];
1825
1826     if (*appData.icsCommPort != NULLCHAR) {
1827         /* Talk to the host through a serial comm port */
1828         return OpenCommPort(appData.icsCommPort, &icsPR);
1829
1830     } else if (*appData.gateway != NULLCHAR) {
1831         if (*appData.remoteShell == NULLCHAR) {
1832             /* Use the rcmd protocol to run telnet program on a gateway host */
1833             snprintf(buf, sizeof(buf), "%s %s %s",
1834                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1835             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1836
1837         } else {
1838             /* Use the rsh program to run telnet program on a gateway host */
1839             if (*appData.remoteUser == NULLCHAR) {
1840                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1841                         appData.gateway, appData.telnetProgram,
1842                         appData.icsHost, appData.icsPort);
1843             } else {
1844                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1845                         appData.remoteShell, appData.gateway,
1846                         appData.remoteUser, appData.telnetProgram,
1847                         appData.icsHost, appData.icsPort);
1848             }
1849             return StartChildProcess(buf, "", &icsPR);
1850
1851         }
1852     } else if (appData.useTelnet) {
1853         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1854
1855     } else {
1856         /* TCP socket interface differs somewhat between
1857            Unix and NT; handle details in the front end.
1858            */
1859         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1860     }
1861 }
1862
1863 void
1864 EscapeExpand (char *p, char *q)
1865 {       // [HGM] initstring: routine to shape up string arguments
1866         while(*p++ = *q++) if(p[-1] == '\\')
1867             switch(*q++) {
1868                 case 'n': p[-1] = '\n'; break;
1869                 case 'r': p[-1] = '\r'; break;
1870                 case 't': p[-1] = '\t'; break;
1871                 case '\\': p[-1] = '\\'; break;
1872                 case 0: *p = 0; return;
1873                 default: p[-1] = q[-1]; break;
1874             }
1875 }
1876
1877 void
1878 show_bytes (FILE *fp, char *buf, int count)
1879 {
1880     while (count--) {
1881         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1882             fprintf(fp, "\\%03o", *buf & 0xff);
1883         } else {
1884             putc(*buf, fp);
1885         }
1886         buf++;
1887     }
1888     fflush(fp);
1889 }
1890
1891 /* Returns an errno value */
1892 int
1893 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1894 {
1895     char buf[8192], *p, *q, *buflim;
1896     int left, newcount, outcount;
1897
1898     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1899         *appData.gateway != NULLCHAR) {
1900         if (appData.debugMode) {
1901             fprintf(debugFP, ">ICS: ");
1902             show_bytes(debugFP, message, count);
1903             fprintf(debugFP, "\n");
1904         }
1905         return OutputToProcess(pr, message, count, outError);
1906     }
1907
1908     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1909     p = message;
1910     q = buf;
1911     left = count;
1912     newcount = 0;
1913     while (left) {
1914         if (q >= buflim) {
1915             if (appData.debugMode) {
1916                 fprintf(debugFP, ">ICS: ");
1917                 show_bytes(debugFP, buf, newcount);
1918                 fprintf(debugFP, "\n");
1919             }
1920             outcount = OutputToProcess(pr, buf, newcount, outError);
1921             if (outcount < newcount) return -1; /* to be sure */
1922             q = buf;
1923             newcount = 0;
1924         }
1925         if (*p == '\n') {
1926             *q++ = '\r';
1927             newcount++;
1928         } else if (((unsigned char) *p) == TN_IAC) {
1929             *q++ = (char) TN_IAC;
1930             newcount ++;
1931         }
1932         *q++ = *p++;
1933         newcount++;
1934         left--;
1935     }
1936     if (appData.debugMode) {
1937         fprintf(debugFP, ">ICS: ");
1938         show_bytes(debugFP, buf, newcount);
1939         fprintf(debugFP, "\n");
1940     }
1941     outcount = OutputToProcess(pr, buf, newcount, outError);
1942     if (outcount < newcount) return -1; /* to be sure */
1943     return count;
1944 }
1945
1946 void
1947 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1948 {
1949     int outError, outCount;
1950     static int gotEof = 0;
1951     static FILE *ini;
1952
1953     /* Pass data read from player on to ICS */
1954     if (count > 0) {
1955         gotEof = 0;
1956         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1957         if (outCount < count) {
1958             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1959         }
1960         if(have_sent_ICS_logon == 2) {
1961           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1962             fprintf(ini, "%s", message);
1963             have_sent_ICS_logon = 3;
1964           } else
1965             have_sent_ICS_logon = 1;
1966         } else if(have_sent_ICS_logon == 3) {
1967             fprintf(ini, "%s", message);
1968             fclose(ini);
1969           have_sent_ICS_logon = 1;
1970         }
1971     } else if (count < 0) {
1972         RemoveInputSource(isr);
1973         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1974     } else if (gotEof++ > 0) {
1975         RemoveInputSource(isr);
1976         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1977     }
1978 }
1979
1980 void
1981 KeepAlive ()
1982 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1983     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1984     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1985     SendToICS("date\n");
1986     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1987 }
1988
1989 /* added routine for printf style output to ics */
1990 void
1991 ics_printf (char *format, ...)
1992 {
1993     char buffer[MSG_SIZ];
1994     va_list args;
1995
1996     va_start(args, format);
1997     vsnprintf(buffer, sizeof(buffer), format, args);
1998     buffer[sizeof(buffer)-1] = '\0';
1999     SendToICS(buffer);
2000     va_end(args);
2001 }
2002
2003 void
2004 SendToICS (char *s)
2005 {
2006     int count, outCount, outError;
2007
2008     if (icsPR == NoProc) return;
2009
2010     count = strlen(s);
2011     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2012     if (outCount < count) {
2013         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2014     }
2015 }
2016
2017 /* This is used for sending logon scripts to the ICS. Sending
2018    without a delay causes problems when using timestamp on ICC
2019    (at least on my machine). */
2020 void
2021 SendToICSDelayed (char *s, long msdelay)
2022 {
2023     int count, outCount, outError;
2024
2025     if (icsPR == NoProc) return;
2026
2027     count = strlen(s);
2028     if (appData.debugMode) {
2029         fprintf(debugFP, ">ICS: ");
2030         show_bytes(debugFP, s, count);
2031         fprintf(debugFP, "\n");
2032     }
2033     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2034                                       msdelay);
2035     if (outCount < count) {
2036         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2037     }
2038 }
2039
2040
2041 /* Remove all highlighting escape sequences in s
2042    Also deletes any suffix starting with '('
2043    */
2044 char *
2045 StripHighlightAndTitle (char *s)
2046 {
2047     static char retbuf[MSG_SIZ];
2048     char *p = retbuf;
2049
2050     while (*s != NULLCHAR) {
2051         while (*s == '\033') {
2052             while (*s != NULLCHAR && !isalpha(*s)) s++;
2053             if (*s != NULLCHAR) s++;
2054         }
2055         while (*s != NULLCHAR && *s != '\033') {
2056             if (*s == '(' || *s == '[') {
2057                 *p = NULLCHAR;
2058                 return retbuf;
2059             }
2060             *p++ = *s++;
2061         }
2062     }
2063     *p = NULLCHAR;
2064     return retbuf;
2065 }
2066
2067 /* Remove all highlighting escape sequences in s */
2068 char *
2069 StripHighlight (char *s)
2070 {
2071     static char retbuf[MSG_SIZ];
2072     char *p = retbuf;
2073
2074     while (*s != NULLCHAR) {
2075         while (*s == '\033') {
2076             while (*s != NULLCHAR && !isalpha(*s)) s++;
2077             if (*s != NULLCHAR) s++;
2078         }
2079         while (*s != NULLCHAR && *s != '\033') {
2080             *p++ = *s++;
2081         }
2082     }
2083     *p = NULLCHAR;
2084     return retbuf;
2085 }
2086
2087 char engineVariant[MSG_SIZ];
2088 char *variantNames[] = VARIANT_NAMES;
2089 char *
2090 VariantName (VariantClass v)
2091 {
2092     if(v == VariantUnknown || *engineVariant) return engineVariant;
2093     return variantNames[v];
2094 }
2095
2096
2097 /* Identify a variant from the strings the chess servers use or the
2098    PGN Variant tag names we use. */
2099 VariantClass
2100 StringToVariant (char *e)
2101 {
2102     char *p;
2103     int wnum = -1;
2104     VariantClass v = VariantNormal;
2105     int i, found = FALSE;
2106     char buf[MSG_SIZ];
2107     int len;
2108
2109     if (!e) return v;
2110
2111     /* [HGM] skip over optional board-size prefixes */
2112     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2113         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2114         while( *e++ != '_');
2115     }
2116
2117     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2118         v = VariantNormal;
2119         found = TRUE;
2120     } else
2121     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2122       if (p = StrCaseStr(e, variantNames[i])) {
2123         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2124         v = (VariantClass) i;
2125         found = TRUE;
2126         break;
2127       }
2128     }
2129
2130     if (!found) {
2131       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2132           || StrCaseStr(e, "wild/fr")
2133           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2134         v = VariantFischeRandom;
2135       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2136                  (i = 1, p = StrCaseStr(e, "w"))) {
2137         p += i;
2138         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2139         if (isdigit(*p)) {
2140           wnum = atoi(p);
2141         } else {
2142           wnum = -1;
2143         }
2144         switch (wnum) {
2145         case 0: /* FICS only, actually */
2146         case 1:
2147           /* Castling legal even if K starts on d-file */
2148           v = VariantWildCastle;
2149           break;
2150         case 2:
2151         case 3:
2152         case 4:
2153           /* Castling illegal even if K & R happen to start in
2154              normal positions. */
2155           v = VariantNoCastle;
2156           break;
2157         case 5:
2158         case 7:
2159         case 8:
2160         case 10:
2161         case 11:
2162         case 12:
2163         case 13:
2164         case 14:
2165         case 15:
2166         case 18:
2167         case 19:
2168           /* Castling legal iff K & R start in normal positions */
2169           v = VariantNormal;
2170           break;
2171         case 6:
2172         case 20:
2173         case 21:
2174           /* Special wilds for position setup; unclear what to do here */
2175           v = VariantLoadable;
2176           break;
2177         case 9:
2178           /* Bizarre ICC game */
2179           v = VariantTwoKings;
2180           break;
2181         case 16:
2182           v = VariantKriegspiel;
2183           break;
2184         case 17:
2185           v = VariantLosers;
2186           break;
2187         case 22:
2188           v = VariantFischeRandom;
2189           break;
2190         case 23:
2191           v = VariantCrazyhouse;
2192           break;
2193         case 24:
2194           v = VariantBughouse;
2195           break;
2196         case 25:
2197           v = Variant3Check;
2198           break;
2199         case 26:
2200           /* Not quite the same as FICS suicide! */
2201           v = VariantGiveaway;
2202           break;
2203         case 27:
2204           v = VariantAtomic;
2205           break;
2206         case 28:
2207           v = VariantShatranj;
2208           break;
2209
2210         /* Temporary names for future ICC types.  The name *will* change in
2211            the next xboard/WinBoard release after ICC defines it. */
2212         case 29:
2213           v = Variant29;
2214           break;
2215         case 30:
2216           v = Variant30;
2217           break;
2218         case 31:
2219           v = Variant31;
2220           break;
2221         case 32:
2222           v = Variant32;
2223           break;
2224         case 33:
2225           v = Variant33;
2226           break;
2227         case 34:
2228           v = Variant34;
2229           break;
2230         case 35:
2231           v = Variant35;
2232           break;
2233         case 36:
2234           v = Variant36;
2235           break;
2236         case 37:
2237           v = VariantShogi;
2238           break;
2239         case 38:
2240           v = VariantXiangqi;
2241           break;
2242         case 39:
2243           v = VariantCourier;
2244           break;
2245         case 40:
2246           v = VariantGothic;
2247           break;
2248         case 41:
2249           v = VariantCapablanca;
2250           break;
2251         case 42:
2252           v = VariantKnightmate;
2253           break;
2254         case 43:
2255           v = VariantFairy;
2256           break;
2257         case 44:
2258           v = VariantCylinder;
2259           break;
2260         case 45:
2261           v = VariantFalcon;
2262           break;
2263         case 46:
2264           v = VariantCapaRandom;
2265           break;
2266         case 47:
2267           v = VariantBerolina;
2268           break;
2269         case 48:
2270           v = VariantJanus;
2271           break;
2272         case 49:
2273           v = VariantSuper;
2274           break;
2275         case 50:
2276           v = VariantGreat;
2277           break;
2278         case -1:
2279           /* Found "wild" or "w" in the string but no number;
2280              must assume it's normal chess. */
2281           v = VariantNormal;
2282           break;
2283         default:
2284           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2285           if( (len >= MSG_SIZ) && appData.debugMode )
2286             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2287
2288           DisplayError(buf, 0);
2289           v = VariantUnknown;
2290           break;
2291         }
2292       }
2293     }
2294     if (appData.debugMode) {
2295       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2296               e, wnum, VariantName(v));
2297     }
2298     return v;
2299 }
2300
2301 static int leftover_start = 0, leftover_len = 0;
2302 char star_match[STAR_MATCH_N][MSG_SIZ];
2303
2304 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2305    advance *index beyond it, and set leftover_start to the new value of
2306    *index; else return FALSE.  If pattern contains the character '*', it
2307    matches any sequence of characters not containing '\r', '\n', or the
2308    character following the '*' (if any), and the matched sequence(s) are
2309    copied into star_match.
2310    */
2311 int
2312 looking_at ( char *buf, int *index, char *pattern)
2313 {
2314     char *bufp = &buf[*index], *patternp = pattern;
2315     int star_count = 0;
2316     char *matchp = star_match[0];
2317
2318     for (;;) {
2319         if (*patternp == NULLCHAR) {
2320             *index = leftover_start = bufp - buf;
2321             *matchp = NULLCHAR;
2322             return TRUE;
2323         }
2324         if (*bufp == NULLCHAR) return FALSE;
2325         if (*patternp == '*') {
2326             if (*bufp == *(patternp + 1)) {
2327                 *matchp = NULLCHAR;
2328                 matchp = star_match[++star_count];
2329                 patternp += 2;
2330                 bufp++;
2331                 continue;
2332             } else if (*bufp == '\n' || *bufp == '\r') {
2333                 patternp++;
2334                 if (*patternp == NULLCHAR)
2335                   continue;
2336                 else
2337                   return FALSE;
2338             } else {
2339                 *matchp++ = *bufp++;
2340                 continue;
2341             }
2342         }
2343         if (*patternp != *bufp) return FALSE;
2344         patternp++;
2345         bufp++;
2346     }
2347 }
2348
2349 void
2350 SendToPlayer (char *data, int length)
2351 {
2352     int error, outCount;
2353     outCount = OutputToProcess(NoProc, data, length, &error);
2354     if (outCount < length) {
2355         DisplayFatalError(_("Error writing to display"), error, 1);
2356     }
2357 }
2358
2359 void
2360 PackHolding (char packed[], char *holding)
2361 {
2362     char *p = holding;
2363     char *q = packed;
2364     int runlength = 0;
2365     int curr = 9999;
2366     do {
2367         if (*p == curr) {
2368             runlength++;
2369         } else {
2370             switch (runlength) {
2371               case 0:
2372                 break;
2373               case 1:
2374                 *q++ = curr;
2375                 break;
2376               case 2:
2377                 *q++ = curr;
2378                 *q++ = curr;
2379                 break;
2380               default:
2381                 sprintf(q, "%d", runlength);
2382                 while (*q) q++;
2383                 *q++ = curr;
2384                 break;
2385             }
2386             runlength = 1;
2387             curr = *p;
2388         }
2389     } while (*p++);
2390     *q = NULLCHAR;
2391 }
2392
2393 /* Telnet protocol requests from the front end */
2394 void
2395 TelnetRequest (unsigned char ddww, unsigned char option)
2396 {
2397     unsigned char msg[3];
2398     int outCount, outError;
2399
2400     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2401
2402     if (appData.debugMode) {
2403         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2404         switch (ddww) {
2405           case TN_DO:
2406             ddwwStr = "DO";
2407             break;
2408           case TN_DONT:
2409             ddwwStr = "DONT";
2410             break;
2411           case TN_WILL:
2412             ddwwStr = "WILL";
2413             break;
2414           case TN_WONT:
2415             ddwwStr = "WONT";
2416             break;
2417           default:
2418             ddwwStr = buf1;
2419             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2420             break;
2421         }
2422         switch (option) {
2423           case TN_ECHO:
2424             optionStr = "ECHO";
2425             break;
2426           default:
2427             optionStr = buf2;
2428             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2429             break;
2430         }
2431         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2432     }
2433     msg[0] = TN_IAC;
2434     msg[1] = ddww;
2435     msg[2] = option;
2436     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2437     if (outCount < 3) {
2438         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2439     }
2440 }
2441
2442 void
2443 DoEcho ()
2444 {
2445     if (!appData.icsActive) return;
2446     TelnetRequest(TN_DO, TN_ECHO);
2447 }
2448
2449 void
2450 DontEcho ()
2451 {
2452     if (!appData.icsActive) return;
2453     TelnetRequest(TN_DONT, TN_ECHO);
2454 }
2455
2456 void
2457 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2458 {
2459     /* put the holdings sent to us by the server on the board holdings area */
2460     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2461     char p;
2462     ChessSquare piece;
2463
2464     if(gameInfo.holdingsWidth < 2)  return;
2465     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2466         return; // prevent overwriting by pre-board holdings
2467
2468     if( (int)lowestPiece >= BlackPawn ) {
2469         holdingsColumn = 0;
2470         countsColumn = 1;
2471         holdingsStartRow = BOARD_HEIGHT-1;
2472         direction = -1;
2473     } else {
2474         holdingsColumn = BOARD_WIDTH-1;
2475         countsColumn = BOARD_WIDTH-2;
2476         holdingsStartRow = 0;
2477         direction = 1;
2478     }
2479
2480     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2481         board[i][holdingsColumn] = EmptySquare;
2482         board[i][countsColumn]   = (ChessSquare) 0;
2483     }
2484     while( (p=*holdings++) != NULLCHAR ) {
2485         piece = CharToPiece( ToUpper(p) );
2486         if(piece == EmptySquare) continue;
2487         /*j = (int) piece - (int) WhitePawn;*/
2488         j = PieceToNumber(piece);
2489         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2490         if(j < 0) continue;               /* should not happen */
2491         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2492         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2493         board[holdingsStartRow+j*direction][countsColumn]++;
2494     }
2495 }
2496
2497
2498 void
2499 VariantSwitch (Board board, VariantClass newVariant)
2500 {
2501    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2502    static Board oldBoard;
2503
2504    startedFromPositionFile = FALSE;
2505    if(gameInfo.variant == newVariant) return;
2506
2507    /* [HGM] This routine is called each time an assignment is made to
2508     * gameInfo.variant during a game, to make sure the board sizes
2509     * are set to match the new variant. If that means adding or deleting
2510     * holdings, we shift the playing board accordingly
2511     * This kludge is needed because in ICS observe mode, we get boards
2512     * of an ongoing game without knowing the variant, and learn about the
2513     * latter only later. This can be because of the move list we requested,
2514     * in which case the game history is refilled from the beginning anyway,
2515     * but also when receiving holdings of a crazyhouse game. In the latter
2516     * case we want to add those holdings to the already received position.
2517     */
2518
2519
2520    if (appData.debugMode) {
2521      fprintf(debugFP, "Switch board from %s to %s\n",
2522              VariantName(gameInfo.variant), VariantName(newVariant));
2523      setbuf(debugFP, NULL);
2524    }
2525    shuffleOpenings = 0;       /* [HGM] shuffle */
2526    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2527    switch(newVariant)
2528      {
2529      case VariantShogi:
2530        newWidth = 9;  newHeight = 9;
2531        gameInfo.holdingsSize = 7;
2532      case VariantBughouse:
2533      case VariantCrazyhouse:
2534        newHoldingsWidth = 2; break;
2535      case VariantGreat:
2536        newWidth = 10;
2537      case VariantSuper:
2538        newHoldingsWidth = 2;
2539        gameInfo.holdingsSize = 8;
2540        break;
2541      case VariantGothic:
2542      case VariantCapablanca:
2543      case VariantCapaRandom:
2544        newWidth = 10;
2545      default:
2546        newHoldingsWidth = gameInfo.holdingsSize = 0;
2547      };
2548
2549    if(newWidth  != gameInfo.boardWidth  ||
2550       newHeight != gameInfo.boardHeight ||
2551       newHoldingsWidth != gameInfo.holdingsWidth ) {
2552
2553      /* shift position to new playing area, if needed */
2554      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2555        for(i=0; i<BOARD_HEIGHT; i++)
2556          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2557            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2558              board[i][j];
2559        for(i=0; i<newHeight; i++) {
2560          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2561          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2562        }
2563      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2564        for(i=0; i<BOARD_HEIGHT; i++)
2565          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2566            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2567              board[i][j];
2568      }
2569      board[HOLDINGS_SET] = 0;
2570      gameInfo.boardWidth  = newWidth;
2571      gameInfo.boardHeight = newHeight;
2572      gameInfo.holdingsWidth = newHoldingsWidth;
2573      gameInfo.variant = newVariant;
2574      InitDrawingSizes(-2, 0);
2575    } else gameInfo.variant = newVariant;
2576    CopyBoard(oldBoard, board);   // remember correctly formatted board
2577      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2578    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2579 }
2580
2581 static int loggedOn = FALSE;
2582
2583 /*-- Game start info cache: --*/
2584 int gs_gamenum;
2585 char gs_kind[MSG_SIZ];
2586 static char player1Name[128] = "";
2587 static char player2Name[128] = "";
2588 static char cont_seq[] = "\n\\   ";
2589 static int player1Rating = -1;
2590 static int player2Rating = -1;
2591 /*----------------------------*/
2592
2593 ColorClass curColor = ColorNormal;
2594 int suppressKibitz = 0;
2595
2596 // [HGM] seekgraph
2597 Boolean soughtPending = FALSE;
2598 Boolean seekGraphUp;
2599 #define MAX_SEEK_ADS 200
2600 #define SQUARE 0x80
2601 char *seekAdList[MAX_SEEK_ADS];
2602 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2603 float tcList[MAX_SEEK_ADS];
2604 char colorList[MAX_SEEK_ADS];
2605 int nrOfSeekAds = 0;
2606 int minRating = 1010, maxRating = 2800;
2607 int hMargin = 10, vMargin = 20, h, w;
2608 extern int squareSize, lineGap;
2609
2610 void
2611 PlotSeekAd (int i)
2612 {
2613         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2614         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2615         if(r < minRating+100 && r >=0 ) r = minRating+100;
2616         if(r > maxRating) r = maxRating;
2617         if(tc < 1.f) tc = 1.f;
2618         if(tc > 95.f) tc = 95.f;
2619         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2620         y = ((double)r - minRating)/(maxRating - minRating)
2621             * (h-vMargin-squareSize/8-1) + vMargin;
2622         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2623         if(strstr(seekAdList[i], " u ")) color = 1;
2624         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2625            !strstr(seekAdList[i], "bullet") &&
2626            !strstr(seekAdList[i], "blitz") &&
2627            !strstr(seekAdList[i], "standard") ) color = 2;
2628         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2629         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2630 }
2631
2632 void
2633 PlotSingleSeekAd (int i)
2634 {
2635         PlotSeekAd(i);
2636 }
2637
2638 void
2639 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2640 {
2641         char buf[MSG_SIZ], *ext = "";
2642         VariantClass v = StringToVariant(type);
2643         if(strstr(type, "wild")) {
2644             ext = type + 4; // append wild number
2645             if(v == VariantFischeRandom) type = "chess960"; else
2646             if(v == VariantLoadable) type = "setup"; else
2647             type = VariantName(v);
2648         }
2649         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2650         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2651             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2652             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2653             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2654             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2655             seekNrList[nrOfSeekAds] = nr;
2656             zList[nrOfSeekAds] = 0;
2657             seekAdList[nrOfSeekAds++] = StrSave(buf);
2658             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2659         }
2660 }
2661
2662 void
2663 EraseSeekDot (int i)
2664 {
2665     int x = xList[i], y = yList[i], d=squareSize/4, k;
2666     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2667     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2668     // now replot every dot that overlapped
2669     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2670         int xx = xList[k], yy = yList[k];
2671         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2672             DrawSeekDot(xx, yy, colorList[k]);
2673     }
2674 }
2675
2676 void
2677 RemoveSeekAd (int nr)
2678 {
2679         int i;
2680         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2681             EraseSeekDot(i);
2682             if(seekAdList[i]) free(seekAdList[i]);
2683             seekAdList[i] = seekAdList[--nrOfSeekAds];
2684             seekNrList[i] = seekNrList[nrOfSeekAds];
2685             ratingList[i] = ratingList[nrOfSeekAds];
2686             colorList[i]  = colorList[nrOfSeekAds];
2687             tcList[i] = tcList[nrOfSeekAds];
2688             xList[i]  = xList[nrOfSeekAds];
2689             yList[i]  = yList[nrOfSeekAds];
2690             zList[i]  = zList[nrOfSeekAds];
2691             seekAdList[nrOfSeekAds] = NULL;
2692             break;
2693         }
2694 }
2695
2696 Boolean
2697 MatchSoughtLine (char *line)
2698 {
2699     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2700     int nr, base, inc, u=0; char dummy;
2701
2702     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2703        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2704        (u=1) &&
2705        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2706         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2707         // match: compact and save the line
2708         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2709         return TRUE;
2710     }
2711     return FALSE;
2712 }
2713
2714 int
2715 DrawSeekGraph ()
2716 {
2717     int i;
2718     if(!seekGraphUp) return FALSE;
2719     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2720     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2721
2722     DrawSeekBackground(0, 0, w, h);
2723     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2724     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2725     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2726         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2727         yy = h-1-yy;
2728         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2729         if(i%500 == 0) {
2730             char buf[MSG_SIZ];
2731             snprintf(buf, MSG_SIZ, "%d", i);
2732             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2733         }
2734     }
2735     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2736     for(i=1; i<100; i+=(i<10?1:5)) {
2737         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2738         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2739         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2740             char buf[MSG_SIZ];
2741             snprintf(buf, MSG_SIZ, "%d", i);
2742             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2743         }
2744     }
2745     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2746     return TRUE;
2747 }
2748
2749 int
2750 SeekGraphClick (ClickType click, int x, int y, int moving)
2751 {
2752     static int lastDown = 0, displayed = 0, lastSecond;
2753     if(y < 0) return FALSE;
2754     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2755         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2756         if(!seekGraphUp) return FALSE;
2757         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2758         DrawPosition(TRUE, NULL);
2759         return TRUE;
2760     }
2761     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2762         if(click == Release || moving) return FALSE;
2763         nrOfSeekAds = 0;
2764         soughtPending = TRUE;
2765         SendToICS(ics_prefix);
2766         SendToICS("sought\n"); // should this be "sought all"?
2767     } else { // issue challenge based on clicked ad
2768         int dist = 10000; int i, closest = 0, second = 0;
2769         for(i=0; i<nrOfSeekAds; i++) {
2770             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2771             if(d < dist) { dist = d; closest = i; }
2772             second += (d - zList[i] < 120); // count in-range ads
2773             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2774         }
2775         if(dist < 120) {
2776             char buf[MSG_SIZ];
2777             second = (second > 1);
2778             if(displayed != closest || second != lastSecond) {
2779                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2780                 lastSecond = second; displayed = closest;
2781             }
2782             if(click == Press) {
2783                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2784                 lastDown = closest;
2785                 return TRUE;
2786             } // on press 'hit', only show info
2787             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2788             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2789             SendToICS(ics_prefix);
2790             SendToICS(buf);
2791             return TRUE; // let incoming board of started game pop down the graph
2792         } else if(click == Release) { // release 'miss' is ignored
2793             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2794             if(moving == 2) { // right up-click
2795                 nrOfSeekAds = 0; // refresh graph
2796                 soughtPending = TRUE;
2797                 SendToICS(ics_prefix);
2798                 SendToICS("sought\n"); // should this be "sought all"?
2799             }
2800             return TRUE;
2801         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2802         // press miss or release hit 'pop down' seek graph
2803         seekGraphUp = FALSE;
2804         DrawPosition(TRUE, NULL);
2805     }
2806     return TRUE;
2807 }
2808
2809 void
2810 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2811 {
2812 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2813 #define STARTED_NONE 0
2814 #define STARTED_MOVES 1
2815 #define STARTED_BOARD 2
2816 #define STARTED_OBSERVE 3
2817 #define STARTED_HOLDINGS 4
2818 #define STARTED_CHATTER 5
2819 #define STARTED_COMMENT 6
2820 #define STARTED_MOVES_NOHIDE 7
2821
2822     static int started = STARTED_NONE;
2823     static char parse[20000];
2824     static int parse_pos = 0;
2825     static char buf[BUF_SIZE + 1];
2826     static int firstTime = TRUE, intfSet = FALSE;
2827     static ColorClass prevColor = ColorNormal;
2828     static int savingComment = FALSE;
2829     static int cmatch = 0; // continuation sequence match
2830     char *bp;
2831     char str[MSG_SIZ];
2832     int i, oldi;
2833     int buf_len;
2834     int next_out;
2835     int tkind;
2836     int backup;    /* [DM] For zippy color lines */
2837     char *p;
2838     char talker[MSG_SIZ]; // [HGM] chat
2839     int channel, collective=0;
2840
2841     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2842
2843     if (appData.debugMode) {
2844       if (!error) {
2845         fprintf(debugFP, "<ICS: ");
2846         show_bytes(debugFP, data, count);
2847         fprintf(debugFP, "\n");
2848       }
2849     }
2850
2851     if (appData.debugMode) { int f = forwardMostMove;
2852         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2853                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2854                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2855     }
2856     if (count > 0) {
2857         /* If last read ended with a partial line that we couldn't parse,
2858            prepend it to the new read and try again. */
2859         if (leftover_len > 0) {
2860             for (i=0; i<leftover_len; i++)
2861               buf[i] = buf[leftover_start + i];
2862         }
2863
2864     /* copy new characters into the buffer */
2865     bp = buf + leftover_len;
2866     buf_len=leftover_len;
2867     for (i=0; i<count; i++)
2868     {
2869         // ignore these
2870         if (data[i] == '\r')
2871             continue;
2872
2873         // join lines split by ICS?
2874         if (!appData.noJoin)
2875         {
2876             /*
2877                 Joining just consists of finding matches against the
2878                 continuation sequence, and discarding that sequence
2879                 if found instead of copying it.  So, until a match
2880                 fails, there's nothing to do since it might be the
2881                 complete sequence, and thus, something we don't want
2882                 copied.
2883             */
2884             if (data[i] == cont_seq[cmatch])
2885             {
2886                 cmatch++;
2887                 if (cmatch == strlen(cont_seq))
2888                 {
2889                     cmatch = 0; // complete match.  just reset the counter
2890
2891                     /*
2892                         it's possible for the ICS to not include the space
2893                         at the end of the last word, making our [correct]
2894                         join operation fuse two separate words.  the server
2895                         does this when the space occurs at the width setting.
2896                     */
2897                     if (!buf_len || buf[buf_len-1] != ' ')
2898                     {
2899                         *bp++ = ' ';
2900                         buf_len++;
2901                     }
2902                 }
2903                 continue;
2904             }
2905             else if (cmatch)
2906             {
2907                 /*
2908                     match failed, so we have to copy what matched before
2909                     falling through and copying this character.  In reality,
2910                     this will only ever be just the newline character, but
2911                     it doesn't hurt to be precise.
2912                 */
2913                 strncpy(bp, cont_seq, cmatch);
2914                 bp += cmatch;
2915                 buf_len += cmatch;
2916                 cmatch = 0;
2917             }
2918         }
2919
2920         // copy this char
2921         *bp++ = data[i];
2922         buf_len++;
2923     }
2924
2925         buf[buf_len] = NULLCHAR;
2926 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2927         next_out = 0;
2928         leftover_start = 0;
2929
2930         i = 0;
2931         while (i < buf_len) {
2932             /* Deal with part of the TELNET option negotiation
2933                protocol.  We refuse to do anything beyond the
2934                defaults, except that we allow the WILL ECHO option,
2935                which ICS uses to turn off password echoing when we are
2936                directly connected to it.  We reject this option
2937                if localLineEditing mode is on (always on in xboard)
2938                and we are talking to port 23, which might be a real
2939                telnet server that will try to keep WILL ECHO on permanently.
2940              */
2941             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2942                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2943                 unsigned char option;
2944                 oldi = i;
2945                 switch ((unsigned char) buf[++i]) {
2946                   case TN_WILL:
2947                     if (appData.debugMode)
2948                       fprintf(debugFP, "\n<WILL ");
2949                     switch (option = (unsigned char) buf[++i]) {
2950                       case TN_ECHO:
2951                         if (appData.debugMode)
2952                           fprintf(debugFP, "ECHO ");
2953                         /* Reply only if this is a change, according
2954                            to the protocol rules. */
2955                         if (remoteEchoOption) break;
2956                         if (appData.localLineEditing &&
2957                             atoi(appData.icsPort) == TN_PORT) {
2958                             TelnetRequest(TN_DONT, TN_ECHO);
2959                         } else {
2960                             EchoOff();
2961                             TelnetRequest(TN_DO, TN_ECHO);
2962                             remoteEchoOption = TRUE;
2963                         }
2964                         break;
2965                       default:
2966                         if (appData.debugMode)
2967                           fprintf(debugFP, "%d ", option);
2968                         /* Whatever this is, we don't want it. */
2969                         TelnetRequest(TN_DONT, option);
2970                         break;
2971                     }
2972                     break;
2973                   case TN_WONT:
2974                     if (appData.debugMode)
2975                       fprintf(debugFP, "\n<WONT ");
2976                     switch (option = (unsigned char) buf[++i]) {
2977                       case TN_ECHO:
2978                         if (appData.debugMode)
2979                           fprintf(debugFP, "ECHO ");
2980                         /* Reply only if this is a change, according
2981                            to the protocol rules. */
2982                         if (!remoteEchoOption) break;
2983                         EchoOn();
2984                         TelnetRequest(TN_DONT, TN_ECHO);
2985                         remoteEchoOption = FALSE;
2986                         break;
2987                       default:
2988                         if (appData.debugMode)
2989                           fprintf(debugFP, "%d ", (unsigned char) option);
2990                         /* Whatever this is, it must already be turned
2991                            off, because we never agree to turn on
2992                            anything non-default, so according to the
2993                            protocol rules, we don't reply. */
2994                         break;
2995                     }
2996                     break;
2997                   case TN_DO:
2998                     if (appData.debugMode)
2999                       fprintf(debugFP, "\n<DO ");
3000                     switch (option = (unsigned char) buf[++i]) {
3001                       default:
3002                         /* Whatever this is, we refuse to do it. */
3003                         if (appData.debugMode)
3004                           fprintf(debugFP, "%d ", option);
3005                         TelnetRequest(TN_WONT, option);
3006                         break;
3007                     }
3008                     break;
3009                   case TN_DONT:
3010                     if (appData.debugMode)
3011                       fprintf(debugFP, "\n<DONT ");
3012                     switch (option = (unsigned char) buf[++i]) {
3013                       default:
3014                         if (appData.debugMode)
3015                           fprintf(debugFP, "%d ", option);
3016                         /* Whatever this is, we are already not doing
3017                            it, because we never agree to do anything
3018                            non-default, so according to the protocol
3019                            rules, we don't reply. */
3020                         break;
3021                     }
3022                     break;
3023                   case TN_IAC:
3024                     if (appData.debugMode)
3025                       fprintf(debugFP, "\n<IAC ");
3026                     /* Doubled IAC; pass it through */
3027                     i--;
3028                     break;
3029                   default:
3030                     if (appData.debugMode)
3031                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3032                     /* Drop all other telnet commands on the floor */
3033                     break;
3034                 }
3035                 if (oldi > next_out)
3036                   SendToPlayer(&buf[next_out], oldi - next_out);
3037                 if (++i > next_out)
3038                   next_out = i;
3039                 continue;
3040             }
3041
3042             /* OK, this at least will *usually* work */
3043             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3044                 loggedOn = TRUE;
3045             }
3046
3047             if (loggedOn && !intfSet) {
3048                 if (ics_type == ICS_ICC) {
3049                   snprintf(str, MSG_SIZ,
3050                           "/set-quietly interface %s\n/set-quietly style 12\n",
3051                           programVersion);
3052                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3053                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3054                 } else if (ics_type == ICS_CHESSNET) {
3055                   snprintf(str, MSG_SIZ, "/style 12\n");
3056                 } else {
3057                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3058                   strcat(str, programVersion);
3059                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3060                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3061                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3062 #ifdef WIN32
3063                   strcat(str, "$iset nohighlight 1\n");
3064 #endif
3065                   strcat(str, "$iset lock 1\n$style 12\n");
3066                 }
3067                 SendToICS(str);
3068                 NotifyFrontendLogin();
3069                 intfSet = TRUE;
3070             }
3071
3072             if (started == STARTED_COMMENT) {
3073                 /* Accumulate characters in comment */
3074                 parse[parse_pos++] = buf[i];
3075                 if (buf[i] == '\n') {
3076                     parse[parse_pos] = NULLCHAR;
3077                     if(chattingPartner>=0) {
3078                         char mess[MSG_SIZ];
3079                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3080                         OutputChatMessage(chattingPartner, mess);
3081                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3082                             int p;
3083                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3084                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3085                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3086                                 OutputChatMessage(p, mess);
3087                                 break;
3088                             }
3089                         }
3090                         chattingPartner = -1;
3091                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3092                         collective = 0;
3093                     } else
3094                     if(!suppressKibitz) // [HGM] kibitz
3095                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3096                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3097                         int nrDigit = 0, nrAlph = 0, j;
3098                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3099                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3100                         parse[parse_pos] = NULLCHAR;
3101                         // try to be smart: if it does not look like search info, it should go to
3102                         // ICS interaction window after all, not to engine-output window.
3103                         for(j=0; j<parse_pos; j++) { // count letters and digits
3104                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3105                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3106                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3107                         }
3108                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3109                             int depth=0; float score;
3110                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3111                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3112                                 pvInfoList[forwardMostMove-1].depth = depth;
3113                                 pvInfoList[forwardMostMove-1].score = 100*score;
3114                             }
3115                             OutputKibitz(suppressKibitz, parse);
3116                         } else {
3117                             char tmp[MSG_SIZ];
3118                             if(gameMode == IcsObserving) // restore original ICS messages
3119                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3120                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3121                             else
3122                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3123                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3124                             SendToPlayer(tmp, strlen(tmp));
3125                         }
3126                         next_out = i+1; // [HGM] suppress printing in ICS window
3127                     }
3128                     started = STARTED_NONE;
3129                 } else {
3130                     /* Don't match patterns against characters in comment */
3131                     i++;
3132                     continue;
3133                 }
3134             }
3135             if (started == STARTED_CHATTER) {
3136                 if (buf[i] != '\n') {
3137                     /* Don't match patterns against characters in chatter */
3138                     i++;
3139                     continue;
3140                 }
3141                 started = STARTED_NONE;
3142                 if(suppressKibitz) next_out = i+1;
3143             }
3144
3145             /* Kludge to deal with rcmd protocol */
3146             if (firstTime && looking_at(buf, &i, "\001*")) {
3147                 DisplayFatalError(&buf[1], 0, 1);
3148                 continue;
3149             } else {
3150                 firstTime = FALSE;
3151             }
3152
3153             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3154                 ics_type = ICS_ICC;
3155                 ics_prefix = "/";
3156                 if (appData.debugMode)
3157                   fprintf(debugFP, "ics_type %d\n", ics_type);
3158                 continue;
3159             }
3160             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3161                 ics_type = ICS_FICS;
3162                 ics_prefix = "$";
3163                 if (appData.debugMode)
3164                   fprintf(debugFP, "ics_type %d\n", ics_type);
3165                 continue;
3166             }
3167             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3168                 ics_type = ICS_CHESSNET;
3169                 ics_prefix = "/";
3170                 if (appData.debugMode)
3171                   fprintf(debugFP, "ics_type %d\n", ics_type);
3172                 continue;
3173             }
3174
3175             if (!loggedOn &&
3176                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3177                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3178                  looking_at(buf, &i, "will be \"*\""))) {
3179               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3180               continue;
3181             }
3182
3183             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3184               char buf[MSG_SIZ];
3185               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3186               DisplayIcsInteractionTitle(buf);
3187               have_set_title = TRUE;
3188             }
3189
3190             /* skip finger notes */
3191             if (started == STARTED_NONE &&
3192                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3193                  (buf[i] == '1' && buf[i+1] == '0')) &&
3194                 buf[i+2] == ':' && buf[i+3] == ' ') {
3195               started = STARTED_CHATTER;
3196               i += 3;
3197               continue;
3198             }
3199
3200             oldi = i;
3201             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3202             if(appData.seekGraph) {
3203                 if(soughtPending && MatchSoughtLine(buf+i)) {
3204                     i = strstr(buf+i, "rated") - buf;
3205                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3206                     next_out = leftover_start = i;
3207                     started = STARTED_CHATTER;
3208                     suppressKibitz = TRUE;
3209                     continue;
3210                 }
3211                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3212                         && looking_at(buf, &i, "* ads displayed")) {
3213                     soughtPending = FALSE;
3214                     seekGraphUp = TRUE;
3215                     DrawSeekGraph();
3216                     continue;
3217                 }
3218                 if(appData.autoRefresh) {
3219                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3220                         int s = (ics_type == ICS_ICC); // ICC format differs
3221                         if(seekGraphUp)
3222                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3223                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3224                         looking_at(buf, &i, "*% "); // eat prompt
3225                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3226                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3227                         next_out = i; // suppress
3228                         continue;
3229                     }
3230                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3231                         char *p = star_match[0];
3232                         while(*p) {
3233                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3234                             while(*p && *p++ != ' '); // next
3235                         }
3236                         looking_at(buf, &i, "*% "); // eat prompt
3237                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3238                         next_out = i;
3239                         continue;
3240                     }
3241                 }
3242             }
3243
3244             /* skip formula vars */
3245             if (started == STARTED_NONE &&
3246                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3247               started = STARTED_CHATTER;
3248               i += 3;
3249               continue;
3250             }
3251
3252             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3253             if (appData.autoKibitz && started == STARTED_NONE &&
3254                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3255                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3256                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3257                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3258                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3259                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3260                         suppressKibitz = TRUE;
3261                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3262                         next_out = i;
3263                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3264                                 && (gameMode == IcsPlayingWhite)) ||
3265                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3266                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3267                             started = STARTED_CHATTER; // own kibitz we simply discard
3268                         else {
3269                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3270                             parse_pos = 0; parse[0] = NULLCHAR;
3271                             savingComment = TRUE;
3272                             suppressKibitz = gameMode != IcsObserving ? 2 :
3273                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3274                         }
3275                         continue;
3276                 } else
3277                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3278                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3279                          && atoi(star_match[0])) {
3280                     // suppress the acknowledgements of our own autoKibitz
3281                     char *p;
3282                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3283                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3284                     SendToPlayer(star_match[0], strlen(star_match[0]));
3285                     if(looking_at(buf, &i, "*% ")) // eat prompt
3286                         suppressKibitz = FALSE;
3287                     next_out = i;
3288                     continue;
3289                 }
3290             } // [HGM] kibitz: end of patch
3291
3292             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3293
3294             // [HGM] chat: intercept tells by users for which we have an open chat window
3295             channel = -1;
3296             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3297                                            looking_at(buf, &i, "* whispers:") ||
3298                                            looking_at(buf, &i, "* kibitzes:") ||
3299                                            looking_at(buf, &i, "* shouts:") ||
3300                                            looking_at(buf, &i, "* c-shouts:") ||
3301                                            looking_at(buf, &i, "--> * ") ||
3302                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3303                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3304                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3305                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3306                 int p;
3307                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3308                 chattingPartner = -1; collective = 0;
3309
3310                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3311                 for(p=0; p<MAX_CHAT; p++) {
3312                     collective = 1;
3313                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3314                     talker[0] = '['; strcat(talker, "] ");
3315                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3316                     chattingPartner = p; break;
3317                     }
3318                 } else
3319                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3320                 for(p=0; p<MAX_CHAT; p++) {
3321                     collective = 1;
3322                     if(!strcmp("kibitzes", chatPartner[p])) {
3323                         talker[0] = '['; strcat(talker, "] ");
3324                         chattingPartner = p; break;
3325                     }
3326                 } else
3327                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(!strcmp("whispers", chatPartner[p])) {
3331                         talker[0] = '['; strcat(talker, "] ");
3332                         chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3336                   if(buf[i-8] == '-' && buf[i-3] == 't')
3337                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3338                     collective = 1;
3339                     if(!strcmp("c-shouts", chatPartner[p])) {
3340                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3341                         chattingPartner = p; break;
3342                     }
3343                   }
3344                   if(chattingPartner < 0)
3345                   for(p=0; p<MAX_CHAT; p++) {
3346                     collective = 1;
3347                     if(!strcmp("shouts", chatPartner[p])) {
3348                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3349                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3350                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3351                         chattingPartner = p; break;
3352                     }
3353                   }
3354                 }
3355                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3356                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3357                     talker[0] = 0;
3358                     Colorize(ColorTell, FALSE);
3359                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3360                     collective |= 2;
3361                     chattingPartner = p; break;
3362                 }
3363                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3364                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3365                     started = STARTED_COMMENT;
3366                     parse_pos = 0; parse[0] = NULLCHAR;
3367                     savingComment = 3 + chattingPartner; // counts as TRUE
3368                     if(collective == 3) i = oldi; else {
3369                         suppressKibitz = TRUE;
3370                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3371                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3372                         continue;
3373                     }
3374                 }
3375             } // [HGM] chat: end of patch
3376
3377           backup = i;
3378             if (appData.zippyTalk || appData.zippyPlay) {
3379                 /* [DM] Backup address for color zippy lines */
3380 #if ZIPPY
3381                if (loggedOn == TRUE)
3382                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3383                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3384 #endif
3385             } // [DM] 'else { ' deleted
3386                 if (
3387                     /* Regular tells and says */
3388                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3389                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3390                     looking_at(buf, &i, "* says: ") ||
3391                     /* Don't color "message" or "messages" output */
3392                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3393                     looking_at(buf, &i, "*. * at *:*: ") ||
3394                     looking_at(buf, &i, "--* (*:*): ") ||
3395                     /* Message notifications (same color as tells) */
3396                     looking_at(buf, &i, "* has left a message ") ||
3397                     looking_at(buf, &i, "* just sent you a message:\n") ||
3398                     /* Whispers and kibitzes */
3399                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3400                     looking_at(buf, &i, "* kibitzes: ") ||
3401                     /* Channel tells */
3402                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3403
3404                   if (tkind == 1 && strchr(star_match[0], ':')) {
3405                       /* Avoid "tells you:" spoofs in channels */
3406                      tkind = 3;
3407                   }
3408                   if (star_match[0][0] == NULLCHAR ||
3409                       strchr(star_match[0], ' ') ||
3410                       (tkind == 3 && strchr(star_match[1], ' '))) {
3411                     /* Reject bogus matches */
3412                     i = oldi;
3413                   } else {
3414                     if (appData.colorize) {
3415                       if (oldi > next_out) {
3416                         SendToPlayer(&buf[next_out], oldi - next_out);
3417                         next_out = oldi;
3418                       }
3419                       switch (tkind) {
3420                       case 1:
3421                         Colorize(ColorTell, FALSE);
3422                         curColor = ColorTell;
3423                         break;
3424                       case 2:
3425                         Colorize(ColorKibitz, FALSE);
3426                         curColor = ColorKibitz;
3427                         break;
3428                       case 3:
3429                         p = strrchr(star_match[1], '(');
3430                         if (p == NULL) {
3431                           p = star_match[1];
3432                         } else {
3433                           p++;
3434                         }
3435                         if (atoi(p) == 1) {
3436                           Colorize(ColorChannel1, FALSE);
3437                           curColor = ColorChannel1;
3438                         } else {
3439                           Colorize(ColorChannel, FALSE);
3440                           curColor = ColorChannel;
3441                         }
3442                         break;
3443                       case 5:
3444                         curColor = ColorNormal;
3445                         break;
3446                       }
3447                     }
3448                     if (started == STARTED_NONE && appData.autoComment &&
3449                         (gameMode == IcsObserving ||
3450                          gameMode == IcsPlayingWhite ||
3451                          gameMode == IcsPlayingBlack)) {
3452                       parse_pos = i - oldi;
3453                       memcpy(parse, &buf[oldi], parse_pos);
3454                       parse[parse_pos] = NULLCHAR;
3455                       started = STARTED_COMMENT;
3456                       savingComment = TRUE;
3457                     } else if(collective != 3) {
3458                       started = STARTED_CHATTER;
3459                       savingComment = FALSE;
3460                     }
3461                     loggedOn = TRUE;
3462                     continue;
3463                   }
3464                 }
3465
3466                 if (looking_at(buf, &i, "* s-shouts: ") ||
3467                     looking_at(buf, &i, "* c-shouts: ")) {
3468                     if (appData.colorize) {
3469                         if (oldi > next_out) {
3470                             SendToPlayer(&buf[next_out], oldi - next_out);
3471                             next_out = oldi;
3472                         }
3473                         Colorize(ColorSShout, FALSE);
3474                         curColor = ColorSShout;
3475                     }
3476                     loggedOn = TRUE;
3477                     started = STARTED_CHATTER;
3478                     continue;
3479                 }
3480
3481                 if (looking_at(buf, &i, "--->")) {
3482                     loggedOn = TRUE;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "* shouts: ") ||
3487                     looking_at(buf, &i, "--> ")) {
3488                     if (appData.colorize) {
3489                         if (oldi > next_out) {
3490                             SendToPlayer(&buf[next_out], oldi - next_out);
3491                             next_out = oldi;
3492                         }
3493                         Colorize(ColorShout, FALSE);
3494                         curColor = ColorShout;
3495                     }
3496                     loggedOn = TRUE;
3497                     started = STARTED_CHATTER;
3498                     continue;
3499                 }
3500
3501                 if (looking_at( buf, &i, "Challenge:")) {
3502                     if (appData.colorize) {
3503                         if (oldi > next_out) {
3504                             SendToPlayer(&buf[next_out], oldi - next_out);
3505                             next_out = oldi;
3506                         }
3507                         Colorize(ColorChallenge, FALSE);
3508                         curColor = ColorChallenge;
3509                     }
3510                     loggedOn = TRUE;
3511                     continue;
3512                 }
3513
3514                 if (looking_at(buf, &i, "* offers you") ||
3515                     looking_at(buf, &i, "* offers to be") ||
3516                     looking_at(buf, &i, "* would like to") ||
3517                     looking_at(buf, &i, "* requests to") ||
3518                     looking_at(buf, &i, "Your opponent offers") ||
3519                     looking_at(buf, &i, "Your opponent requests")) {
3520
3521                     if (appData.colorize) {
3522                         if (oldi > next_out) {
3523                             SendToPlayer(&buf[next_out], oldi - next_out);
3524                             next_out = oldi;
3525                         }
3526                         Colorize(ColorRequest, FALSE);
3527                         curColor = ColorRequest;
3528                     }
3529                     continue;
3530                 }
3531
3532                 if (looking_at(buf, &i, "* (*) seeking")) {
3533                     if (appData.colorize) {
3534                         if (oldi > next_out) {
3535                             SendToPlayer(&buf[next_out], oldi - next_out);
3536                             next_out = oldi;
3537                         }
3538                         Colorize(ColorSeek, FALSE);
3539                         curColor = ColorSeek;
3540                     }
3541                     continue;
3542             }
3543
3544           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3545
3546             if (looking_at(buf, &i, "\\   ")) {
3547                 if (prevColor != ColorNormal) {
3548                     if (oldi > next_out) {
3549                         SendToPlayer(&buf[next_out], oldi - next_out);
3550                         next_out = oldi;
3551                     }
3552                     Colorize(prevColor, TRUE);
3553                     curColor = prevColor;
3554                 }
3555                 if (savingComment) {
3556                     parse_pos = i - oldi;
3557                     memcpy(parse, &buf[oldi], parse_pos);
3558                     parse[parse_pos] = NULLCHAR;
3559                     started = STARTED_COMMENT;
3560                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3561                         chattingPartner = savingComment - 3; // kludge to remember the box
3562                 } else {
3563                     started = STARTED_CHATTER;
3564                 }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "Black Strength :") ||
3569                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3570                 looking_at(buf, &i, "<10>") ||
3571                 looking_at(buf, &i, "#@#")) {
3572                 /* Wrong board style */
3573                 loggedOn = TRUE;
3574                 SendToICS(ics_prefix);
3575                 SendToICS("set style 12\n");
3576                 SendToICS(ics_prefix);
3577                 SendToICS("refresh\n");
3578                 continue;
3579             }
3580
3581             if (looking_at(buf, &i, "login:")) {
3582               if (!have_sent_ICS_logon) {
3583                 if(ICSInitScript())
3584                   have_sent_ICS_logon = 1;
3585                 else // no init script was found
3586                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3587               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3588                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3589               }
3590                 continue;
3591             }
3592
3593             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3594                 (looking_at(buf, &i, "\n<12> ") ||
3595                  looking_at(buf, &i, "<12> "))) {
3596                 loggedOn = TRUE;
3597                 if (oldi > next_out) {
3598                     SendToPlayer(&buf[next_out], oldi - next_out);
3599                 }
3600                 next_out = i;
3601                 started = STARTED_BOARD;
3602                 parse_pos = 0;
3603                 continue;
3604             }
3605
3606             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3607                 looking_at(buf, &i, "<b1> ")) {
3608                 if (oldi > next_out) {
3609                     SendToPlayer(&buf[next_out], oldi - next_out);
3610                 }
3611                 next_out = i;
3612                 started = STARTED_HOLDINGS;
3613                 parse_pos = 0;
3614                 continue;
3615             }
3616
3617             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3618                 loggedOn = TRUE;
3619                 /* Header for a move list -- first line */
3620
3621                 switch (ics_getting_history) {
3622                   case H_FALSE:
3623                     switch (gameMode) {
3624                       case IcsIdle:
3625                       case BeginningOfGame:
3626                         /* User typed "moves" or "oldmoves" while we
3627                            were idle.  Pretend we asked for these
3628                            moves and soak them up so user can step
3629                            through them and/or save them.
3630                            */
3631                         Reset(FALSE, TRUE);
3632                         gameMode = IcsObserving;
3633                         ModeHighlight();
3634                         ics_gamenum = -1;
3635                         ics_getting_history = H_GOT_UNREQ_HEADER;
3636                         break;
3637                       case EditGame: /*?*/
3638                       case EditPosition: /*?*/
3639                         /* Should above feature work in these modes too? */
3640                         /* For now it doesn't */
3641                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3642                         break;
3643                       default:
3644                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3645                         break;
3646                     }
3647                     break;
3648                   case H_REQUESTED:
3649                     /* Is this the right one? */
3650                     if (gameInfo.white && gameInfo.black &&
3651                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3652                         strcmp(gameInfo.black, star_match[2]) == 0) {
3653                         /* All is well */
3654                         ics_getting_history = H_GOT_REQ_HEADER;
3655                     }
3656                     break;
3657                   case H_GOT_REQ_HEADER:
3658                   case H_GOT_UNREQ_HEADER:
3659                   case H_GOT_UNWANTED_HEADER:
3660                   case H_GETTING_MOVES:
3661                     /* Should not happen */
3662                     DisplayError(_("Error gathering move list: two headers"), 0);
3663                     ics_getting_history = H_FALSE;
3664                     break;
3665                 }
3666
3667                 /* Save player ratings into gameInfo if needed */
3668                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3669                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3670                     (gameInfo.whiteRating == -1 ||
3671                      gameInfo.blackRating == -1)) {
3672
3673                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3674                     gameInfo.blackRating = string_to_rating(star_match[3]);
3675                     if (appData.debugMode)
3676                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3677                               gameInfo.whiteRating, gameInfo.blackRating);
3678                 }
3679                 continue;
3680             }
3681
3682             if (looking_at(buf, &i,
3683               "* * match, initial time: * minute*, increment: * second")) {
3684                 /* Header for a move list -- second line */
3685                 /* Initial board will follow if this is a wild game */
3686                 if (gameInfo.event != NULL) free(gameInfo.event);
3687                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3688                 gameInfo.event = StrSave(str);
3689                 /* [HGM] we switched variant. Translate boards if needed. */
3690                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3691                 continue;
3692             }
3693
3694             if (looking_at(buf, &i, "Move  ")) {
3695                 /* Beginning of a move list */
3696                 switch (ics_getting_history) {
3697                   case H_FALSE:
3698                     /* Normally should not happen */
3699                     /* Maybe user hit reset while we were parsing */
3700                     break;
3701                   case H_REQUESTED:
3702                     /* Happens if we are ignoring a move list that is not
3703                      * the one we just requested.  Common if the user
3704                      * tries to observe two games without turning off
3705                      * getMoveList */
3706                     break;
3707                   case H_GETTING_MOVES:
3708                     /* Should not happen */
3709                     DisplayError(_("Error gathering move list: nested"), 0);
3710                     ics_getting_history = H_FALSE;
3711                     break;
3712                   case H_GOT_REQ_HEADER:
3713                     ics_getting_history = H_GETTING_MOVES;
3714                     started = STARTED_MOVES;
3715                     parse_pos = 0;
3716                     if (oldi > next_out) {
3717                         SendToPlayer(&buf[next_out], oldi - next_out);
3718                     }
3719                     break;
3720                   case H_GOT_UNREQ_HEADER:
3721                     ics_getting_history = H_GETTING_MOVES;
3722                     started = STARTED_MOVES_NOHIDE;
3723                     parse_pos = 0;
3724                     break;
3725                   case H_GOT_UNWANTED_HEADER:
3726                     ics_getting_history = H_FALSE;
3727                     break;
3728                 }
3729                 continue;
3730             }
3731
3732             if (looking_at(buf, &i, "% ") ||
3733                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3734                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3735                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3736                     soughtPending = FALSE;
3737                     seekGraphUp = TRUE;
3738                     DrawSeekGraph();
3739                 }
3740                 if(suppressKibitz) next_out = i;
3741                 savingComment = FALSE;
3742                 suppressKibitz = 0;
3743                 switch (started) {
3744                   case STARTED_MOVES:
3745                   case STARTED_MOVES_NOHIDE:
3746                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3747                     parse[parse_pos + i - oldi] = NULLCHAR;
3748                     ParseGameHistory(parse);
3749 #if ZIPPY
3750                     if (appData.zippyPlay && first.initDone) {
3751                         FeedMovesToProgram(&first, forwardMostMove);
3752                         if (gameMode == IcsPlayingWhite) {
3753                             if (WhiteOnMove(forwardMostMove)) {
3754                                 if (first.sendTime) {
3755                                   if (first.useColors) {
3756                                     SendToProgram("black\n", &first);
3757                                   }
3758                                   SendTimeRemaining(&first, TRUE);
3759                                 }
3760                                 if (first.useColors) {
3761                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3762                                 }
3763                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3764                                 first.maybeThinking = TRUE;
3765                             } else {
3766                                 if (first.usePlayother) {
3767                                   if (first.sendTime) {
3768                                     SendTimeRemaining(&first, TRUE);
3769                                   }
3770                                   SendToProgram("playother\n", &first);
3771                                   firstMove = FALSE;
3772                                 } else {
3773                                   firstMove = TRUE;
3774                                 }
3775                             }
3776                         } else if (gameMode == IcsPlayingBlack) {
3777                             if (!WhiteOnMove(forwardMostMove)) {
3778                                 if (first.sendTime) {
3779                                   if (first.useColors) {
3780                                     SendToProgram("white\n", &first);
3781                                   }
3782                                   SendTimeRemaining(&first, FALSE);
3783                                 }
3784                                 if (first.useColors) {
3785                                   SendToProgram("black\n", &first);
3786                                 }
3787                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3788                                 first.maybeThinking = TRUE;
3789                             } else {
3790                                 if (first.usePlayother) {
3791                                   if (first.sendTime) {
3792                                     SendTimeRemaining(&first, FALSE);
3793                                   }
3794                                   SendToProgram("playother\n", &first);
3795                                   firstMove = FALSE;
3796                                 } else {
3797                                   firstMove = TRUE;
3798                                 }
3799                             }
3800                         }
3801                     }
3802 #endif
3803                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3804                         /* Moves came from oldmoves or moves command
3805                            while we weren't doing anything else.
3806                            */
3807                         currentMove = forwardMostMove;
3808                         ClearHighlights();/*!!could figure this out*/
3809                         flipView = appData.flipView;
3810                         DrawPosition(TRUE, boards[currentMove]);
3811                         DisplayBothClocks();
3812                         snprintf(str, MSG_SIZ, "%s %s %s",
3813                                 gameInfo.white, _("vs."),  gameInfo.black);
3814                         DisplayTitle(str);
3815                         gameMode = IcsIdle;
3816                     } else {
3817                         /* Moves were history of an active game */
3818                         if (gameInfo.resultDetails != NULL) {
3819                             free(gameInfo.resultDetails);
3820                             gameInfo.resultDetails = NULL;
3821                         }
3822                     }
3823                     HistorySet(parseList, backwardMostMove,
3824                                forwardMostMove, currentMove-1);
3825                     DisplayMove(currentMove - 1);
3826                     if (started == STARTED_MOVES) next_out = i;
3827                     started = STARTED_NONE;
3828                     ics_getting_history = H_FALSE;
3829                     break;
3830
3831                   case STARTED_OBSERVE:
3832                     started = STARTED_NONE;
3833                     SendToICS(ics_prefix);
3834                     SendToICS("refresh\n");
3835                     break;
3836
3837                   default:
3838                     break;
3839                 }
3840                 if(bookHit) { // [HGM] book: simulate book reply
3841                     static char bookMove[MSG_SIZ]; // a bit generous?
3842
3843                     programStats.nodes = programStats.depth = programStats.time =
3844                     programStats.score = programStats.got_only_move = 0;
3845                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3846
3847                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3848                     strcat(bookMove, bookHit);
3849                     HandleMachineMove(bookMove, &first);
3850                 }
3851                 continue;
3852             }
3853
3854             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3855                  started == STARTED_HOLDINGS ||
3856                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3857                 /* Accumulate characters in move list or board */
3858                 parse[parse_pos++] = buf[i];
3859             }
3860
3861             /* Start of game messages.  Mostly we detect start of game
3862                when the first board image arrives.  On some versions
3863                of the ICS, though, we need to do a "refresh" after starting
3864                to observe in order to get the current board right away. */
3865             if (looking_at(buf, &i, "Adding game * to observation list")) {
3866                 started = STARTED_OBSERVE;
3867                 continue;
3868             }
3869
3870             /* Handle auto-observe */
3871             if (appData.autoObserve &&
3872                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3873                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3874                 char *player;
3875                 /* Choose the player that was highlighted, if any. */
3876                 if (star_match[0][0] == '\033' ||
3877                     star_match[1][0] != '\033') {
3878                     player = star_match[0];
3879                 } else {
3880                     player = star_match[2];
3881                 }
3882                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3883                         ics_prefix, StripHighlightAndTitle(player));
3884                 SendToICS(str);
3885
3886                 /* Save ratings from notify string */
3887                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3888                 player1Rating = string_to_rating(star_match[1]);
3889                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3890                 player2Rating = string_to_rating(star_match[3]);
3891
3892                 if (appData.debugMode)
3893                   fprintf(debugFP,
3894                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3895                           player1Name, player1Rating,
3896                           player2Name, player2Rating);
3897
3898                 continue;
3899             }
3900
3901             /* Deal with automatic examine mode after a game,
3902                and with IcsObserving -> IcsExamining transition */
3903             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3904                 looking_at(buf, &i, "has made you an examiner of game *")) {
3905
3906                 int gamenum = atoi(star_match[0]);
3907                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3908                     gamenum == ics_gamenum) {
3909                     /* We were already playing or observing this game;
3910                        no need to refetch history */
3911                     gameMode = IcsExamining;
3912                     if (pausing) {
3913                         pauseExamForwardMostMove = forwardMostMove;
3914                     } else if (currentMove < forwardMostMove) {
3915                         ForwardInner(forwardMostMove);
3916                     }
3917                 } else {
3918                     /* I don't think this case really can happen */
3919                     SendToICS(ics_prefix);
3920                     SendToICS("refresh\n");
3921                 }
3922                 continue;
3923             }
3924
3925             /* Error messages */
3926 //          if (ics_user_moved) {
3927             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3928                 if (looking_at(buf, &i, "Illegal move") ||
3929                     looking_at(buf, &i, "Not a legal move") ||
3930                     looking_at(buf, &i, "Your king is in check") ||
3931                     looking_at(buf, &i, "It isn't your turn") ||
3932                     looking_at(buf, &i, "It is not your move")) {
3933                     /* Illegal move */
3934                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3935                         currentMove = forwardMostMove-1;
3936                         DisplayMove(currentMove - 1); /* before DMError */
3937                         DrawPosition(FALSE, boards[currentMove]);
3938                         SwitchClocks(forwardMostMove-1); // [HGM] race
3939                         DisplayBothClocks();
3940                     }
3941                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3942                     ics_user_moved = 0;
3943                     continue;
3944                 }
3945             }
3946
3947             if (looking_at(buf, &i, "still have time") ||
3948                 looking_at(buf, &i, "not out of time") ||
3949                 looking_at(buf, &i, "either player is out of time") ||
3950                 looking_at(buf, &i, "has timeseal; checking")) {
3951                 /* We must have called his flag a little too soon */
3952                 whiteFlag = blackFlag = FALSE;
3953                 continue;
3954             }
3955
3956             if (looking_at(buf, &i, "added * seconds to") ||
3957                 looking_at(buf, &i, "seconds were added to")) {
3958                 /* Update the clocks */
3959                 SendToICS(ics_prefix);
3960                 SendToICS("refresh\n");
3961                 continue;
3962             }
3963
3964             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3965                 ics_clock_paused = TRUE;
3966                 StopClocks();
3967                 continue;
3968             }
3969
3970             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3971                 ics_clock_paused = FALSE;
3972                 StartClocks();
3973                 continue;
3974             }
3975
3976             /* Grab player ratings from the Creating: message.
3977                Note we have to check for the special case when
3978                the ICS inserts things like [white] or [black]. */
3979             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3980                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3981                 /* star_matches:
3982                    0    player 1 name (not necessarily white)
3983                    1    player 1 rating
3984                    2    empty, white, or black (IGNORED)
3985                    3    player 2 name (not necessarily black)
3986                    4    player 2 rating
3987
3988                    The names/ratings are sorted out when the game
3989                    actually starts (below).
3990                 */
3991                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3992                 player1Rating = string_to_rating(star_match[1]);
3993                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3994                 player2Rating = string_to_rating(star_match[4]);
3995
3996                 if (appData.debugMode)
3997                   fprintf(debugFP,
3998                           "Ratings from 'Creating:' %s %d, %s %d\n",
3999                           player1Name, player1Rating,
4000                           player2Name, player2Rating);
4001
4002                 continue;
4003             }
4004
4005             /* Improved generic start/end-of-game messages */
4006             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4007                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4008                 /* If tkind == 0: */
4009                 /* star_match[0] is the game number */
4010                 /*           [1] is the white player's name */
4011                 /*           [2] is the black player's name */
4012                 /* For end-of-game: */
4013                 /*           [3] is the reason for the game end */
4014                 /*           [4] is a PGN end game-token, preceded by " " */
4015                 /* For start-of-game: */
4016                 /*           [3] begins with "Creating" or "Continuing" */
4017                 /*           [4] is " *" or empty (don't care). */
4018                 int gamenum = atoi(star_match[0]);
4019                 char *whitename, *blackname, *why, *endtoken;
4020                 ChessMove endtype = EndOfFile;
4021
4022                 if (tkind == 0) {
4023                   whitename = star_match[1];
4024                   blackname = star_match[2];
4025                   why = star_match[3];
4026                   endtoken = star_match[4];
4027                 } else {
4028                   whitename = star_match[1];
4029                   blackname = star_match[3];
4030                   why = star_match[5];
4031                   endtoken = star_match[6];
4032                 }
4033
4034                 /* Game start messages */
4035                 if (strncmp(why, "Creating ", 9) == 0 ||
4036                     strncmp(why, "Continuing ", 11) == 0) {
4037                     gs_gamenum = gamenum;
4038                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4039                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4040                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4041 #if ZIPPY
4042                     if (appData.zippyPlay) {
4043                         ZippyGameStart(whitename, blackname);
4044                     }
4045 #endif /*ZIPPY*/
4046                     partnerBoardValid = FALSE; // [HGM] bughouse
4047                     continue;
4048                 }
4049
4050                 /* Game end messages */
4051                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4052                     ics_gamenum != gamenum) {
4053                     continue;
4054                 }
4055                 while (endtoken[0] == ' ') endtoken++;
4056                 switch (endtoken[0]) {
4057                   case '*':
4058                   default:
4059                     endtype = GameUnfinished;
4060                     break;
4061                   case '0':
4062                     endtype = BlackWins;
4063                     break;
4064                   case '1':
4065                     if (endtoken[1] == '/')
4066                       endtype = GameIsDrawn;
4067                     else
4068                       endtype = WhiteWins;
4069                     break;
4070                 }
4071                 GameEnds(endtype, why, GE_ICS);
4072 #if ZIPPY
4073                 if (appData.zippyPlay && first.initDone) {
4074                     ZippyGameEnd(endtype, why);
4075                     if (first.pr == NoProc) {
4076                       /* Start the next process early so that we'll
4077                          be ready for the next challenge */
4078                       StartChessProgram(&first);
4079                     }
4080                     /* Send "new" early, in case this command takes
4081                        a long time to finish, so that we'll be ready
4082                        for the next challenge. */
4083                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4084                     Reset(TRUE, TRUE);
4085                 }
4086 #endif /*ZIPPY*/
4087                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4088                 continue;
4089             }
4090
4091             if (looking_at(buf, &i, "Removing game * from observation") ||
4092                 looking_at(buf, &i, "no longer observing game *") ||
4093                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4094                 if (gameMode == IcsObserving &&
4095                     atoi(star_match[0]) == ics_gamenum)
4096                   {
4097                       /* icsEngineAnalyze */
4098                       if (appData.icsEngineAnalyze) {
4099                             ExitAnalyzeMode();
4100                             ModeHighlight();
4101                       }
4102                       StopClocks();
4103                       gameMode = IcsIdle;
4104                       ics_gamenum = -1;
4105                       ics_user_moved = FALSE;
4106                   }
4107                 continue;
4108             }
4109
4110             if (looking_at(buf, &i, "no longer examining game *")) {
4111                 if (gameMode == IcsExamining &&
4112                     atoi(star_match[0]) == ics_gamenum)
4113                   {
4114                       gameMode = IcsIdle;
4115                       ics_gamenum = -1;
4116                       ics_user_moved = FALSE;
4117                   }
4118                 continue;
4119             }
4120
4121             /* Advance leftover_start past any newlines we find,
4122                so only partial lines can get reparsed */
4123             if (looking_at(buf, &i, "\n")) {
4124                 prevColor = curColor;
4125                 if (curColor != ColorNormal) {
4126                     if (oldi > next_out) {
4127                         SendToPlayer(&buf[next_out], oldi - next_out);
4128                         next_out = oldi;
4129                     }
4130                     Colorize(ColorNormal, FALSE);
4131                     curColor = ColorNormal;
4132                 }
4133                 if (started == STARTED_BOARD) {
4134                     started = STARTED_NONE;
4135                     parse[parse_pos] = NULLCHAR;
4136                     ParseBoard12(parse);
4137                     ics_user_moved = 0;
4138
4139                     /* Send premove here */
4140                     if (appData.premove) {
4141                       char str[MSG_SIZ];
4142                       if (currentMove == 0 &&
4143                           gameMode == IcsPlayingWhite &&
4144                           appData.premoveWhite) {
4145                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4146                         if (appData.debugMode)
4147                           fprintf(debugFP, "Sending premove:\n");
4148                         SendToICS(str);
4149                       } else if (currentMove == 1 &&
4150                                  gameMode == IcsPlayingBlack &&
4151                                  appData.premoveBlack) {
4152                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4153                         if (appData.debugMode)
4154                           fprintf(debugFP, "Sending premove:\n");
4155                         SendToICS(str);
4156                       } else if (gotPremove) {
4157                         gotPremove = 0;
4158                         ClearPremoveHighlights();
4159                         if (appData.debugMode)
4160                           fprintf(debugFP, "Sending premove:\n");
4161                           UserMoveEvent(premoveFromX, premoveFromY,
4162                                         premoveToX, premoveToY,
4163                                         premovePromoChar);
4164                       }
4165                     }
4166
4167                     /* Usually suppress following prompt */
4168                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4169                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4170                         if (looking_at(buf, &i, "*% ")) {
4171                             savingComment = FALSE;
4172                             suppressKibitz = 0;
4173                         }
4174                     }
4175                     next_out = i;
4176                 } else if (started == STARTED_HOLDINGS) {
4177                     int gamenum;
4178                     char new_piece[MSG_SIZ];
4179                     started = STARTED_NONE;
4180                     parse[parse_pos] = NULLCHAR;
4181                     if (appData.debugMode)
4182                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4183                                                         parse, currentMove);
4184                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4185                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4186                         if (gameInfo.variant == VariantNormal) {
4187                           /* [HGM] We seem to switch variant during a game!
4188                            * Presumably no holdings were displayed, so we have
4189                            * to move the position two files to the right to
4190                            * create room for them!
4191                            */
4192                           VariantClass newVariant;
4193                           switch(gameInfo.boardWidth) { // base guess on board width
4194                                 case 9:  newVariant = VariantShogi; break;
4195                                 case 10: newVariant = VariantGreat; break;
4196                                 default: newVariant = VariantCrazyhouse; break;
4197                           }
4198                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4199                           /* Get a move list just to see the header, which
4200                              will tell us whether this is really bug or zh */
4201                           if (ics_getting_history == H_FALSE) {
4202                             ics_getting_history = H_REQUESTED;
4203                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4204                             SendToICS(str);
4205                           }
4206                         }
4207                         new_piece[0] = NULLCHAR;
4208                         sscanf(parse, "game %d white [%s black [%s <- %s",
4209                                &gamenum, white_holding, black_holding,
4210                                new_piece);
4211                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4212                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4213                         /* [HGM] copy holdings to board holdings area */
4214                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4215                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4216                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4217 #if ZIPPY
4218                         if (appData.zippyPlay && first.initDone) {
4219                             ZippyHoldings(white_holding, black_holding,
4220                                           new_piece);
4221                         }
4222 #endif /*ZIPPY*/
4223                         if (tinyLayout || smallLayout) {
4224                             char wh[16], bh[16];
4225                             PackHolding(wh, white_holding);
4226                             PackHolding(bh, black_holding);
4227                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4228                                     gameInfo.white, gameInfo.black);
4229                         } else {
4230                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4231                                     gameInfo.white, white_holding, _("vs."),
4232                                     gameInfo.black, black_holding);
4233                         }
4234                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4235                         DrawPosition(FALSE, boards[currentMove]);
4236                         DisplayTitle(str);
4237                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4238                         sscanf(parse, "game %d white [%s black [%s <- %s",
4239                                &gamenum, white_holding, black_holding,
4240                                new_piece);
4241                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4242                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4243                         /* [HGM] copy holdings to partner-board holdings area */
4244                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4245                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4246                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4247                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4248                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4249                       }
4250                     }
4251                     /* Suppress following prompt */
4252                     if (looking_at(buf, &i, "*% ")) {
4253                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4254                         savingComment = FALSE;
4255                         suppressKibitz = 0;
4256                     }
4257                     next_out = i;
4258                 }
4259                 continue;
4260             }
4261
4262             i++;                /* skip unparsed character and loop back */
4263         }
4264
4265         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4266 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4267 //          SendToPlayer(&buf[next_out], i - next_out);
4268             started != STARTED_HOLDINGS && leftover_start > next_out) {
4269             SendToPlayer(&buf[next_out], leftover_start - next_out);
4270             next_out = i;
4271         }
4272
4273         leftover_len = buf_len - leftover_start;
4274         /* if buffer ends with something we couldn't parse,
4275            reparse it after appending the next read */
4276
4277     } else if (count == 0) {
4278         RemoveInputSource(isr);
4279         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4280     } else {
4281         DisplayFatalError(_("Error reading from ICS"), error, 1);
4282     }
4283 }
4284
4285
4286 /* Board style 12 looks like this:
4287
4288    <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
4289
4290  * The "<12> " is stripped before it gets to this routine.  The two
4291  * trailing 0's (flip state and clock ticking) are later addition, and
4292  * some chess servers may not have them, or may have only the first.
4293  * Additional trailing fields may be added in the future.
4294  */
4295
4296 #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"
4297
4298 #define RELATION_OBSERVING_PLAYED    0
4299 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4300 #define RELATION_PLAYING_MYMOVE      1
4301 #define RELATION_PLAYING_NOTMYMOVE  -1
4302 #define RELATION_EXAMINING           2
4303 #define RELATION_ISOLATED_BOARD     -3
4304 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4305
4306 void
4307 ParseBoard12 (char *string)
4308 {
4309 #if ZIPPY
4310     int i, takeback;
4311     char *bookHit = NULL; // [HGM] book
4312 #endif
4313     GameMode newGameMode;
4314     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4315     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4316     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4317     char to_play, board_chars[200];
4318     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4319     char black[32], white[32];
4320     Board board;
4321     int prevMove = currentMove;
4322     int ticking = 2;
4323     ChessMove moveType;
4324     int fromX, fromY, toX, toY;
4325     char promoChar;
4326     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4327     Boolean weird = FALSE, reqFlag = FALSE;
4328
4329     fromX = fromY = toX = toY = -1;
4330
4331     newGame = FALSE;
4332
4333     if (appData.debugMode)
4334       fprintf(debugFP, "Parsing board: %s\n", string);
4335
4336     move_str[0] = NULLCHAR;
4337     elapsed_time[0] = NULLCHAR;
4338     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4339         int  i = 0, j;
4340         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4341             if(string[i] == ' ') { ranks++; files = 0; }
4342             else files++;
4343             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4344             i++;
4345         }
4346         for(j = 0; j <i; j++) board_chars[j] = string[j];
4347         board_chars[i] = '\0';
4348         string += i + 1;
4349     }
4350     n = sscanf(string, PATTERN, &to_play, &double_push,
4351                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4352                &gamenum, white, black, &relation, &basetime, &increment,
4353                &white_stren, &black_stren, &white_time, &black_time,
4354                &moveNum, str, elapsed_time, move_str, &ics_flip,
4355                &ticking);
4356
4357     if (n < 21) {
4358         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4359         DisplayError(str, 0);
4360         return;
4361     }
4362
4363     /* Convert the move number to internal form */
4364     moveNum = (moveNum - 1) * 2;
4365     if (to_play == 'B') moveNum++;
4366     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4367       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4368                         0, 1);
4369       return;
4370     }
4371
4372     switch (relation) {
4373       case RELATION_OBSERVING_PLAYED:
4374       case RELATION_OBSERVING_STATIC:
4375         if (gamenum == -1) {
4376             /* Old ICC buglet */
4377             relation = RELATION_OBSERVING_STATIC;
4378         }
4379         newGameMode = IcsObserving;
4380         break;
4381       case RELATION_PLAYING_MYMOVE:
4382       case RELATION_PLAYING_NOTMYMOVE:
4383         newGameMode =
4384           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4385             IcsPlayingWhite : IcsPlayingBlack;
4386         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4387         break;
4388       case RELATION_EXAMINING:
4389         newGameMode = IcsExamining;
4390         break;
4391       case RELATION_ISOLATED_BOARD:
4392       default:
4393         /* Just display this board.  If user was doing something else,
4394            we will forget about it until the next board comes. */
4395         newGameMode = IcsIdle;
4396         break;
4397       case RELATION_STARTING_POSITION:
4398         newGameMode = gameMode;
4399         break;
4400     }
4401
4402     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4403         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4404          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4405       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4406       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4407       static int lastBgGame = -1;
4408       char *toSqr;
4409       for (k = 0; k < ranks; k++) {
4410         for (j = 0; j < files; j++)
4411           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4412         if(gameInfo.holdingsWidth > 1) {
4413              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4414              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4415         }
4416       }
4417       CopyBoard(partnerBoard, board);
4418       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4419         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4420         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4421       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4422       if(toSqr = strchr(str, '-')) {
4423         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4424         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4425       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4426       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4427       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4428       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4429       if(twoBoards) {
4430           DisplayWhiteClock(white_time*fac, to_play == 'W');
4431           DisplayBlackClock(black_time*fac, to_play != 'W');
4432           activePartner = to_play;
4433           if(gamenum != lastBgGame) {
4434               char buf[MSG_SIZ];
4435               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4436               DisplayTitle(buf);
4437           }
4438           lastBgGame = gamenum;
4439           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4440                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4441       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4442                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4443       if(!twoBoards) DisplayMessage(partnerStatus, "");
4444         partnerBoardValid = TRUE;
4445       return;
4446     }
4447
4448     if(appData.dualBoard && appData.bgObserve) {
4449         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4450             SendToICS(ics_prefix), SendToICS("pobserve\n");
4451         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4452             char buf[MSG_SIZ];
4453             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4454             SendToICS(buf);
4455         }
4456     }
4457
4458     /* Modify behavior for initial board display on move listing
4459        of wild games.
4460        */
4461     switch (ics_getting_history) {
4462       case H_FALSE:
4463       case H_REQUESTED:
4464         break;
4465       case H_GOT_REQ_HEADER:
4466       case H_GOT_UNREQ_HEADER:
4467         /* This is the initial position of the current game */
4468         gamenum = ics_gamenum;
4469         moveNum = 0;            /* old ICS bug workaround */
4470         if (to_play == 'B') {
4471           startedFromSetupPosition = TRUE;
4472           blackPlaysFirst = TRUE;
4473           moveNum = 1;
4474           if (forwardMostMove == 0) forwardMostMove = 1;
4475           if (backwardMostMove == 0) backwardMostMove = 1;
4476           if (currentMove == 0) currentMove = 1;
4477         }
4478         newGameMode = gameMode;
4479         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4480         break;
4481       case H_GOT_UNWANTED_HEADER:
4482         /* This is an initial board that we don't want */
4483         return;
4484       case H_GETTING_MOVES:
4485         /* Should not happen */
4486         DisplayError(_("Error gathering move list: extra board"), 0);
4487         ics_getting_history = H_FALSE;
4488         return;
4489     }
4490
4491    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4492                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4493                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4494      /* [HGM] We seem to have switched variant unexpectedly
4495       * Try to guess new variant from board size
4496       */
4497           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4498           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4499           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4500           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4501           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4502           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4503           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4504           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4505           /* Get a move list just to see the header, which
4506              will tell us whether this is really bug or zh */
4507           if (ics_getting_history == H_FALSE) {
4508             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4509             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4510             SendToICS(str);
4511           }
4512     }
4513
4514     /* Take action if this is the first board of a new game, or of a
4515        different game than is currently being displayed.  */
4516     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4517         relation == RELATION_ISOLATED_BOARD) {
4518
4519         /* Forget the old game and get the history (if any) of the new one */
4520         if (gameMode != BeginningOfGame) {
4521           Reset(TRUE, TRUE);
4522         }
4523         newGame = TRUE;
4524         if (appData.autoRaiseBoard) BoardToTop();
4525         prevMove = -3;
4526         if (gamenum == -1) {
4527             newGameMode = IcsIdle;
4528         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4529                    appData.getMoveList && !reqFlag) {
4530             /* Need to get game history */
4531             ics_getting_history = H_REQUESTED;
4532             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4533             SendToICS(str);
4534         }
4535
4536         /* Initially flip the board to have black on the bottom if playing
4537            black or if the ICS flip flag is set, but let the user change
4538            it with the Flip View button. */
4539         flipView = appData.autoFlipView ?
4540           (newGameMode == IcsPlayingBlack) || ics_flip :
4541           appData.flipView;
4542
4543         /* Done with values from previous mode; copy in new ones */
4544         gameMode = newGameMode;
4545         ModeHighlight();
4546         ics_gamenum = gamenum;
4547         if (gamenum == gs_gamenum) {
4548             int klen = strlen(gs_kind);
4549             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4550             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4551             gameInfo.event = StrSave(str);
4552         } else {
4553             gameInfo.event = StrSave("ICS game");
4554         }
4555         gameInfo.site = StrSave(appData.icsHost);
4556         gameInfo.date = PGNDate();
4557         gameInfo.round = StrSave("-");
4558         gameInfo.white = StrSave(white);
4559         gameInfo.black = StrSave(black);
4560         timeControl = basetime * 60 * 1000;
4561         timeControl_2 = 0;
4562         timeIncrement = increment * 1000;
4563         movesPerSession = 0;
4564         gameInfo.timeControl = TimeControlTagValue();
4565         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4566   if (appData.debugMode) {
4567     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4568     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4569     setbuf(debugFP, NULL);
4570   }
4571
4572         gameInfo.outOfBook = NULL;
4573
4574         /* Do we have the ratings? */
4575         if (strcmp(player1Name, white) == 0 &&
4576             strcmp(player2Name, black) == 0) {
4577             if (appData.debugMode)
4578               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4579                       player1Rating, player2Rating);
4580             gameInfo.whiteRating = player1Rating;
4581             gameInfo.blackRating = player2Rating;
4582         } else if (strcmp(player2Name, white) == 0 &&
4583                    strcmp(player1Name, black) == 0) {
4584             if (appData.debugMode)
4585               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4586                       player2Rating, player1Rating);
4587             gameInfo.whiteRating = player2Rating;
4588             gameInfo.blackRating = player1Rating;
4589         }
4590         player1Name[0] = player2Name[0] = NULLCHAR;
4591
4592         /* Silence shouts if requested */
4593         if (appData.quietPlay &&
4594             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4595             SendToICS(ics_prefix);
4596             SendToICS("set shout 0\n");
4597         }
4598     }
4599
4600     /* Deal with midgame name changes */
4601     if (!newGame) {
4602         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4603             if (gameInfo.white) free(gameInfo.white);
4604             gameInfo.white = StrSave(white);
4605         }
4606         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4607             if (gameInfo.black) free(gameInfo.black);
4608             gameInfo.black = StrSave(black);
4609         }
4610     }
4611
4612     /* Throw away game result if anything actually changes in examine mode */
4613     if (gameMode == IcsExamining && !newGame) {
4614         gameInfo.result = GameUnfinished;
4615         if (gameInfo.resultDetails != NULL) {
4616             free(gameInfo.resultDetails);
4617             gameInfo.resultDetails = NULL;
4618         }
4619     }
4620
4621     /* In pausing && IcsExamining mode, we ignore boards coming
4622        in if they are in a different variation than we are. */
4623     if (pauseExamInvalid) return;
4624     if (pausing && gameMode == IcsExamining) {
4625         if (moveNum <= pauseExamForwardMostMove) {
4626             pauseExamInvalid = TRUE;
4627             forwardMostMove = pauseExamForwardMostMove;
4628             return;
4629         }
4630     }
4631
4632   if (appData.debugMode) {
4633     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4634   }
4635     /* Parse the board */
4636     for (k = 0; k < ranks; k++) {
4637       for (j = 0; j < files; j++)
4638         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4639       if(gameInfo.holdingsWidth > 1) {
4640            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4641            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4642       }
4643     }
4644     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4645       board[5][BOARD_RGHT+1] = WhiteAngel;
4646       board[6][BOARD_RGHT+1] = WhiteMarshall;
4647       board[1][0] = BlackMarshall;
4648       board[2][0] = BlackAngel;
4649       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4650     }
4651     CopyBoard(boards[moveNum], board);
4652     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4653     if (moveNum == 0) {
4654         startedFromSetupPosition =
4655           !CompareBoards(board, initialPosition);
4656         if(startedFromSetupPosition)
4657             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4658     }
4659
4660     /* [HGM] Set castling rights. Take the outermost Rooks,
4661        to make it also work for FRC opening positions. Note that board12
4662        is really defective for later FRC positions, as it has no way to
4663        indicate which Rook can castle if they are on the same side of King.
4664        For the initial position we grant rights to the outermost Rooks,
4665        and remember thos rights, and we then copy them on positions
4666        later in an FRC game. This means WB might not recognize castlings with
4667        Rooks that have moved back to their original position as illegal,
4668        but in ICS mode that is not its job anyway.
4669     */
4670     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4671     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4672
4673         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4674             if(board[0][i] == WhiteRook) j = i;
4675         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4676         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4677             if(board[0][i] == WhiteRook) j = i;
4678         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4679         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4680             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4681         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4682         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4683             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4684         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4685
4686         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4687         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4688         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4689             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4690         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4691             if(board[BOARD_HEIGHT-1][k] == bKing)
4692                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4693         if(gameInfo.variant == VariantTwoKings) {
4694             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4695             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4696             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4697         }
4698     } else { int r;
4699         r = boards[moveNum][CASTLING][0] = initialRights[0];
4700         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4701         r = boards[moveNum][CASTLING][1] = initialRights[1];
4702         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4703         r = boards[moveNum][CASTLING][3] = initialRights[3];
4704         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4705         r = boards[moveNum][CASTLING][4] = initialRights[4];
4706         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4707         /* wildcastle kludge: always assume King has rights */
4708         r = boards[moveNum][CASTLING][2] = initialRights[2];
4709         r = boards[moveNum][CASTLING][5] = initialRights[5];
4710     }
4711     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4712     boards[moveNum][EP_STATUS] = EP_NONE;
4713     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4714     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4715     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4716
4717
4718     if (ics_getting_history == H_GOT_REQ_HEADER ||
4719         ics_getting_history == H_GOT_UNREQ_HEADER) {
4720         /* This was an initial position from a move list, not
4721            the current position */
4722         return;
4723     }
4724
4725     /* Update currentMove and known move number limits */
4726     newMove = newGame || moveNum > forwardMostMove;
4727
4728     if (newGame) {
4729         forwardMostMove = backwardMostMove = currentMove = moveNum;
4730         if (gameMode == IcsExamining && moveNum == 0) {
4731           /* Workaround for ICS limitation: we are not told the wild
4732              type when starting to examine a game.  But if we ask for
4733              the move list, the move list header will tell us */
4734             ics_getting_history = H_REQUESTED;
4735             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4736             SendToICS(str);
4737         }
4738     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4739                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4740 #if ZIPPY
4741         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4742         /* [HGM] applied this also to an engine that is silently watching        */
4743         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4744             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4745             gameInfo.variant == currentlyInitializedVariant) {
4746           takeback = forwardMostMove - moveNum;
4747           for (i = 0; i < takeback; i++) {
4748             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4749             SendToProgram("undo\n", &first);
4750           }
4751         }
4752 #endif
4753
4754         forwardMostMove = moveNum;
4755         if (!pausing || currentMove > forwardMostMove)
4756           currentMove = forwardMostMove;
4757     } else {
4758         /* New part of history that is not contiguous with old part */
4759         if (pausing && gameMode == IcsExamining) {
4760             pauseExamInvalid = TRUE;
4761             forwardMostMove = pauseExamForwardMostMove;
4762             return;
4763         }
4764         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4765 #if ZIPPY
4766             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4767                 // [HGM] when we will receive the move list we now request, it will be
4768                 // fed to the engine from the first move on. So if the engine is not
4769                 // in the initial position now, bring it there.
4770                 InitChessProgram(&first, 0);
4771             }
4772 #endif
4773             ics_getting_history = H_REQUESTED;
4774             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4775             SendToICS(str);
4776         }
4777         forwardMostMove = backwardMostMove = currentMove = moveNum;
4778     }
4779
4780     /* Update the clocks */
4781     if (strchr(elapsed_time, '.')) {
4782       /* Time is in ms */
4783       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4784       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4785     } else {
4786       /* Time is in seconds */
4787       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4788       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4789     }
4790
4791
4792 #if ZIPPY
4793     if (appData.zippyPlay && newGame &&
4794         gameMode != IcsObserving && gameMode != IcsIdle &&
4795         gameMode != IcsExamining)
4796       ZippyFirstBoard(moveNum, basetime, increment);
4797 #endif
4798
4799     /* Put the move on the move list, first converting
4800        to canonical algebraic form. */
4801     if (moveNum > 0) {
4802   if (appData.debugMode) {
4803     int f = forwardMostMove;
4804     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4805             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4806             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4807     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4808     fprintf(debugFP, "moveNum = %d\n", moveNum);
4809     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4810     setbuf(debugFP, NULL);
4811   }
4812         if (moveNum <= backwardMostMove) {
4813             /* We don't know what the board looked like before
4814                this move.  Punt. */
4815           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4816             strcat(parseList[moveNum - 1], " ");
4817             strcat(parseList[moveNum - 1], elapsed_time);
4818             moveList[moveNum - 1][0] = NULLCHAR;
4819         } else if (strcmp(move_str, "none") == 0) {
4820             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4821             /* Again, we don't know what the board looked like;
4822                this is really the start of the game. */
4823             parseList[moveNum - 1][0] = NULLCHAR;
4824             moveList[moveNum - 1][0] = NULLCHAR;
4825             backwardMostMove = moveNum;
4826             startedFromSetupPosition = TRUE;
4827             fromX = fromY = toX = toY = -1;
4828         } else {
4829           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4830           //                 So we parse the long-algebraic move string in stead of the SAN move
4831           int valid; char buf[MSG_SIZ], *prom;
4832
4833           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4834                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4835           // str looks something like "Q/a1-a2"; kill the slash
4836           if(str[1] == '/')
4837             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4838           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4839           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4840                 strcat(buf, prom); // long move lacks promo specification!
4841           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4842                 if(appData.debugMode)
4843                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4844                 safeStrCpy(move_str, buf, MSG_SIZ);
4845           }
4846           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4847                                 &fromX, &fromY, &toX, &toY, &promoChar)
4848                || ParseOneMove(buf, moveNum - 1, &moveType,
4849                                 &fromX, &fromY, &toX, &toY, &promoChar);
4850           // end of long SAN patch
4851           if (valid) {
4852             (void) CoordsToAlgebraic(boards[moveNum - 1],
4853                                      PosFlags(moveNum - 1),
4854                                      fromY, fromX, toY, toX, promoChar,
4855                                      parseList[moveNum-1]);
4856             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4857               case MT_NONE:
4858               case MT_STALEMATE:
4859               default:
4860                 break;
4861               case MT_CHECK:
4862                 if(!IS_SHOGI(gameInfo.variant))
4863                     strcat(parseList[moveNum - 1], "+");
4864                 break;
4865               case MT_CHECKMATE:
4866               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4867                 strcat(parseList[moveNum - 1], "#");
4868                 break;
4869             }
4870             strcat(parseList[moveNum - 1], " ");
4871             strcat(parseList[moveNum - 1], elapsed_time);
4872             /* currentMoveString is set as a side-effect of ParseOneMove */
4873             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4874             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4875             strcat(moveList[moveNum - 1], "\n");
4876
4877             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4878                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4879               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4880                 ChessSquare old, new = boards[moveNum][k][j];
4881                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4882                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4883                   if(old == new) continue;
4884                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4885                   else if(new == WhiteWazir || new == BlackWazir) {
4886                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4887                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4888                       else boards[moveNum][k][j] = old; // preserve type of Gold
4889                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4890                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4891               }
4892           } else {
4893             /* Move from ICS was illegal!?  Punt. */
4894             if (appData.debugMode) {
4895               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4896               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4897             }
4898             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4899             strcat(parseList[moveNum - 1], " ");
4900             strcat(parseList[moveNum - 1], elapsed_time);
4901             moveList[moveNum - 1][0] = NULLCHAR;
4902             fromX = fromY = toX = toY = -1;
4903           }
4904         }
4905   if (appData.debugMode) {
4906     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4907     setbuf(debugFP, NULL);
4908   }
4909
4910 #if ZIPPY
4911         /* Send move to chess program (BEFORE animating it). */
4912         if (appData.zippyPlay && !newGame && newMove &&
4913            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4914
4915             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4916                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4917                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4918                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4919                             move_str);
4920                     DisplayError(str, 0);
4921                 } else {
4922                     if (first.sendTime) {
4923                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4924                     }
4925                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4926                     if (firstMove && !bookHit) {
4927                         firstMove = FALSE;
4928                         if (first.useColors) {
4929                           SendToProgram(gameMode == IcsPlayingWhite ?
4930                                         "white\ngo\n" :
4931                                         "black\ngo\n", &first);
4932                         } else {
4933                           SendToProgram("go\n", &first);
4934                         }
4935                         first.maybeThinking = TRUE;
4936                     }
4937                 }
4938             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4939               if (moveList[moveNum - 1][0] == NULLCHAR) {
4940                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4941                 DisplayError(str, 0);
4942               } else {
4943                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4944                 SendMoveToProgram(moveNum - 1, &first);
4945               }
4946             }
4947         }
4948 #endif
4949     }
4950
4951     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4952         /* If move comes from a remote source, animate it.  If it
4953            isn't remote, it will have already been animated. */
4954         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4955             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4956         }
4957         if (!pausing && appData.highlightLastMove) {
4958             SetHighlights(fromX, fromY, toX, toY);
4959         }
4960     }
4961
4962     /* Start the clocks */
4963     whiteFlag = blackFlag = FALSE;
4964     appData.clockMode = !(basetime == 0 && increment == 0);
4965     if (ticking == 0) {
4966       ics_clock_paused = TRUE;
4967       StopClocks();
4968     } else if (ticking == 1) {
4969       ics_clock_paused = FALSE;
4970     }
4971     if (gameMode == IcsIdle ||
4972         relation == RELATION_OBSERVING_STATIC ||
4973         relation == RELATION_EXAMINING ||
4974         ics_clock_paused)
4975       DisplayBothClocks();
4976     else
4977       StartClocks();
4978
4979     /* Display opponents and material strengths */
4980     if (gameInfo.variant != VariantBughouse &&
4981         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4982         if (tinyLayout || smallLayout) {
4983             if(gameInfo.variant == VariantNormal)
4984               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4985                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4986                     basetime, increment);
4987             else
4988               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4989                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4990                     basetime, increment, (int) gameInfo.variant);
4991         } else {
4992             if(gameInfo.variant == VariantNormal)
4993               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4994                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4995                     basetime, increment);
4996             else
4997               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4998                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4999                     basetime, increment, VariantName(gameInfo.variant));
5000         }
5001         DisplayTitle(str);
5002   if (appData.debugMode) {
5003     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5004   }
5005     }
5006
5007
5008     /* Display the board */
5009     if (!pausing && !appData.noGUI) {
5010
5011       if (appData.premove)
5012           if (!gotPremove ||
5013              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5014              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5015               ClearPremoveHighlights();
5016
5017       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5018         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5019       DrawPosition(j, boards[currentMove]);
5020
5021       DisplayMove(moveNum - 1);
5022       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5023             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5024               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5025         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5026       }
5027     }
5028
5029     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5030 #if ZIPPY
5031     if(bookHit) { // [HGM] book: simulate book reply
5032         static char bookMove[MSG_SIZ]; // a bit generous?
5033
5034         programStats.nodes = programStats.depth = programStats.time =
5035         programStats.score = programStats.got_only_move = 0;
5036         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5037
5038         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5039         strcat(bookMove, bookHit);
5040         HandleMachineMove(bookMove, &first);
5041     }
5042 #endif
5043 }
5044
5045 void
5046 GetMoveListEvent ()
5047 {
5048     char buf[MSG_SIZ];
5049     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5050         ics_getting_history = H_REQUESTED;
5051         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5052         SendToICS(buf);
5053     }
5054 }
5055
5056 void
5057 SendToBoth (char *msg)
5058 {   // to make it easy to keep two engines in step in dual analysis
5059     SendToProgram(msg, &first);
5060     if(second.analyzing) SendToProgram(msg, &second);
5061 }
5062
5063 void
5064 AnalysisPeriodicEvent (int force)
5065 {
5066     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5067          && !force) || !appData.periodicUpdates)
5068       return;
5069
5070     /* Send . command to Crafty to collect stats */
5071     SendToBoth(".\n");
5072
5073     /* Don't send another until we get a response (this makes
5074        us stop sending to old Crafty's which don't understand
5075        the "." command (sending illegal cmds resets node count & time,
5076        which looks bad)) */
5077     programStats.ok_to_send = 0;
5078 }
5079
5080 void
5081 ics_update_width (int new_width)
5082 {
5083         ics_printf("set width %d\n", new_width);
5084 }
5085
5086 void
5087 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5088 {
5089     char buf[MSG_SIZ];
5090
5091     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5092         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5093             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5094             SendToProgram(buf, cps);
5095             return;
5096         }
5097         // null move in variant where engine does not understand it (for analysis purposes)
5098         SendBoard(cps, moveNum + 1); // send position after move in stead.
5099         return;
5100     }
5101     if (cps->useUsermove) {
5102       SendToProgram("usermove ", cps);
5103     }
5104     if (cps->useSAN) {
5105       char *space;
5106       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5107         int len = space - parseList[moveNum];
5108         memcpy(buf, parseList[moveNum], len);
5109         buf[len++] = '\n';
5110         buf[len] = NULLCHAR;
5111       } else {
5112         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5113       }
5114       SendToProgram(buf, cps);
5115     } else {
5116       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5117         AlphaRank(moveList[moveNum], 4);
5118         SendToProgram(moveList[moveNum], cps);
5119         AlphaRank(moveList[moveNum], 4); // and back
5120       } else
5121       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5122        * the engine. It would be nice to have a better way to identify castle
5123        * moves here. */
5124       if(appData.fischerCastling && cps->useOOCastle) {
5125         int fromX = moveList[moveNum][0] - AAA;
5126         int fromY = moveList[moveNum][1] - ONE;
5127         int toX = moveList[moveNum][2] - AAA;
5128         int toY = moveList[moveNum][3] - ONE;
5129         if((boards[moveNum][fromY][fromX] == WhiteKing
5130             && boards[moveNum][toY][toX] == WhiteRook)
5131            || (boards[moveNum][fromY][fromX] == BlackKing
5132                && boards[moveNum][toY][toX] == BlackRook)) {
5133           if(toX > fromX) SendToProgram("O-O\n", cps);
5134           else SendToProgram("O-O-O\n", cps);
5135         }
5136         else SendToProgram(moveList[moveNum], cps);
5137       } else
5138       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5139           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5140                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5141                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5142                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5143           SendToProgram(buf, cps);
5144       } else
5145       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5146         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5147           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5148           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5149                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5150         } else
5151           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5152                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5153         SendToProgram(buf, cps);
5154       }
5155       else SendToProgram(moveList[moveNum], cps);
5156       /* End of additions by Tord */
5157     }
5158
5159     /* [HGM] setting up the opening has brought engine in force mode! */
5160     /*       Send 'go' if we are in a mode where machine should play. */
5161     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5162         (gameMode == TwoMachinesPlay   ||
5163 #if ZIPPY
5164          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5165 #endif
5166          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5167         SendToProgram("go\n", cps);
5168   if (appData.debugMode) {
5169     fprintf(debugFP, "(extra)\n");
5170   }
5171     }
5172     setboardSpoiledMachineBlack = 0;
5173 }
5174
5175 void
5176 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5177 {
5178     char user_move[MSG_SIZ];
5179     char suffix[4];
5180
5181     if(gameInfo.variant == VariantSChess && promoChar) {
5182         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5183         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5184     } else suffix[0] = NULLCHAR;
5185
5186     switch (moveType) {
5187       default:
5188         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5189                 (int)moveType, fromX, fromY, toX, toY);
5190         DisplayError(user_move + strlen("say "), 0);
5191         break;
5192       case WhiteKingSideCastle:
5193       case BlackKingSideCastle:
5194       case WhiteQueenSideCastleWild:
5195       case BlackQueenSideCastleWild:
5196       /* PUSH Fabien */
5197       case WhiteHSideCastleFR:
5198       case BlackHSideCastleFR:
5199       /* POP Fabien */
5200         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5201         break;
5202       case WhiteQueenSideCastle:
5203       case BlackQueenSideCastle:
5204       case WhiteKingSideCastleWild:
5205       case BlackKingSideCastleWild:
5206       /* PUSH Fabien */
5207       case WhiteASideCastleFR:
5208       case BlackASideCastleFR:
5209       /* POP Fabien */
5210         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5211         break;
5212       case WhiteNonPromotion:
5213       case BlackNonPromotion:
5214         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5215         break;
5216       case WhitePromotion:
5217       case BlackPromotion:
5218         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5219            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5220           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5221                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5222                 PieceToChar(WhiteFerz));
5223         else if(gameInfo.variant == VariantGreat)
5224           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5225                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5226                 PieceToChar(WhiteMan));
5227         else
5228           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5229                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5230                 promoChar);
5231         break;
5232       case WhiteDrop:
5233       case BlackDrop:
5234       drop:
5235         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5236                  ToUpper(PieceToChar((ChessSquare) fromX)),
5237                  AAA + toX, ONE + toY);
5238         break;
5239       case IllegalMove:  /* could be a variant we don't quite understand */
5240         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5241       case NormalMove:
5242       case WhiteCapturesEnPassant:
5243       case BlackCapturesEnPassant:
5244         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5245                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5246         break;
5247     }
5248     SendToICS(user_move);
5249     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5250         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5251 }
5252
5253 void
5254 UploadGameEvent ()
5255 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5256     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5257     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5258     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5259       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5260       return;
5261     }
5262     if(gameMode != IcsExamining) { // is this ever not the case?
5263         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5264
5265         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5266           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5267         } else { // on FICS we must first go to general examine mode
5268           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5269         }
5270         if(gameInfo.variant != VariantNormal) {
5271             // try figure out wild number, as xboard names are not always valid on ICS
5272             for(i=1; i<=36; i++) {
5273               snprintf(buf, MSG_SIZ, "wild/%d", i);
5274                 if(StringToVariant(buf) == gameInfo.variant) break;
5275             }
5276             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5277             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5278             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5279         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5280         SendToICS(ics_prefix);
5281         SendToICS(buf);
5282         if(startedFromSetupPosition || backwardMostMove != 0) {
5283           fen = PositionToFEN(backwardMostMove, NULL, 1);
5284           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5285             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5286             SendToICS(buf);
5287           } else { // FICS: everything has to set by separate bsetup commands
5288             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5289             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5290             SendToICS(buf);
5291             if(!WhiteOnMove(backwardMostMove)) {
5292                 SendToICS("bsetup tomove black\n");
5293             }
5294             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5295             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5296             SendToICS(buf);
5297             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5298             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5299             SendToICS(buf);
5300             i = boards[backwardMostMove][EP_STATUS];
5301             if(i >= 0) { // set e.p.
5302               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5303                 SendToICS(buf);
5304             }
5305             bsetup++;
5306           }
5307         }
5308       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5309             SendToICS("bsetup done\n"); // switch to normal examining.
5310     }
5311     for(i = backwardMostMove; i<last; i++) {
5312         char buf[20];
5313         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5314         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5315             int len = strlen(moveList[i]);
5316             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5317             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5318         }
5319         SendToICS(buf);
5320     }
5321     SendToICS(ics_prefix);
5322     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5323 }
5324
5325 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5326
5327 void
5328 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5329 {
5330     if (rf == DROP_RANK) {
5331       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5332       sprintf(move, "%c@%c%c\n",
5333                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5334     } else {
5335         if (promoChar == 'x' || promoChar == NULLCHAR) {
5336           sprintf(move, "%c%c%c%c\n",
5337                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5338           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5339         } else {
5340             sprintf(move, "%c%c%c%c%c\n",
5341                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5342         }
5343     }
5344 }
5345
5346 void
5347 ProcessICSInitScript (FILE *f)
5348 {
5349     char buf[MSG_SIZ];
5350
5351     while (fgets(buf, MSG_SIZ, f)) {
5352         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5353     }
5354
5355     fclose(f);
5356 }
5357
5358
5359 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5360 int dragging;
5361 static ClickType lastClickType;
5362
5363 int
5364 Partner (ChessSquare *p)
5365 { // change piece into promotion partner if one shogi-promotes to the other
5366   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5367   ChessSquare partner;
5368   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5369   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5370   *p = partner;
5371   return 1;
5372 }
5373
5374 void
5375 Sweep (int step)
5376 {
5377     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5378     static int toggleFlag;
5379     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5380     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5381     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5382     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5383     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5384     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5385     do {
5386         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5387         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5388         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5389         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5390         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5391         if(!step) step = -1;
5392     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5393             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5394             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5395             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5396     if(toX >= 0) {
5397         int victim = boards[currentMove][toY][toX];
5398         boards[currentMove][toY][toX] = promoSweep;
5399         DrawPosition(FALSE, boards[currentMove]);
5400         boards[currentMove][toY][toX] = victim;
5401     } else
5402     ChangeDragPiece(promoSweep);
5403 }
5404
5405 int
5406 PromoScroll (int x, int y)
5407 {
5408   int step = 0;
5409
5410   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5411   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5412   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5413   if(!step) return FALSE;
5414   lastX = x; lastY = y;
5415   if((promoSweep < BlackPawn) == flipView) step = -step;
5416   if(step > 0) selectFlag = 1;
5417   if(!selectFlag) Sweep(step);
5418   return FALSE;
5419 }
5420
5421 void
5422 NextPiece (int step)
5423 {
5424     ChessSquare piece = boards[currentMove][toY][toX];
5425     do {
5426         pieceSweep -= step;
5427         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5428         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5429         if(!step) step = -1;
5430     } while(PieceToChar(pieceSweep) == '.');
5431     boards[currentMove][toY][toX] = pieceSweep;
5432     DrawPosition(FALSE, boards[currentMove]);
5433     boards[currentMove][toY][toX] = piece;
5434 }
5435 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5436 void
5437 AlphaRank (char *move, int n)
5438 {
5439 //    char *p = move, c; int x, y;
5440
5441     if (appData.debugMode) {
5442         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5443     }
5444
5445     if(move[1]=='*' &&
5446        move[2]>='0' && move[2]<='9' &&
5447        move[3]>='a' && move[3]<='x'    ) {
5448         move[1] = '@';
5449         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5450         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5451     } else
5452     if(move[0]>='0' && move[0]<='9' &&
5453        move[1]>='a' && move[1]<='x' &&
5454        move[2]>='0' && move[2]<='9' &&
5455        move[3]>='a' && move[3]<='x'    ) {
5456         /* input move, Shogi -> normal */
5457         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5458         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5459         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5460         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5461     } else
5462     if(move[1]=='@' &&
5463        move[3]>='0' && move[3]<='9' &&
5464        move[2]>='a' && move[2]<='x'    ) {
5465         move[1] = '*';
5466         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5467         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5468     } else
5469     if(
5470        move[0]>='a' && move[0]<='x' &&
5471        move[3]>='0' && move[3]<='9' &&
5472        move[2]>='a' && move[2]<='x'    ) {
5473          /* output move, normal -> Shogi */
5474         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5475         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5476         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5477         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5478         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5479     }
5480     if (appData.debugMode) {
5481         fprintf(debugFP, "   out = '%s'\n", move);
5482     }
5483 }
5484
5485 char yy_textstr[8000];
5486
5487 /* Parser for moves from gnuchess, ICS, or user typein box */
5488 Boolean
5489 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5490 {
5491     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5492
5493     switch (*moveType) {
5494       case WhitePromotion:
5495       case BlackPromotion:
5496       case WhiteNonPromotion:
5497       case BlackNonPromotion:
5498       case NormalMove:
5499       case FirstLeg:
5500       case WhiteCapturesEnPassant:
5501       case BlackCapturesEnPassant:
5502       case WhiteKingSideCastle:
5503       case WhiteQueenSideCastle:
5504       case BlackKingSideCastle:
5505       case BlackQueenSideCastle:
5506       case WhiteKingSideCastleWild:
5507       case WhiteQueenSideCastleWild:
5508       case BlackKingSideCastleWild:
5509       case BlackQueenSideCastleWild:
5510       /* Code added by Tord: */
5511       case WhiteHSideCastleFR:
5512       case WhiteASideCastleFR:
5513       case BlackHSideCastleFR:
5514       case BlackASideCastleFR:
5515       /* End of code added by Tord */
5516       case IllegalMove:         /* bug or odd chess variant */
5517         *fromX = currentMoveString[0] - AAA;
5518         *fromY = currentMoveString[1] - ONE;
5519         *toX = currentMoveString[2] - AAA;
5520         *toY = currentMoveString[3] - ONE;
5521         *promoChar = currentMoveString[4];
5522         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5523             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5524     if (appData.debugMode) {
5525         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5526     }
5527             *fromX = *fromY = *toX = *toY = 0;
5528             return FALSE;
5529         }
5530         if (appData.testLegality) {
5531           return (*moveType != IllegalMove);
5532         } else {
5533           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5534                          // [HGM] lion: if this is a double move we are less critical
5535                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5536         }
5537
5538       case WhiteDrop:
5539       case BlackDrop:
5540         *fromX = *moveType == WhiteDrop ?
5541           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5542           (int) CharToPiece(ToLower(currentMoveString[0]));
5543         *fromY = DROP_RANK;
5544         *toX = currentMoveString[2] - AAA;
5545         *toY = currentMoveString[3] - ONE;
5546         *promoChar = NULLCHAR;
5547         return TRUE;
5548
5549       case AmbiguousMove:
5550       case ImpossibleMove:
5551       case EndOfFile:
5552       case ElapsedTime:
5553       case Comment:
5554       case PGNTag:
5555       case NAG:
5556       case WhiteWins:
5557       case BlackWins:
5558       case GameIsDrawn:
5559       default:
5560     if (appData.debugMode) {
5561         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5562     }
5563         /* bug? */
5564         *fromX = *fromY = *toX = *toY = 0;
5565         *promoChar = NULLCHAR;
5566         return FALSE;
5567     }
5568 }
5569
5570 Boolean pushed = FALSE;
5571 char *lastParseAttempt;
5572
5573 void
5574 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5575 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5576   int fromX, fromY, toX, toY; char promoChar;
5577   ChessMove moveType;
5578   Boolean valid;
5579   int nr = 0;
5580
5581   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5582   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5583     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5584     pushed = TRUE;
5585   }
5586   endPV = forwardMostMove;
5587   do {
5588     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5589     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5590     lastParseAttempt = pv;
5591     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5592     if(!valid && nr == 0 &&
5593        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5594         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5595         // Hande case where played move is different from leading PV move
5596         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5597         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5598         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5599         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5600           endPV += 2; // if position different, keep this
5601           moveList[endPV-1][0] = fromX + AAA;
5602           moveList[endPV-1][1] = fromY + ONE;
5603           moveList[endPV-1][2] = toX + AAA;
5604           moveList[endPV-1][3] = toY + ONE;
5605           parseList[endPV-1][0] = NULLCHAR;
5606           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5607         }
5608       }
5609     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5610     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5611     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5612     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5613         valid++; // allow comments in PV
5614         continue;
5615     }
5616     nr++;
5617     if(endPV+1 > framePtr) break; // no space, truncate
5618     if(!valid) break;
5619     endPV++;
5620     CopyBoard(boards[endPV], boards[endPV-1]);
5621     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5622     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5623     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5624     CoordsToAlgebraic(boards[endPV - 1],
5625                              PosFlags(endPV - 1),
5626                              fromY, fromX, toY, toX, promoChar,
5627                              parseList[endPV - 1]);
5628   } while(valid);
5629   if(atEnd == 2) return; // used hidden, for PV conversion
5630   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5631   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5632   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5633                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5634   DrawPosition(TRUE, boards[currentMove]);
5635 }
5636
5637 int
5638 MultiPV (ChessProgramState *cps)
5639 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5640         int i;
5641         for(i=0; i<cps->nrOptions; i++)
5642             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5643                 return i;
5644         return -1;
5645 }
5646
5647 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5648
5649 Boolean
5650 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5651 {
5652         int startPV, multi, lineStart, origIndex = index;
5653         char *p, buf2[MSG_SIZ];
5654         ChessProgramState *cps = (pane ? &second : &first);
5655
5656         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5657         lastX = x; lastY = y;
5658         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5659         lineStart = startPV = index;
5660         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5661         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5662         index = startPV;
5663         do{ while(buf[index] && buf[index] != '\n') index++;
5664         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5665         buf[index] = 0;
5666         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5667                 int n = cps->option[multi].value;
5668                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5669                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5670                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5671                 cps->option[multi].value = n;
5672                 *start = *end = 0;
5673                 return FALSE;
5674         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5675                 ExcludeClick(origIndex - lineStart);
5676                 return FALSE;
5677         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5678                 Collapse(origIndex - lineStart);
5679                 return FALSE;
5680         }
5681         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5682         *start = startPV; *end = index-1;
5683         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5684         return TRUE;
5685 }
5686
5687 char *
5688 PvToSAN (char *pv)
5689 {
5690         static char buf[10*MSG_SIZ];
5691         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5692         *buf = NULLCHAR;
5693         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5694         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5695         for(i = forwardMostMove; i<endPV; i++){
5696             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5697             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5698             k += strlen(buf+k);
5699         }
5700         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5701         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5702         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5703         endPV = savedEnd;
5704         return buf;
5705 }
5706
5707 Boolean
5708 LoadPV (int x, int y)
5709 { // called on right mouse click to load PV
5710   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5711   lastX = x; lastY = y;
5712   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5713   extendGame = FALSE;
5714   return TRUE;
5715 }
5716
5717 void
5718 UnLoadPV ()
5719 {
5720   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5721   if(endPV < 0) return;
5722   if(appData.autoCopyPV) CopyFENToClipboard();
5723   endPV = -1;
5724   if(extendGame && currentMove > forwardMostMove) {
5725         Boolean saveAnimate = appData.animate;
5726         if(pushed) {
5727             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5728                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5729             } else storedGames--; // abandon shelved tail of original game
5730         }
5731         pushed = FALSE;
5732         forwardMostMove = currentMove;
5733         currentMove = oldFMM;
5734         appData.animate = FALSE;
5735         ToNrEvent(forwardMostMove);
5736         appData.animate = saveAnimate;
5737   }
5738   currentMove = forwardMostMove;
5739   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5740   ClearPremoveHighlights();
5741   DrawPosition(TRUE, boards[currentMove]);
5742 }
5743
5744 void
5745 MovePV (int x, int y, int h)
5746 { // step through PV based on mouse coordinates (called on mouse move)
5747   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5748
5749   // we must somehow check if right button is still down (might be released off board!)
5750   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5751   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5752   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5753   if(!step) return;
5754   lastX = x; lastY = y;
5755
5756   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5757   if(endPV < 0) return;
5758   if(y < margin) step = 1; else
5759   if(y > h - margin) step = -1;
5760   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5761   currentMove += step;
5762   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5763   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5764                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5765   DrawPosition(FALSE, boards[currentMove]);
5766 }
5767
5768
5769 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5770 // All positions will have equal probability, but the current method will not provide a unique
5771 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5772 #define DARK 1
5773 #define LITE 2
5774 #define ANY 3
5775
5776 int squaresLeft[4];
5777 int piecesLeft[(int)BlackPawn];
5778 int seed, nrOfShuffles;
5779
5780 void
5781 GetPositionNumber ()
5782 {       // sets global variable seed
5783         int i;
5784
5785         seed = appData.defaultFrcPosition;
5786         if(seed < 0) { // randomize based on time for negative FRC position numbers
5787                 for(i=0; i<50; i++) seed += random();
5788                 seed = random() ^ random() >> 8 ^ random() << 8;
5789                 if(seed<0) seed = -seed;
5790         }
5791 }
5792
5793 int
5794 put (Board board, int pieceType, int rank, int n, int shade)
5795 // put the piece on the (n-1)-th empty squares of the given shade
5796 {
5797         int i;
5798
5799         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5800                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5801                         board[rank][i] = (ChessSquare) pieceType;
5802                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5803                         squaresLeft[ANY]--;
5804                         piecesLeft[pieceType]--;
5805                         return i;
5806                 }
5807         }
5808         return -1;
5809 }
5810
5811
5812 void
5813 AddOnePiece (Board board, int pieceType, int rank, int shade)
5814 // calculate where the next piece goes, (any empty square), and put it there
5815 {
5816         int i;
5817
5818         i = seed % squaresLeft[shade];
5819         nrOfShuffles *= squaresLeft[shade];
5820         seed /= squaresLeft[shade];
5821         put(board, pieceType, rank, i, shade);
5822 }
5823
5824 void
5825 AddTwoPieces (Board board, int pieceType, int rank)
5826 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5827 {
5828         int i, n=squaresLeft[ANY], j=n-1, k;
5829
5830         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5831         i = seed % k;  // pick one
5832         nrOfShuffles *= k;
5833         seed /= k;
5834         while(i >= j) i -= j--;
5835         j = n - 1 - j; i += j;
5836         put(board, pieceType, rank, j, ANY);
5837         put(board, pieceType, rank, i, ANY);
5838 }
5839
5840 void
5841 SetUpShuffle (Board board, int number)
5842 {
5843         int i, p, first=1;
5844
5845         GetPositionNumber(); nrOfShuffles = 1;
5846
5847         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5848         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5849         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5850
5851         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5852
5853         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5854             p = (int) board[0][i];
5855             if(p < (int) BlackPawn) piecesLeft[p] ++;
5856             board[0][i] = EmptySquare;
5857         }
5858
5859         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5860             // shuffles restricted to allow normal castling put KRR first
5861             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5862                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5863             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5864                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5865             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5866                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5867             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5868                 put(board, WhiteRook, 0, 0, ANY);
5869             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5870         }
5871
5872         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5873             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5874             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5875                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5876                 while(piecesLeft[p] >= 2) {
5877                     AddOnePiece(board, p, 0, LITE);
5878                     AddOnePiece(board, p, 0, DARK);
5879                 }
5880                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5881             }
5882
5883         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5884             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5885             // but we leave King and Rooks for last, to possibly obey FRC restriction
5886             if(p == (int)WhiteRook) continue;
5887             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5888             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5889         }
5890
5891         // now everything is placed, except perhaps King (Unicorn) and Rooks
5892
5893         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5894             // Last King gets castling rights
5895             while(piecesLeft[(int)WhiteUnicorn]) {
5896                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5897                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5898             }
5899
5900             while(piecesLeft[(int)WhiteKing]) {
5901                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5902                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5903             }
5904
5905
5906         } else {
5907             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5908             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5909         }
5910
5911         // Only Rooks can be left; simply place them all
5912         while(piecesLeft[(int)WhiteRook]) {
5913                 i = put(board, WhiteRook, 0, 0, ANY);
5914                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5915                         if(first) {
5916                                 first=0;
5917                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5918                         }
5919                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5920                 }
5921         }
5922         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5923             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5924         }
5925
5926         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5927 }
5928
5929 int
5930 SetCharTable (char *table, const char * map)
5931 /* [HGM] moved here from winboard.c because of its general usefulness */
5932 /*       Basically a safe strcpy that uses the last character as King */
5933 {
5934     int result = FALSE; int NrPieces;
5935
5936     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5937                     && NrPieces >= 12 && !(NrPieces&1)) {
5938         int i; /* [HGM] Accept even length from 12 to 34 */
5939
5940         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5941         for( i=0; i<NrPieces/2-1; i++ ) {
5942             table[i] = map[i];
5943             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5944         }
5945         table[(int) WhiteKing]  = map[NrPieces/2-1];
5946         table[(int) BlackKing]  = map[NrPieces-1];
5947
5948         result = TRUE;
5949     }
5950
5951     return result;
5952 }
5953
5954 void
5955 Prelude (Board board)
5956 {       // [HGM] superchess: random selection of exo-pieces
5957         int i, j, k; ChessSquare p;
5958         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5959
5960         GetPositionNumber(); // use FRC position number
5961
5962         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5963             SetCharTable(pieceToChar, appData.pieceToCharTable);
5964             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5965                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5966         }
5967
5968         j = seed%4;                 seed /= 4;
5969         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5970         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5971         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5972         j = seed%3 + (seed%3 >= j); seed /= 3;
5973         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5974         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5975         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5976         j = seed%3;                 seed /= 3;
5977         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5978         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5979         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5980         j = seed%2 + (seed%2 >= j); seed /= 2;
5981         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5982         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5983         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5984         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5985         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5986         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5987         put(board, exoPieces[0],    0, 0, ANY);
5988         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5989 }
5990
5991 void
5992 InitPosition (int redraw)
5993 {
5994     ChessSquare (* pieces)[BOARD_FILES];
5995     int i, j, pawnRow=1, pieceRows=1, overrule,
5996     oldx = gameInfo.boardWidth,
5997     oldy = gameInfo.boardHeight,
5998     oldh = gameInfo.holdingsWidth;
5999     static int oldv;
6000
6001     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6002
6003     /* [AS] Initialize pv info list [HGM] and game status */
6004     {
6005         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6006             pvInfoList[i].depth = 0;
6007             boards[i][EP_STATUS] = EP_NONE;
6008             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6009         }
6010
6011         initialRulePlies = 0; /* 50-move counter start */
6012
6013         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6014         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6015     }
6016
6017
6018     /* [HGM] logic here is completely changed. In stead of full positions */
6019     /* the initialized data only consist of the two backranks. The switch */
6020     /* selects which one we will use, which is than copied to the Board   */
6021     /* initialPosition, which for the rest is initialized by Pawns and    */
6022     /* empty squares. This initial position is then copied to boards[0],  */
6023     /* possibly after shuffling, so that it remains available.            */
6024
6025     gameInfo.holdingsWidth = 0; /* default board sizes */
6026     gameInfo.boardWidth    = 8;
6027     gameInfo.boardHeight   = 8;
6028     gameInfo.holdingsSize  = 0;
6029     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6030     for(i=0; i<BOARD_FILES-2; i++)
6031       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6032     initialPosition[EP_STATUS] = EP_NONE;
6033     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6034     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6035          SetCharTable(pieceNickName, appData.pieceNickNames);
6036     else SetCharTable(pieceNickName, "............");
6037     pieces = FIDEArray;
6038
6039     switch (gameInfo.variant) {
6040     case VariantFischeRandom:
6041       shuffleOpenings = TRUE;
6042       appData.fischerCastling = TRUE;
6043     default:
6044       break;
6045     case VariantShatranj:
6046       pieces = ShatranjArray;
6047       nrCastlingRights = 0;
6048       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6049       break;
6050     case VariantMakruk:
6051       pieces = makrukArray;
6052       nrCastlingRights = 0;
6053       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6054       break;
6055     case VariantASEAN:
6056       pieces = aseanArray;
6057       nrCastlingRights = 0;
6058       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6059       break;
6060     case VariantTwoKings:
6061       pieces = twoKingsArray;
6062       break;
6063     case VariantGrand:
6064       pieces = GrandArray;
6065       nrCastlingRights = 0;
6066       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6067       gameInfo.boardWidth = 10;
6068       gameInfo.boardHeight = 10;
6069       gameInfo.holdingsSize = 7;
6070       break;
6071     case VariantCapaRandom:
6072       shuffleOpenings = TRUE;
6073       appData.fischerCastling = TRUE;
6074     case VariantCapablanca:
6075       pieces = CapablancaArray;
6076       gameInfo.boardWidth = 10;
6077       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6078       break;
6079     case VariantGothic:
6080       pieces = GothicArray;
6081       gameInfo.boardWidth = 10;
6082       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6083       break;
6084     case VariantSChess:
6085       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6086       gameInfo.holdingsSize = 7;
6087       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6088       break;
6089     case VariantJanus:
6090       pieces = JanusArray;
6091       gameInfo.boardWidth = 10;
6092       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6093       nrCastlingRights = 6;
6094         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6095         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6096         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6097         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6098         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6099         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6100       break;
6101     case VariantFalcon:
6102       pieces = FalconArray;
6103       gameInfo.boardWidth = 10;
6104       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6105       break;
6106     case VariantXiangqi:
6107       pieces = XiangqiArray;
6108       gameInfo.boardWidth  = 9;
6109       gameInfo.boardHeight = 10;
6110       nrCastlingRights = 0;
6111       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6112       break;
6113     case VariantShogi:
6114       pieces = ShogiArray;
6115       gameInfo.boardWidth  = 9;
6116       gameInfo.boardHeight = 9;
6117       gameInfo.holdingsSize = 7;
6118       nrCastlingRights = 0;
6119       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6120       break;
6121     case VariantChu:
6122       pieces = ChuArray; pieceRows = 3;
6123       gameInfo.boardWidth  = 12;
6124       gameInfo.boardHeight = 12;
6125       nrCastlingRights = 0;
6126       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6127                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6128       break;
6129     case VariantCourier:
6130       pieces = CourierArray;
6131       gameInfo.boardWidth  = 12;
6132       nrCastlingRights = 0;
6133       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6134       break;
6135     case VariantKnightmate:
6136       pieces = KnightmateArray;
6137       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6138       break;
6139     case VariantSpartan:
6140       pieces = SpartanArray;
6141       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6142       break;
6143     case VariantLion:
6144       pieces = lionArray;
6145       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6146       break;
6147     case VariantChuChess:
6148       pieces = ChuChessArray;
6149       gameInfo.boardWidth = 10;
6150       gameInfo.boardHeight = 10;
6151       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6152       break;
6153     case VariantFairy:
6154       pieces = fairyArray;
6155       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6156       break;
6157     case VariantGreat:
6158       pieces = GreatArray;
6159       gameInfo.boardWidth = 10;
6160       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6161       gameInfo.holdingsSize = 8;
6162       break;
6163     case VariantSuper:
6164       pieces = FIDEArray;
6165       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6166       gameInfo.holdingsSize = 8;
6167       startedFromSetupPosition = TRUE;
6168       break;
6169     case VariantCrazyhouse:
6170     case VariantBughouse:
6171       pieces = FIDEArray;
6172       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6173       gameInfo.holdingsSize = 5;
6174       break;
6175     case VariantWildCastle:
6176       pieces = FIDEArray;
6177       /* !!?shuffle with kings guaranteed to be on d or e file */
6178       shuffleOpenings = 1;
6179       break;
6180     case VariantNoCastle:
6181       pieces = FIDEArray;
6182       nrCastlingRights = 0;
6183       /* !!?unconstrained back-rank shuffle */
6184       shuffleOpenings = 1;
6185       break;
6186     }
6187
6188     overrule = 0;
6189     if(appData.NrFiles >= 0) {
6190         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6191         gameInfo.boardWidth = appData.NrFiles;
6192     }
6193     if(appData.NrRanks >= 0) {
6194         gameInfo.boardHeight = appData.NrRanks;
6195     }
6196     if(appData.holdingsSize >= 0) {
6197         i = appData.holdingsSize;
6198         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6199         gameInfo.holdingsSize = i;
6200     }
6201     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6202     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6203         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6204
6205     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6206     if(pawnRow < 1) pawnRow = 1;
6207     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6208        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6209     if(gameInfo.variant == VariantChu) pawnRow = 3;
6210
6211     /* User pieceToChar list overrules defaults */
6212     if(appData.pieceToCharTable != NULL)
6213         SetCharTable(pieceToChar, appData.pieceToCharTable);
6214
6215     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6216
6217         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6218             s = (ChessSquare) 0; /* account holding counts in guard band */
6219         for( i=0; i<BOARD_HEIGHT; i++ )
6220             initialPosition[i][j] = s;
6221
6222         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6223         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6224         initialPosition[pawnRow][j] = WhitePawn;
6225         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6226         if(gameInfo.variant == VariantXiangqi) {
6227             if(j&1) {
6228                 initialPosition[pawnRow][j] =
6229                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6230                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6231                    initialPosition[2][j] = WhiteCannon;
6232                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6233                 }
6234             }
6235         }
6236         if(gameInfo.variant == VariantChu) {
6237              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6238                initialPosition[pawnRow+1][j] = WhiteCobra,
6239                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6240              for(i=1; i<pieceRows; i++) {
6241                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6242                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6243              }
6244         }
6245         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6246             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6247                initialPosition[0][j] = WhiteRook;
6248                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6249             }
6250         }
6251         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6252     }
6253     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6254     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6255
6256             j=BOARD_LEFT+1;
6257             initialPosition[1][j] = WhiteBishop;
6258             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6259             j=BOARD_RGHT-2;
6260             initialPosition[1][j] = WhiteRook;
6261             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6262     }
6263
6264     if( nrCastlingRights == -1) {
6265         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6266         /*       This sets default castling rights from none to normal corners   */
6267         /* Variants with other castling rights must set them themselves above    */
6268         nrCastlingRights = 6;
6269
6270         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6271         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6272         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6273         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6274         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6275         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6276      }
6277
6278      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6279      if(gameInfo.variant == VariantGreat) { // promotion commoners
6280         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6281         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6282         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6283         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6284      }
6285      if( gameInfo.variant == VariantSChess ) {
6286       initialPosition[1][0] = BlackMarshall;
6287       initialPosition[2][0] = BlackAngel;
6288       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6289       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6290       initialPosition[1][1] = initialPosition[2][1] =
6291       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6292      }
6293   if (appData.debugMode) {
6294     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6295   }
6296     if(shuffleOpenings) {
6297         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6298         startedFromSetupPosition = TRUE;
6299     }
6300     if(startedFromPositionFile) {
6301       /* [HGM] loadPos: use PositionFile for every new game */
6302       CopyBoard(initialPosition, filePosition);
6303       for(i=0; i<nrCastlingRights; i++)
6304           initialRights[i] = filePosition[CASTLING][i];
6305       startedFromSetupPosition = TRUE;
6306     }
6307
6308     CopyBoard(boards[0], initialPosition);
6309
6310     if(oldx != gameInfo.boardWidth ||
6311        oldy != gameInfo.boardHeight ||
6312        oldv != gameInfo.variant ||
6313        oldh != gameInfo.holdingsWidth
6314                                          )
6315             InitDrawingSizes(-2 ,0);
6316
6317     oldv = gameInfo.variant;
6318     if (redraw)
6319       DrawPosition(TRUE, boards[currentMove]);
6320 }
6321
6322 void
6323 SendBoard (ChessProgramState *cps, int moveNum)
6324 {
6325     char message[MSG_SIZ];
6326
6327     if (cps->useSetboard) {
6328       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6329       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6330       SendToProgram(message, cps);
6331       free(fen);
6332
6333     } else {
6334       ChessSquare *bp;
6335       int i, j, left=0, right=BOARD_WIDTH;
6336       /* Kludge to set black to move, avoiding the troublesome and now
6337        * deprecated "black" command.
6338        */
6339       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6340         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6341
6342       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6343
6344       SendToProgram("edit\n", cps);
6345       SendToProgram("#\n", cps);
6346       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6347         bp = &boards[moveNum][i][left];
6348         for (j = left; j < right; j++, bp++) {
6349           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6350           if ((int) *bp < (int) BlackPawn) {
6351             if(j == BOARD_RGHT+1)
6352                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6353             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6354             if(message[0] == '+' || message[0] == '~') {
6355               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6356                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6357                         AAA + j, ONE + i);
6358             }
6359             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6360                 message[1] = BOARD_RGHT   - 1 - j + '1';
6361                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6362             }
6363             SendToProgram(message, cps);
6364           }
6365         }
6366       }
6367
6368       SendToProgram("c\n", cps);
6369       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6370         bp = &boards[moveNum][i][left];
6371         for (j = left; j < right; j++, bp++) {
6372           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6373           if (((int) *bp != (int) EmptySquare)
6374               && ((int) *bp >= (int) BlackPawn)) {
6375             if(j == BOARD_LEFT-2)
6376                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6377             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6378                     AAA + j, ONE + i);
6379             if(message[0] == '+' || message[0] == '~') {
6380               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6381                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6382                         AAA + j, ONE + i);
6383             }
6384             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6385                 message[1] = BOARD_RGHT   - 1 - j + '1';
6386                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6387             }
6388             SendToProgram(message, cps);
6389           }
6390         }
6391       }
6392
6393       SendToProgram(".\n", cps);
6394     }
6395     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6396 }
6397
6398 char exclusionHeader[MSG_SIZ];
6399 int exCnt, excludePtr;
6400 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6401 static Exclusion excluTab[200];
6402 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6403
6404 static void
6405 WriteMap (int s)
6406 {
6407     int j;
6408     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6409     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6410 }
6411
6412 static void
6413 ClearMap ()
6414 {
6415     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6416     excludePtr = 24; exCnt = 0;
6417     WriteMap(0);
6418 }
6419
6420 static void
6421 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6422 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6423     char buf[2*MOVE_LEN], *p;
6424     Exclusion *e = excluTab;
6425     int i;
6426     for(i=0; i<exCnt; i++)
6427         if(e[i].ff == fromX && e[i].fr == fromY &&
6428            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6429     if(i == exCnt) { // was not in exclude list; add it
6430         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6431         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6432             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6433             return; // abort
6434         }
6435         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6436         excludePtr++; e[i].mark = excludePtr++;
6437         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6438         exCnt++;
6439     }
6440     exclusionHeader[e[i].mark] = state;
6441 }
6442
6443 static int
6444 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6445 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6446     char buf[MSG_SIZ];
6447     int j, k;
6448     ChessMove moveType;
6449     if((signed char)promoChar == -1) { // kludge to indicate best move
6450         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6451             return 1; // if unparsable, abort
6452     }
6453     // update exclusion map (resolving toggle by consulting existing state)
6454     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6455     j = k%8; k >>= 3;
6456     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6457     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6458          excludeMap[k] |=   1<<j;
6459     else excludeMap[k] &= ~(1<<j);
6460     // update header
6461     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6462     // inform engine
6463     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6464     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6465     SendToBoth(buf);
6466     return (state == '+');
6467 }
6468
6469 static void
6470 ExcludeClick (int index)
6471 {
6472     int i, j;
6473     Exclusion *e = excluTab;
6474     if(index < 25) { // none, best or tail clicked
6475         if(index < 13) { // none: include all
6476             WriteMap(0); // clear map
6477             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6478             SendToBoth("include all\n"); // and inform engine
6479         } else if(index > 18) { // tail
6480             if(exclusionHeader[19] == '-') { // tail was excluded
6481                 SendToBoth("include all\n");
6482                 WriteMap(0); // clear map completely
6483                 // now re-exclude selected moves
6484                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6485                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6486             } else { // tail was included or in mixed state
6487                 SendToBoth("exclude all\n");
6488                 WriteMap(0xFF); // fill map completely
6489                 // now re-include selected moves
6490                 j = 0; // count them
6491                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6492                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6493                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6494             }
6495         } else { // best
6496             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6497         }
6498     } else {
6499         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6500             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6501             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6502             break;
6503         }
6504     }
6505 }
6506
6507 ChessSquare
6508 DefaultPromoChoice (int white)
6509 {
6510     ChessSquare result;
6511     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6512        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6513         result = WhiteFerz; // no choice
6514     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6515         result= WhiteKing; // in Suicide Q is the last thing we want
6516     else if(gameInfo.variant == VariantSpartan)
6517         result = white ? WhiteQueen : WhiteAngel;
6518     else result = WhiteQueen;
6519     if(!white) result = WHITE_TO_BLACK result;
6520     return result;
6521 }
6522
6523 static int autoQueen; // [HGM] oneclick
6524
6525 int
6526 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6527 {
6528     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6529     /* [HGM] add Shogi promotions */
6530     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6531     ChessSquare piece, partner;
6532     ChessMove moveType;
6533     Boolean premove;
6534
6535     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6536     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6537
6538     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6539       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6540         return FALSE;
6541
6542     piece = boards[currentMove][fromY][fromX];
6543     if(gameInfo.variant == VariantChu) {
6544         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6545         promotionZoneSize = BOARD_HEIGHT/3;
6546         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6547     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6548         promotionZoneSize = BOARD_HEIGHT/3;
6549         highestPromotingPiece = (int)WhiteAlfil;
6550     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6551         promotionZoneSize = 3;
6552     }
6553
6554     // Treat Lance as Pawn when it is not representing Amazon or Lance
6555     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6556         if(piece == WhiteLance) piece = WhitePawn; else
6557         if(piece == BlackLance) piece = BlackPawn;
6558     }
6559
6560     // next weed out all moves that do not touch the promotion zone at all
6561     if((int)piece >= BlackPawn) {
6562         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6563              return FALSE;
6564         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6565         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6566     } else {
6567         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6568            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6569         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6570              return FALSE;
6571     }
6572
6573     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6574
6575     // weed out mandatory Shogi promotions
6576     if(gameInfo.variant == VariantShogi) {
6577         if(piece >= BlackPawn) {
6578             if(toY == 0 && piece == BlackPawn ||
6579                toY == 0 && piece == BlackQueen ||
6580                toY <= 1 && piece == BlackKnight) {
6581                 *promoChoice = '+';
6582                 return FALSE;
6583             }
6584         } else {
6585             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6586                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6587                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6588                 *promoChoice = '+';
6589                 return FALSE;
6590             }
6591         }
6592     }
6593
6594     // weed out obviously illegal Pawn moves
6595     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6596         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6597         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6598         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6599         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6600         // note we are not allowed to test for valid (non-)capture, due to premove
6601     }
6602
6603     // we either have a choice what to promote to, or (in Shogi) whether to promote
6604     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6605        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6606         ChessSquare p=BlackFerz;  // no choice
6607         while(p < EmptySquare) {  //but make sure we use piece that exists
6608             *promoChoice = PieceToChar(p++);
6609             if(*promoChoice != '.') break;
6610         }
6611         return FALSE;
6612     }
6613     // no sense asking what we must promote to if it is going to explode...
6614     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6615         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6616         return FALSE;
6617     }
6618     // give caller the default choice even if we will not make it
6619     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6620     partner = piece; // pieces can promote if the pieceToCharTable says so
6621     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6622     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6623     if(        sweepSelect && gameInfo.variant != VariantGreat
6624                            && gameInfo.variant != VariantGrand
6625                            && gameInfo.variant != VariantSuper) return FALSE;
6626     if(autoQueen) return FALSE; // predetermined
6627
6628     // suppress promotion popup on illegal moves that are not premoves
6629     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6630               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6631     if(appData.testLegality && !premove) {
6632         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6633                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6634         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6635         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6636             return FALSE;
6637     }
6638
6639     return TRUE;
6640 }
6641
6642 int
6643 InPalace (int row, int column)
6644 {   /* [HGM] for Xiangqi */
6645     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6646          column < (BOARD_WIDTH + 4)/2 &&
6647          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6648     return FALSE;
6649 }
6650
6651 int
6652 PieceForSquare (int x, int y)
6653 {
6654   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6655      return -1;
6656   else
6657      return boards[currentMove][y][x];
6658 }
6659
6660 int
6661 OKToStartUserMove (int x, int y)
6662 {
6663     ChessSquare from_piece;
6664     int white_piece;
6665
6666     if (matchMode) return FALSE;
6667     if (gameMode == EditPosition) return TRUE;
6668
6669     if (x >= 0 && y >= 0)
6670       from_piece = boards[currentMove][y][x];
6671     else
6672       from_piece = EmptySquare;
6673
6674     if (from_piece == EmptySquare) return FALSE;
6675
6676     white_piece = (int)from_piece >= (int)WhitePawn &&
6677       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6678
6679     switch (gameMode) {
6680       case AnalyzeFile:
6681       case TwoMachinesPlay:
6682       case EndOfGame:
6683         return FALSE;
6684
6685       case IcsObserving:
6686       case IcsIdle:
6687         return FALSE;
6688
6689       case MachinePlaysWhite:
6690       case IcsPlayingBlack:
6691         if (appData.zippyPlay) return FALSE;
6692         if (white_piece) {
6693             DisplayMoveError(_("You are playing Black"));
6694             return FALSE;
6695         }
6696         break;
6697
6698       case MachinePlaysBlack:
6699       case IcsPlayingWhite:
6700         if (appData.zippyPlay) return FALSE;
6701         if (!white_piece) {
6702             DisplayMoveError(_("You are playing White"));
6703             return FALSE;
6704         }
6705         break;
6706
6707       case PlayFromGameFile:
6708             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6709       case EditGame:
6710         if (!white_piece && WhiteOnMove(currentMove)) {
6711             DisplayMoveError(_("It is White's turn"));
6712             return FALSE;
6713         }
6714         if (white_piece && !WhiteOnMove(currentMove)) {
6715             DisplayMoveError(_("It is Black's turn"));
6716             return FALSE;
6717         }
6718         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6719             /* Editing correspondence game history */
6720             /* Could disallow this or prompt for confirmation */
6721             cmailOldMove = -1;
6722         }
6723         break;
6724
6725       case BeginningOfGame:
6726         if (appData.icsActive) return FALSE;
6727         if (!appData.noChessProgram) {
6728             if (!white_piece) {
6729                 DisplayMoveError(_("You are playing White"));
6730                 return FALSE;
6731             }
6732         }
6733         break;
6734
6735       case Training:
6736         if (!white_piece && WhiteOnMove(currentMove)) {
6737             DisplayMoveError(_("It is White's turn"));
6738             return FALSE;
6739         }
6740         if (white_piece && !WhiteOnMove(currentMove)) {
6741             DisplayMoveError(_("It is Black's turn"));
6742             return FALSE;
6743         }
6744         break;
6745
6746       default:
6747       case IcsExamining:
6748         break;
6749     }
6750     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6751         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6752         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6753         && gameMode != AnalyzeFile && gameMode != Training) {
6754         DisplayMoveError(_("Displayed position is not current"));
6755         return FALSE;
6756     }
6757     return TRUE;
6758 }
6759
6760 Boolean
6761 OnlyMove (int *x, int *y, Boolean captures)
6762 {
6763     DisambiguateClosure cl;
6764     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6765     switch(gameMode) {
6766       case MachinePlaysBlack:
6767       case IcsPlayingWhite:
6768       case BeginningOfGame:
6769         if(!WhiteOnMove(currentMove)) return FALSE;
6770         break;
6771       case MachinePlaysWhite:
6772       case IcsPlayingBlack:
6773         if(WhiteOnMove(currentMove)) return FALSE;
6774         break;
6775       case EditGame:
6776         break;
6777       default:
6778         return FALSE;
6779     }
6780     cl.pieceIn = EmptySquare;
6781     cl.rfIn = *y;
6782     cl.ffIn = *x;
6783     cl.rtIn = -1;
6784     cl.ftIn = -1;
6785     cl.promoCharIn = NULLCHAR;
6786     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6787     if( cl.kind == NormalMove ||
6788         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6789         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6790         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6791       fromX = cl.ff;
6792       fromY = cl.rf;
6793       *x = cl.ft;
6794       *y = cl.rt;
6795       return TRUE;
6796     }
6797     if(cl.kind != ImpossibleMove) return FALSE;
6798     cl.pieceIn = EmptySquare;
6799     cl.rfIn = -1;
6800     cl.ffIn = -1;
6801     cl.rtIn = *y;
6802     cl.ftIn = *x;
6803     cl.promoCharIn = NULLCHAR;
6804     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6805     if( cl.kind == NormalMove ||
6806         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6807         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6808         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6809       fromX = cl.ff;
6810       fromY = cl.rf;
6811       *x = cl.ft;
6812       *y = cl.rt;
6813       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6814       return TRUE;
6815     }
6816     return FALSE;
6817 }
6818
6819 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6820 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6821 int lastLoadGameUseList = FALSE;
6822 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6823 ChessMove lastLoadGameStart = EndOfFile;
6824 int doubleClick;
6825 Boolean addToBookFlag;
6826
6827 void
6828 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6829 {
6830     ChessMove moveType;
6831     ChessSquare pup;
6832     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6833
6834     /* Check if the user is playing in turn.  This is complicated because we
6835        let the user "pick up" a piece before it is his turn.  So the piece he
6836        tried to pick up may have been captured by the time he puts it down!
6837        Therefore we use the color the user is supposed to be playing in this
6838        test, not the color of the piece that is currently on the starting
6839        square---except in EditGame mode, where the user is playing both
6840        sides; fortunately there the capture race can't happen.  (It can
6841        now happen in IcsExamining mode, but that's just too bad.  The user
6842        will get a somewhat confusing message in that case.)
6843        */
6844
6845     switch (gameMode) {
6846       case AnalyzeFile:
6847       case TwoMachinesPlay:
6848       case EndOfGame:
6849       case IcsObserving:
6850       case IcsIdle:
6851         /* We switched into a game mode where moves are not accepted,
6852            perhaps while the mouse button was down. */
6853         return;
6854
6855       case MachinePlaysWhite:
6856         /* User is moving for Black */
6857         if (WhiteOnMove(currentMove)) {
6858             DisplayMoveError(_("It is White's turn"));
6859             return;
6860         }
6861         break;
6862
6863       case MachinePlaysBlack:
6864         /* User is moving for White */
6865         if (!WhiteOnMove(currentMove)) {
6866             DisplayMoveError(_("It is Black's turn"));
6867             return;
6868         }
6869         break;
6870
6871       case PlayFromGameFile:
6872             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6873       case EditGame:
6874       case IcsExamining:
6875       case BeginningOfGame:
6876       case AnalyzeMode:
6877       case Training:
6878         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6879         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6880             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6881             /* User is moving for Black */
6882             if (WhiteOnMove(currentMove)) {
6883                 DisplayMoveError(_("It is White's turn"));
6884                 return;
6885             }
6886         } else {
6887             /* User is moving for White */
6888             if (!WhiteOnMove(currentMove)) {
6889                 DisplayMoveError(_("It is Black's turn"));
6890                 return;
6891             }
6892         }
6893         break;
6894
6895       case IcsPlayingBlack:
6896         /* User is moving for Black */
6897         if (WhiteOnMove(currentMove)) {
6898             if (!appData.premove) {
6899                 DisplayMoveError(_("It is White's turn"));
6900             } else if (toX >= 0 && toY >= 0) {
6901                 premoveToX = toX;
6902                 premoveToY = toY;
6903                 premoveFromX = fromX;
6904                 premoveFromY = fromY;
6905                 premovePromoChar = promoChar;
6906                 gotPremove = 1;
6907                 if (appData.debugMode)
6908                     fprintf(debugFP, "Got premove: fromX %d,"
6909                             "fromY %d, toX %d, toY %d\n",
6910                             fromX, fromY, toX, toY);
6911             }
6912             return;
6913         }
6914         break;
6915
6916       case IcsPlayingWhite:
6917         /* User is moving for White */
6918         if (!WhiteOnMove(currentMove)) {
6919             if (!appData.premove) {
6920                 DisplayMoveError(_("It is Black's turn"));
6921             } else if (toX >= 0 && toY >= 0) {
6922                 premoveToX = toX;
6923                 premoveToY = toY;
6924                 premoveFromX = fromX;
6925                 premoveFromY = fromY;
6926                 premovePromoChar = promoChar;
6927                 gotPremove = 1;
6928                 if (appData.debugMode)
6929                     fprintf(debugFP, "Got premove: fromX %d,"
6930                             "fromY %d, toX %d, toY %d\n",
6931                             fromX, fromY, toX, toY);
6932             }
6933             return;
6934         }
6935         break;
6936
6937       default:
6938         break;
6939
6940       case EditPosition:
6941         /* EditPosition, empty square, or different color piece;
6942            click-click move is possible */
6943         if (toX == -2 || toY == -2) {
6944             boards[0][fromY][fromX] = EmptySquare;
6945             DrawPosition(FALSE, boards[currentMove]);
6946             return;
6947         } else if (toX >= 0 && toY >= 0) {
6948             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6949                 ChessSquare q, p = boards[0][rf][ff];
6950                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6951                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6952                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6953                 if(PieceToChar(q) == '+') gatingPiece = p;
6954             }
6955             boards[0][toY][toX] = boards[0][fromY][fromX];
6956             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6957                 if(boards[0][fromY][0] != EmptySquare) {
6958                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6959                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6960                 }
6961             } else
6962             if(fromX == BOARD_RGHT+1) {
6963                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6964                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6965                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6966                 }
6967             } else
6968             boards[0][fromY][fromX] = gatingPiece;
6969             DrawPosition(FALSE, boards[currentMove]);
6970             return;
6971         }
6972         return;
6973     }
6974
6975     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6976     pup = boards[currentMove][toY][toX];
6977
6978     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6979     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6980          if( pup != EmptySquare ) return;
6981          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6982            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6983                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6984            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6985            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6986            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6987            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6988          fromY = DROP_RANK;
6989     }
6990
6991     /* [HGM] always test for legality, to get promotion info */
6992     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6993                                          fromY, fromX, toY, toX, promoChar);
6994
6995     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
6996
6997     /* [HGM] but possibly ignore an IllegalMove result */
6998     if (appData.testLegality) {
6999         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7000             DisplayMoveError(_("Illegal move"));
7001             return;
7002         }
7003     }
7004
7005     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7006         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7007              ClearPremoveHighlights(); // was included
7008         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7009         return;
7010     }
7011
7012     if(addToBookFlag) { // adding moves to book
7013         char buf[MSG_SIZ], move[MSG_SIZ];
7014         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7015         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7016         AddBookMove(buf);
7017         addToBookFlag = FALSE;
7018         ClearHighlights();
7019         return;
7020     }
7021
7022     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7023 }
7024
7025 /* Common tail of UserMoveEvent and DropMenuEvent */
7026 int
7027 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7028 {
7029     char *bookHit = 0;
7030
7031     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7032         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7033         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7034         if(WhiteOnMove(currentMove)) {
7035             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7036         } else {
7037             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7038         }
7039     }
7040
7041     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7042        move type in caller when we know the move is a legal promotion */
7043     if(moveType == NormalMove && promoChar)
7044         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7045
7046     /* [HGM] <popupFix> The following if has been moved here from
7047        UserMoveEvent(). Because it seemed to belong here (why not allow
7048        piece drops in training games?), and because it can only be
7049        performed after it is known to what we promote. */
7050     if (gameMode == Training) {
7051       /* compare the move played on the board to the next move in the
7052        * game. If they match, display the move and the opponent's response.
7053        * If they don't match, display an error message.
7054        */
7055       int saveAnimate;
7056       Board testBoard;
7057       CopyBoard(testBoard, boards[currentMove]);
7058       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7059
7060       if (CompareBoards(testBoard, boards[currentMove+1])) {
7061         ForwardInner(currentMove+1);
7062
7063         /* Autoplay the opponent's response.
7064          * if appData.animate was TRUE when Training mode was entered,
7065          * the response will be animated.
7066          */
7067         saveAnimate = appData.animate;
7068         appData.animate = animateTraining;
7069         ForwardInner(currentMove+1);
7070         appData.animate = saveAnimate;
7071
7072         /* check for the end of the game */
7073         if (currentMove >= forwardMostMove) {
7074           gameMode = PlayFromGameFile;
7075           ModeHighlight();
7076           SetTrainingModeOff();
7077           DisplayInformation(_("End of game"));
7078         }
7079       } else {
7080         DisplayError(_("Incorrect move"), 0);
7081       }
7082       return 1;
7083     }
7084
7085   /* Ok, now we know that the move is good, so we can kill
7086      the previous line in Analysis Mode */
7087   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7088                                 && currentMove < forwardMostMove) {
7089     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7090     else forwardMostMove = currentMove;
7091   }
7092
7093   ClearMap();
7094
7095   /* If we need the chess program but it's dead, restart it */
7096   ResurrectChessProgram();
7097
7098   /* A user move restarts a paused game*/
7099   if (pausing)
7100     PauseEvent();
7101
7102   thinkOutput[0] = NULLCHAR;
7103
7104   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7105
7106   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7107     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7108     return 1;
7109   }
7110
7111   if (gameMode == BeginningOfGame) {
7112     if (appData.noChessProgram) {
7113       gameMode = EditGame;
7114       SetGameInfo();
7115     } else {
7116       char buf[MSG_SIZ];
7117       gameMode = MachinePlaysBlack;
7118       StartClocks();
7119       SetGameInfo();
7120       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7121       DisplayTitle(buf);
7122       if (first.sendName) {
7123         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7124         SendToProgram(buf, &first);
7125       }
7126       StartClocks();
7127     }
7128     ModeHighlight();
7129   }
7130
7131   /* Relay move to ICS or chess engine */
7132   if (appData.icsActive) {
7133     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7134         gameMode == IcsExamining) {
7135       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7136         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7137         SendToICS("draw ");
7138         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7139       }
7140       // also send plain move, in case ICS does not understand atomic claims
7141       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7142       ics_user_moved = 1;
7143     }
7144   } else {
7145     if (first.sendTime && (gameMode == BeginningOfGame ||
7146                            gameMode == MachinePlaysWhite ||
7147                            gameMode == MachinePlaysBlack)) {
7148       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7149     }
7150     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7151          // [HGM] book: if program might be playing, let it use book
7152         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7153         first.maybeThinking = TRUE;
7154     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7155         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7156         SendBoard(&first, currentMove+1);
7157         if(second.analyzing) {
7158             if(!second.useSetboard) SendToProgram("undo\n", &second);
7159             SendBoard(&second, currentMove+1);
7160         }
7161     } else {
7162         SendMoveToProgram(forwardMostMove-1, &first);
7163         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7164     }
7165     if (currentMove == cmailOldMove + 1) {
7166       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7167     }
7168   }
7169
7170   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7171
7172   switch (gameMode) {
7173   case EditGame:
7174     if(appData.testLegality)
7175     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7176     case MT_NONE:
7177     case MT_CHECK:
7178       break;
7179     case MT_CHECKMATE:
7180     case MT_STAINMATE:
7181       if (WhiteOnMove(currentMove)) {
7182         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7183       } else {
7184         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7185       }
7186       break;
7187     case MT_STALEMATE:
7188       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7189       break;
7190     }
7191     break;
7192
7193   case MachinePlaysBlack:
7194   case MachinePlaysWhite:
7195     /* disable certain menu options while machine is thinking */
7196     SetMachineThinkingEnables();
7197     break;
7198
7199   default:
7200     break;
7201   }
7202
7203   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7204   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7205
7206   if(bookHit) { // [HGM] book: simulate book reply
7207         static char bookMove[MSG_SIZ]; // a bit generous?
7208
7209         programStats.nodes = programStats.depth = programStats.time =
7210         programStats.score = programStats.got_only_move = 0;
7211         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7212
7213         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7214         strcat(bookMove, bookHit);
7215         HandleMachineMove(bookMove, &first);
7216   }
7217   return 1;
7218 }
7219
7220 void
7221 MarkByFEN(char *fen)
7222 {
7223         int r, f;
7224         if(!appData.markers || !appData.highlightDragging) return;
7225         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7226         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7227         while(*fen) {
7228             int s = 0;
7229             marker[r][f] = 0;
7230             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7231             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7232             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7233             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7234             if(*fen == 'T') marker[r][f++] = 0; else
7235             if(*fen == 'Y') marker[r][f++] = 1; else
7236             if(*fen == 'G') marker[r][f++] = 3; else
7237             if(*fen == 'B') marker[r][f++] = 4; else
7238             if(*fen == 'C') marker[r][f++] = 5; else
7239             if(*fen == 'M') marker[r][f++] = 6; else
7240             if(*fen == 'W') marker[r][f++] = 7; else
7241             if(*fen == 'D') marker[r][f++] = 8; else
7242             if(*fen == 'R') marker[r][f++] = 2; else {
7243                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7244               f += s; fen -= s>0;
7245             }
7246             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7247             if(r < 0) break;
7248             fen++;
7249         }
7250         DrawPosition(TRUE, NULL);
7251 }
7252
7253 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7254
7255 void
7256 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7257 {
7258     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7259     Markers *m = (Markers *) closure;
7260     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7261         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7262                          || kind == WhiteCapturesEnPassant
7263                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7264     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7265 }
7266
7267 static int hoverSavedValid;
7268
7269 void
7270 MarkTargetSquares (int clear)
7271 {
7272   int x, y, sum=0;
7273   if(clear) { // no reason to ever suppress clearing
7274     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7275     hoverSavedValid = 0;
7276     if(!sum) return; // nothing was cleared,no redraw needed
7277   } else {
7278     int capt = 0;
7279     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7280        !appData.testLegality || gameMode == EditPosition) return;
7281     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7282     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7283       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7284       if(capt)
7285       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7286     }
7287   }
7288   DrawPosition(FALSE, NULL);
7289 }
7290
7291 int
7292 Explode (Board board, int fromX, int fromY, int toX, int toY)
7293 {
7294     if(gameInfo.variant == VariantAtomic &&
7295        (board[toY][toX] != EmptySquare ||                     // capture?
7296         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7297                          board[fromY][fromX] == BlackPawn   )
7298       )) {
7299         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7300         return TRUE;
7301     }
7302     return FALSE;
7303 }
7304
7305 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7306
7307 int
7308 CanPromote (ChessSquare piece, int y)
7309 {
7310         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7311         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7312         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7313         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7314            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7315            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7316          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7317         return (piece == BlackPawn && y <= zone ||
7318                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7319                 piece == BlackLance && y == 1 ||
7320                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7321 }
7322
7323 void
7324 HoverEvent (int xPix, int yPix, int x, int y)
7325 {
7326         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7327         int r, f;
7328         if(!first.highlight) return;
7329         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7330         if(x == oldX && y == oldY) return; // only do something if we enter new square
7331         oldFromX = fromX; oldFromY = fromY;
7332         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7333           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7334             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7335           hoverSavedValid = 1;
7336         } else if(oldX != x || oldY != y) {
7337           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7338           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7339           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7340             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7341           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7342             char buf[MSG_SIZ];
7343             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7344             SendToProgram(buf, &first);
7345           }
7346           oldX = x; oldY = y;
7347 //        SetHighlights(fromX, fromY, x, y);
7348         }
7349 }
7350
7351 void ReportClick(char *action, int x, int y)
7352 {
7353         char buf[MSG_SIZ]; // Inform engine of what user does
7354         int r, f;
7355         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7356           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7357         if(!first.highlight || gameMode == EditPosition) return;
7358         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7359         SendToProgram(buf, &first);
7360 }
7361
7362 void
7363 LeftClick (ClickType clickType, int xPix, int yPix)
7364 {
7365     int x, y;
7366     Boolean saveAnimate;
7367     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7368     char promoChoice = NULLCHAR;
7369     ChessSquare piece;
7370     static TimeMark lastClickTime, prevClickTime;
7371
7372     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7373
7374     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7375
7376     if (clickType == Press) ErrorPopDown();
7377     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7378
7379     x = EventToSquare(xPix, BOARD_WIDTH);
7380     y = EventToSquare(yPix, BOARD_HEIGHT);
7381     if (!flipView && y >= 0) {
7382         y = BOARD_HEIGHT - 1 - y;
7383     }
7384     if (flipView && x >= 0) {
7385         x = BOARD_WIDTH - 1 - x;
7386     }
7387
7388     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7389         defaultPromoChoice = promoSweep;
7390         promoSweep = EmptySquare;   // terminate sweep
7391         promoDefaultAltered = TRUE;
7392         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7393     }
7394
7395     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7396         if(clickType == Release) return; // ignore upclick of click-click destination
7397         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7398         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7399         if(gameInfo.holdingsWidth &&
7400                 (WhiteOnMove(currentMove)
7401                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7402                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7403             // click in right holdings, for determining promotion piece
7404             ChessSquare p = boards[currentMove][y][x];
7405             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7406             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7407             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7408                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7409                 fromX = fromY = -1;
7410                 return;
7411             }
7412         }
7413         DrawPosition(FALSE, boards[currentMove]);
7414         return;
7415     }
7416
7417     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7418     if(clickType == Press
7419             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7420               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7421               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7422         return;
7423
7424     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7425         // could be static click on premove from-square: abort premove
7426         gotPremove = 0;
7427         ClearPremoveHighlights();
7428     }
7429
7430     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7431         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7432
7433     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7434         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7435                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7436         defaultPromoChoice = DefaultPromoChoice(side);
7437     }
7438
7439     autoQueen = appData.alwaysPromoteToQueen;
7440
7441     if (fromX == -1) {
7442       int originalY = y;
7443       gatingPiece = EmptySquare;
7444       if (clickType != Press) {
7445         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7446             DragPieceEnd(xPix, yPix); dragging = 0;
7447             DrawPosition(FALSE, NULL);
7448         }
7449         return;
7450       }
7451       doubleClick = FALSE;
7452       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7453         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7454       }
7455       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7456       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7457          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7458          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7459             /* First square */
7460             if (OKToStartUserMove(fromX, fromY)) {
7461                 second = 0;
7462                 ReportClick("lift", x, y);
7463                 MarkTargetSquares(0);
7464                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7465                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7466                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7467                     promoSweep = defaultPromoChoice;
7468                     selectFlag = 0; lastX = xPix; lastY = yPix;
7469                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7470                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7471                 }
7472                 if (appData.highlightDragging) {
7473                     SetHighlights(fromX, fromY, -1, -1);
7474                 } else {
7475                     ClearHighlights();
7476                 }
7477             } else fromX = fromY = -1;
7478             return;
7479         }
7480     }
7481
7482     /* fromX != -1 */
7483     if (clickType == Press && gameMode != EditPosition) {
7484         ChessSquare fromP;
7485         ChessSquare toP;
7486         int frc;
7487
7488         // ignore off-board to clicks
7489         if(y < 0 || x < 0) return;
7490
7491         /* Check if clicking again on the same color piece */
7492         fromP = boards[currentMove][fromY][fromX];
7493         toP = boards[currentMove][y][x];
7494         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7495         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7496            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7497              WhitePawn <= toP && toP <= WhiteKing &&
7498              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7499              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7500             (BlackPawn <= fromP && fromP <= BlackKing &&
7501              BlackPawn <= toP && toP <= BlackKing &&
7502              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7503              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7504             /* Clicked again on same color piece -- changed his mind */
7505             second = (x == fromX && y == fromY);
7506             killX = killY = -1;
7507             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7508                 second = FALSE; // first double-click rather than scond click
7509                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7510             }
7511             promoDefaultAltered = FALSE;
7512             MarkTargetSquares(1);
7513            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7514             if (appData.highlightDragging) {
7515                 SetHighlights(x, y, -1, -1);
7516             } else {
7517                 ClearHighlights();
7518             }
7519             if (OKToStartUserMove(x, y)) {
7520                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7521                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7522                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7523                  gatingPiece = boards[currentMove][fromY][fromX];
7524                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7525                 fromX = x;
7526                 fromY = y; dragging = 1;
7527                 ReportClick("lift", x, y);
7528                 MarkTargetSquares(0);
7529                 DragPieceBegin(xPix, yPix, FALSE);
7530                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7531                     promoSweep = defaultPromoChoice;
7532                     selectFlag = 0; lastX = xPix; lastY = yPix;
7533                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7534                 }
7535             }
7536            }
7537            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7538            second = FALSE;
7539         }
7540         // ignore clicks on holdings
7541         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7542     }
7543
7544     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7545         DragPieceEnd(xPix, yPix); dragging = 0;
7546         if(clearFlag) {
7547             // a deferred attempt to click-click move an empty square on top of a piece
7548             boards[currentMove][y][x] = EmptySquare;
7549             ClearHighlights();
7550             DrawPosition(FALSE, boards[currentMove]);
7551             fromX = fromY = -1; clearFlag = 0;
7552             return;
7553         }
7554         if (appData.animateDragging) {
7555             /* Undo animation damage if any */
7556             DrawPosition(FALSE, NULL);
7557         }
7558         if (second || sweepSelecting) {
7559             /* Second up/down in same square; just abort move */
7560             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7561             second = sweepSelecting = 0;
7562             fromX = fromY = -1;
7563             gatingPiece = EmptySquare;
7564             MarkTargetSquares(1);
7565             ClearHighlights();
7566             gotPremove = 0;
7567             ClearPremoveHighlights();
7568         } else {
7569             /* First upclick in same square; start click-click mode */
7570             SetHighlights(x, y, -1, -1);
7571         }
7572         return;
7573     }
7574
7575     clearFlag = 0;
7576
7577     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7578         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7579         DisplayMessage(_("only marked squares are legal"),"");
7580         DrawPosition(TRUE, NULL);
7581         return; // ignore to-click
7582     }
7583
7584     /* we now have a different from- and (possibly off-board) to-square */
7585     /* Completed move */
7586     if(!sweepSelecting) {
7587         toX = x;
7588         toY = y;
7589     }
7590
7591     piece = boards[currentMove][fromY][fromX];
7592
7593     saveAnimate = appData.animate;
7594     if (clickType == Press) {
7595         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7596         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7597             // must be Edit Position mode with empty-square selected
7598             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7599             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7600             return;
7601         }
7602         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7603             return;
7604         }
7605         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7606             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7607         } else
7608         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7609         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7610           if(appData.sweepSelect) {
7611             promoSweep = defaultPromoChoice;
7612             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7613             selectFlag = 0; lastX = xPix; lastY = yPix;
7614             Sweep(0); // Pawn that is going to promote: preview promotion piece
7615             sweepSelecting = 1;
7616             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7617             MarkTargetSquares(1);
7618           }
7619           return; // promo popup appears on up-click
7620         }
7621         /* Finish clickclick move */
7622         if (appData.animate || appData.highlightLastMove) {
7623             SetHighlights(fromX, fromY, toX, toY);
7624         } else {
7625             ClearHighlights();
7626         }
7627     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7628         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7629         if (appData.animate || appData.highlightLastMove) {
7630             SetHighlights(fromX, fromY, toX, toY);
7631         } else {
7632             ClearHighlights();
7633         }
7634     } else {
7635 #if 0
7636 // [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
7637         /* Finish drag move */
7638         if (appData.highlightLastMove) {
7639             SetHighlights(fromX, fromY, toX, toY);
7640         } else {
7641             ClearHighlights();
7642         }
7643 #endif
7644         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7645         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7646           dragging *= 2;            // flag button-less dragging if we are dragging
7647           MarkTargetSquares(1);
7648           if(x == killX && y == killY) killX = killY = -1; else {
7649             killX = x; killY = y;     //remeber this square as intermediate
7650             ReportClick("put", x, y); // and inform engine
7651             ReportClick("lift", x, y);
7652             MarkTargetSquares(0);
7653             return;
7654           }
7655         }
7656         DragPieceEnd(xPix, yPix); dragging = 0;
7657         /* Don't animate move and drag both */
7658         appData.animate = FALSE;
7659     }
7660
7661     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7662     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7663         ChessSquare piece = boards[currentMove][fromY][fromX];
7664         if(gameMode == EditPosition && piece != EmptySquare &&
7665            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7666             int n;
7667
7668             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7669                 n = PieceToNumber(piece - (int)BlackPawn);
7670                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7671                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7672                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7673             } else
7674             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7675                 n = PieceToNumber(piece);
7676                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7677                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7678                 boards[currentMove][n][BOARD_WIDTH-2]++;
7679             }
7680             boards[currentMove][fromY][fromX] = EmptySquare;
7681         }
7682         ClearHighlights();
7683         fromX = fromY = -1;
7684         MarkTargetSquares(1);
7685         DrawPosition(TRUE, boards[currentMove]);
7686         return;
7687     }
7688
7689     // off-board moves should not be highlighted
7690     if(x < 0 || y < 0) ClearHighlights();
7691     else ReportClick("put", x, y);
7692
7693     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7694
7695     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7696         SetHighlights(fromX, fromY, toX, toY);
7697         MarkTargetSquares(1);
7698         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7699             // [HGM] super: promotion to captured piece selected from holdings
7700             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7701             promotionChoice = TRUE;
7702             // kludge follows to temporarily execute move on display, without promoting yet
7703             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7704             boards[currentMove][toY][toX] = p;
7705             DrawPosition(FALSE, boards[currentMove]);
7706             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7707             boards[currentMove][toY][toX] = q;
7708             DisplayMessage("Click in holdings to choose piece", "");
7709             return;
7710         }
7711         PromotionPopUp(promoChoice);
7712     } else {
7713         int oldMove = currentMove;
7714         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7715         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7716         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7717         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7718            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7719             DrawPosition(TRUE, boards[currentMove]);
7720         MarkTargetSquares(1);
7721         fromX = fromY = -1;
7722     }
7723     appData.animate = saveAnimate;
7724     if (appData.animate || appData.animateDragging) {
7725         /* Undo animation damage if needed */
7726         DrawPosition(FALSE, NULL);
7727     }
7728 }
7729
7730 int
7731 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7732 {   // front-end-free part taken out of PieceMenuPopup
7733     int whichMenu; int xSqr, ySqr;
7734
7735     if(seekGraphUp) { // [HGM] seekgraph
7736         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7737         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7738         return -2;
7739     }
7740
7741     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7742          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7743         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7744         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7745         if(action == Press)   {
7746             originalFlip = flipView;
7747             flipView = !flipView; // temporarily flip board to see game from partners perspective
7748             DrawPosition(TRUE, partnerBoard);
7749             DisplayMessage(partnerStatus, "");
7750             partnerUp = TRUE;
7751         } else if(action == Release) {
7752             flipView = originalFlip;
7753             DrawPosition(TRUE, boards[currentMove]);
7754             partnerUp = FALSE;
7755         }
7756         return -2;
7757     }
7758
7759     xSqr = EventToSquare(x, BOARD_WIDTH);
7760     ySqr = EventToSquare(y, BOARD_HEIGHT);
7761     if (action == Release) {
7762         if(pieceSweep != EmptySquare) {
7763             EditPositionMenuEvent(pieceSweep, toX, toY);
7764             pieceSweep = EmptySquare;
7765         } else UnLoadPV(); // [HGM] pv
7766     }
7767     if (action != Press) return -2; // return code to be ignored
7768     switch (gameMode) {
7769       case IcsExamining:
7770         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7771       case EditPosition:
7772         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7773         if (xSqr < 0 || ySqr < 0) return -1;
7774         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7775         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7776         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7777         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7778         NextPiece(0);
7779         return 2; // grab
7780       case IcsObserving:
7781         if(!appData.icsEngineAnalyze) return -1;
7782       case IcsPlayingWhite:
7783       case IcsPlayingBlack:
7784         if(!appData.zippyPlay) goto noZip;
7785       case AnalyzeMode:
7786       case AnalyzeFile:
7787       case MachinePlaysWhite:
7788       case MachinePlaysBlack:
7789       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7790         if (!appData.dropMenu) {
7791           LoadPV(x, y);
7792           return 2; // flag front-end to grab mouse events
7793         }
7794         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7795            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7796       case EditGame:
7797       noZip:
7798         if (xSqr < 0 || ySqr < 0) return -1;
7799         if (!appData.dropMenu || appData.testLegality &&
7800             gameInfo.variant != VariantBughouse &&
7801             gameInfo.variant != VariantCrazyhouse) return -1;
7802         whichMenu = 1; // drop menu
7803         break;
7804       default:
7805         return -1;
7806     }
7807
7808     if (((*fromX = xSqr) < 0) ||
7809         ((*fromY = ySqr) < 0)) {
7810         *fromX = *fromY = -1;
7811         return -1;
7812     }
7813     if (flipView)
7814       *fromX = BOARD_WIDTH - 1 - *fromX;
7815     else
7816       *fromY = BOARD_HEIGHT - 1 - *fromY;
7817
7818     return whichMenu;
7819 }
7820
7821 void
7822 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7823 {
7824 //    char * hint = lastHint;
7825     FrontEndProgramStats stats;
7826
7827     stats.which = cps == &first ? 0 : 1;
7828     stats.depth = cpstats->depth;
7829     stats.nodes = cpstats->nodes;
7830     stats.score = cpstats->score;
7831     stats.time = cpstats->time;
7832     stats.pv = cpstats->movelist;
7833     stats.hint = lastHint;
7834     stats.an_move_index = 0;
7835     stats.an_move_count = 0;
7836
7837     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7838         stats.hint = cpstats->move_name;
7839         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7840         stats.an_move_count = cpstats->nr_moves;
7841     }
7842
7843     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
7844
7845     SetProgramStats( &stats );
7846 }
7847
7848 void
7849 ClearEngineOutputPane (int which)
7850 {
7851     static FrontEndProgramStats dummyStats;
7852     dummyStats.which = which;
7853     dummyStats.pv = "#";
7854     SetProgramStats( &dummyStats );
7855 }
7856
7857 #define MAXPLAYERS 500
7858
7859 char *
7860 TourneyStandings (int display)
7861 {
7862     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7863     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7864     char result, *p, *names[MAXPLAYERS];
7865
7866     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7867         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7868     names[0] = p = strdup(appData.participants);
7869     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7870
7871     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7872
7873     while(result = appData.results[nr]) {
7874         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7875         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7876         wScore = bScore = 0;
7877         switch(result) {
7878           case '+': wScore = 2; break;
7879           case '-': bScore = 2; break;
7880           case '=': wScore = bScore = 1; break;
7881           case ' ':
7882           case '*': return strdup("busy"); // tourney not finished
7883         }
7884         score[w] += wScore;
7885         score[b] += bScore;
7886         games[w]++;
7887         games[b]++;
7888         nr++;
7889     }
7890     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7891     for(w=0; w<nPlayers; w++) {
7892         bScore = -1;
7893         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7894         ranking[w] = b; points[w] = bScore; score[b] = -2;
7895     }
7896     p = malloc(nPlayers*34+1);
7897     for(w=0; w<nPlayers && w<display; w++)
7898         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7899     free(names[0]);
7900     return p;
7901 }
7902
7903 void
7904 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7905 {       // count all piece types
7906         int p, f, r;
7907         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7908         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7909         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7910                 p = board[r][f];
7911                 pCnt[p]++;
7912                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7913                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7914                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7915                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7916                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7917                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7918         }
7919 }
7920
7921 int
7922 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7923 {
7924         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7925         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7926
7927         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7928         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7929         if(myPawns == 2 && nMine == 3) // KPP
7930             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7931         if(myPawns == 1 && nMine == 2) // KP
7932             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7933         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7934             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7935         if(myPawns) return FALSE;
7936         if(pCnt[WhiteRook+side])
7937             return pCnt[BlackRook-side] ||
7938                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7939                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7940                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7941         if(pCnt[WhiteCannon+side]) {
7942             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7943             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7944         }
7945         if(pCnt[WhiteKnight+side])
7946             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7947         return FALSE;
7948 }
7949
7950 int
7951 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7952 {
7953         VariantClass v = gameInfo.variant;
7954
7955         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7956         if(v == VariantShatranj) return TRUE; // always winnable through baring
7957         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7958         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7959
7960         if(v == VariantXiangqi) {
7961                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7962
7963                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7964                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7965                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7966                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7967                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7968                 if(stale) // we have at least one last-rank P plus perhaps C
7969                     return majors // KPKX
7970                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7971                 else // KCA*E*
7972                     return pCnt[WhiteFerz+side] // KCAK
7973                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7974                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7975                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7976
7977         } else if(v == VariantKnightmate) {
7978                 if(nMine == 1) return FALSE;
7979                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7980         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7981                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7982
7983                 if(nMine == 1) return FALSE; // bare King
7984                 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
7985                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7986                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7987                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7988                 if(pCnt[WhiteKnight+side])
7989                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7990                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7991                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7992                 if(nBishops)
7993                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7994                 if(pCnt[WhiteAlfil+side])
7995                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7996                 if(pCnt[WhiteWazir+side])
7997                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7998         }
7999
8000         return TRUE;
8001 }
8002
8003 int
8004 CompareWithRights (Board b1, Board b2)
8005 {
8006     int rights = 0;
8007     if(!CompareBoards(b1, b2)) return FALSE;
8008     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8009     /* compare castling rights */
8010     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8011            rights++; /* King lost rights, while rook still had them */
8012     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8013         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8014            rights++; /* but at least one rook lost them */
8015     }
8016     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8017            rights++;
8018     if( b1[CASTLING][5] != NoRights ) {
8019         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8020            rights++;
8021     }
8022     return rights == 0;
8023 }
8024
8025 int
8026 Adjudicate (ChessProgramState *cps)
8027 {       // [HGM] some adjudications useful with buggy engines
8028         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8029         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8030         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8031         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8032         int k, drop, count = 0; static int bare = 1;
8033         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8034         Boolean canAdjudicate = !appData.icsActive;
8035
8036         // most tests only when we understand the game, i.e. legality-checking on
8037             if( appData.testLegality )
8038             {   /* [HGM] Some more adjudications for obstinate engines */
8039                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8040                 static int moveCount = 6;
8041                 ChessMove result;
8042                 char *reason = NULL;
8043
8044                 /* Count what is on board. */
8045                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8046
8047                 /* Some material-based adjudications that have to be made before stalemate test */
8048                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8049                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8050                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8051                      if(canAdjudicate && appData.checkMates) {
8052                          if(engineOpponent)
8053                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8054                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8055                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8056                          return 1;
8057                      }
8058                 }
8059
8060                 /* Bare King in Shatranj (loses) or Losers (wins) */
8061                 if( nrW == 1 || nrB == 1) {
8062                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8063                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8064                      if(canAdjudicate && appData.checkMates) {
8065                          if(engineOpponent)
8066                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8067                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8068                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8069                          return 1;
8070                      }
8071                   } else
8072                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8073                   {    /* bare King */
8074                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8075                         if(canAdjudicate && appData.checkMates) {
8076                             /* but only adjudicate if adjudication enabled */
8077                             if(engineOpponent)
8078                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8079                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8080                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8081                             return 1;
8082                         }
8083                   }
8084                 } else bare = 1;
8085
8086
8087             // don't wait for engine to announce game end if we can judge ourselves
8088             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8089               case MT_CHECK:
8090                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8091                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8092                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8093                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8094                             checkCnt++;
8095                         if(checkCnt >= 2) {
8096                             reason = "Xboard adjudication: 3rd check";
8097                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8098                             break;
8099                         }
8100                     }
8101                 }
8102               case MT_NONE:
8103               default:
8104                 break;
8105               case MT_STEALMATE:
8106               case MT_STALEMATE:
8107               case MT_STAINMATE:
8108                 reason = "Xboard adjudication: Stalemate";
8109                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8110                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8111                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8112                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8113                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8114                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8115                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8116                                                                         EP_CHECKMATE : EP_WINS);
8117                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8118                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8119                 }
8120                 break;
8121               case MT_CHECKMATE:
8122                 reason = "Xboard adjudication: Checkmate";
8123                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8124                 if(gameInfo.variant == VariantShogi) {
8125                     if(forwardMostMove > backwardMostMove
8126                        && moveList[forwardMostMove-1][1] == '@'
8127                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8128                         reason = "XBoard adjudication: pawn-drop mate";
8129                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8130                     }
8131                 }
8132                 break;
8133             }
8134
8135                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8136                     case EP_STALEMATE:
8137                         result = GameIsDrawn; break;
8138                     case EP_CHECKMATE:
8139                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8140                     case EP_WINS:
8141                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8142                     default:
8143                         result = EndOfFile;
8144                 }
8145                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8146                     if(engineOpponent)
8147                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8148                     GameEnds( result, reason, GE_XBOARD );
8149                     return 1;
8150                 }
8151
8152                 /* Next absolutely insufficient mating material. */
8153                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8154                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8155                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8156
8157                      /* always flag draws, for judging claims */
8158                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8159
8160                      if(canAdjudicate && appData.materialDraws) {
8161                          /* but only adjudicate them if adjudication enabled */
8162                          if(engineOpponent) {
8163                            SendToProgram("force\n", engineOpponent); // suppress reply
8164                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8165                          }
8166                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8167                          return 1;
8168                      }
8169                 }
8170
8171                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8172                 if(gameInfo.variant == VariantXiangqi ?
8173                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8174                  : nrW + nrB == 4 &&
8175                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8176                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8177                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8178                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8179                    ) ) {
8180                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8181                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8182                           if(engineOpponent) {
8183                             SendToProgram("force\n", engineOpponent); // suppress reply
8184                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8185                           }
8186                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8187                           return 1;
8188                      }
8189                 } else moveCount = 6;
8190             }
8191
8192         // Repetition draws and 50-move rule can be applied independently of legality testing
8193
8194                 /* Check for rep-draws */
8195                 count = 0;
8196                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8197                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8198                 for(k = forwardMostMove-2;
8199                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8200                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8201                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8202                     k-=2)
8203                 {   int rights=0;
8204                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8205                         /* compare castling rights */
8206                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8207                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8208                                 rights++; /* King lost rights, while rook still had them */
8209                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8210                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8211                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8212                                    rights++; /* but at least one rook lost them */
8213                         }
8214                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8215                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8216                                 rights++;
8217                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8218                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8219                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8220                                    rights++;
8221                         }
8222                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8223                             && appData.drawRepeats > 1) {
8224                              /* adjudicate after user-specified nr of repeats */
8225                              int result = GameIsDrawn;
8226                              char *details = "XBoard adjudication: repetition draw";
8227                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8228                                 // [HGM] xiangqi: check for forbidden perpetuals
8229                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8230                                 for(m=forwardMostMove; m>k; m-=2) {
8231                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8232                                         ourPerpetual = 0; // the current mover did not always check
8233                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8234                                         hisPerpetual = 0; // the opponent did not always check
8235                                 }
8236                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8237                                                                         ourPerpetual, hisPerpetual);
8238                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8239                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8240                                     details = "Xboard adjudication: perpetual checking";
8241                                 } else
8242                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8243                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8244                                 } else
8245                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8246                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8247                                         result = BlackWins;
8248                                         details = "Xboard adjudication: repetition";
8249                                     }
8250                                 } else // it must be XQ
8251                                 // Now check for perpetual chases
8252                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8253                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8254                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8255                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8256                                         static char resdet[MSG_SIZ];
8257                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8258                                         details = resdet;
8259                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8260                                     } else
8261                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8262                                         break; // Abort repetition-checking loop.
8263                                 }
8264                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8265                              }
8266                              if(engineOpponent) {
8267                                SendToProgram("force\n", engineOpponent); // suppress reply
8268                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8269                              }
8270                              GameEnds( result, details, GE_XBOARD );
8271                              return 1;
8272                         }
8273                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8274                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8275                     }
8276                 }
8277
8278                 /* Now we test for 50-move draws. Determine ply count */
8279                 count = forwardMostMove;
8280                 /* look for last irreversble move */
8281                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8282                     count--;
8283                 /* if we hit starting position, add initial plies */
8284                 if( count == backwardMostMove )
8285                     count -= initialRulePlies;
8286                 count = forwardMostMove - count;
8287                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8288                         // adjust reversible move counter for checks in Xiangqi
8289                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8290                         if(i < backwardMostMove) i = backwardMostMove;
8291                         while(i <= forwardMostMove) {
8292                                 lastCheck = inCheck; // check evasion does not count
8293                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8294                                 if(inCheck || lastCheck) count--; // check does not count
8295                                 i++;
8296                         }
8297                 }
8298                 if( count >= 100)
8299                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8300                          /* this is used to judge if draw claims are legal */
8301                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8302                          if(engineOpponent) {
8303                            SendToProgram("force\n", engineOpponent); // suppress reply
8304                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8305                          }
8306                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8307                          return 1;
8308                 }
8309
8310                 /* if draw offer is pending, treat it as a draw claim
8311                  * when draw condition present, to allow engines a way to
8312                  * claim draws before making their move to avoid a race
8313                  * condition occurring after their move
8314                  */
8315                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8316                          char *p = NULL;
8317                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8318                              p = "Draw claim: 50-move rule";
8319                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8320                              p = "Draw claim: 3-fold repetition";
8321                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8322                              p = "Draw claim: insufficient mating material";
8323                          if( p != NULL && canAdjudicate) {
8324                              if(engineOpponent) {
8325                                SendToProgram("force\n", engineOpponent); // suppress reply
8326                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8327                              }
8328                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8329                              return 1;
8330                          }
8331                 }
8332
8333                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8334                     if(engineOpponent) {
8335                       SendToProgram("force\n", engineOpponent); // suppress reply
8336                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8337                     }
8338                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8339                     return 1;
8340                 }
8341         return 0;
8342 }
8343
8344 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8345 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8346 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8347
8348 static int
8349 BitbaseProbe ()
8350 {
8351     int pieces[10], squares[10], cnt=0, r, f, res;
8352     static int loaded;
8353     static PPROBE_EGBB probeBB;
8354     if(!appData.testLegality) return 10;
8355     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8356     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8357     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8358     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8359         ChessSquare piece = boards[forwardMostMove][r][f];
8360         int black = (piece >= BlackPawn);
8361         int type = piece - black*BlackPawn;
8362         if(piece == EmptySquare) continue;
8363         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8364         if(type == WhiteKing) type = WhiteQueen + 1;
8365         type = egbbCode[type];
8366         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8367         pieces[cnt] = type + black*6;
8368         if(++cnt > 5) return 11;
8369     }
8370     pieces[cnt] = squares[cnt] = 0;
8371     // probe EGBB
8372     if(loaded == 2) return 13; // loading failed before
8373     if(loaded == 0) {
8374         loaded = 2; // prepare for failure
8375         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8376         HMODULE lib;
8377         PLOAD_EGBB loadBB;
8378         if(!path) return 13; // no egbb installed
8379         strncpy(buf, path + 8, MSG_SIZ);
8380         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8381         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8382         lib = LoadLibrary(buf);
8383         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8384         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8385         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8386         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8387         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8388         loaded = 1; // success!
8389     }
8390     res = probeBB(forwardMostMove & 1, pieces, squares);
8391     return res > 0 ? 1 : res < 0 ? -1 : 0;
8392 }
8393
8394 char *
8395 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8396 {   // [HGM] book: this routine intercepts moves to simulate book replies
8397     char *bookHit = NULL;
8398
8399     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8400         char buf[MSG_SIZ];
8401         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8402         SendToProgram(buf, cps);
8403     }
8404     //first determine if the incoming move brings opponent into his book
8405     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8406         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8407     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8408     if(bookHit != NULL && !cps->bookSuspend) {
8409         // make sure opponent is not going to reply after receiving move to book position
8410         SendToProgram("force\n", cps);
8411         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8412     }
8413     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8414     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8415     // now arrange restart after book miss
8416     if(bookHit) {
8417         // after a book hit we never send 'go', and the code after the call to this routine
8418         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8419         char buf[MSG_SIZ], *move = bookHit;
8420         if(cps->useSAN) {
8421             int fromX, fromY, toX, toY;
8422             char promoChar;
8423             ChessMove moveType;
8424             move = buf + 30;
8425             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8426                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8427                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8428                                     PosFlags(forwardMostMove),
8429                                     fromY, fromX, toY, toX, promoChar, move);
8430             } else {
8431                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8432                 bookHit = NULL;
8433             }
8434         }
8435         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8436         SendToProgram(buf, cps);
8437         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8438     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8439         SendToProgram("go\n", cps);
8440         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8441     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8442         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8443             SendToProgram("go\n", cps);
8444         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8445     }
8446     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8447 }
8448
8449 int
8450 LoadError (char *errmess, ChessProgramState *cps)
8451 {   // unloads engine and switches back to -ncp mode if it was first
8452     if(cps->initDone) return FALSE;
8453     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8454     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8455     cps->pr = NoProc;
8456     if(cps == &first) {
8457         appData.noChessProgram = TRUE;
8458         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8459         gameMode = BeginningOfGame; ModeHighlight();
8460         SetNCPMode();
8461     }
8462     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8463     DisplayMessage("", ""); // erase waiting message
8464     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8465     return TRUE;
8466 }
8467
8468 char *savedMessage;
8469 ChessProgramState *savedState;
8470 void
8471 DeferredBookMove (void)
8472 {
8473         if(savedState->lastPing != savedState->lastPong)
8474                     ScheduleDelayedEvent(DeferredBookMove, 10);
8475         else
8476         HandleMachineMove(savedMessage, savedState);
8477 }
8478
8479 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8480 static ChessProgramState *stalledEngine;
8481 static char stashedInputMove[MSG_SIZ];
8482
8483 void
8484 HandleMachineMove (char *message, ChessProgramState *cps)
8485 {
8486     static char firstLeg[20];
8487     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8488     char realname[MSG_SIZ];
8489     int fromX, fromY, toX, toY;
8490     ChessMove moveType;
8491     char promoChar, roar;
8492     char *p, *pv=buf1;
8493     int machineWhite, oldError;
8494     char *bookHit;
8495
8496     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8497         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8498         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8499             DisplayError(_("Invalid pairing from pairing engine"), 0);
8500             return;
8501         }
8502         pairingReceived = 1;
8503         NextMatchGame();
8504         return; // Skim the pairing messages here.
8505     }
8506
8507     oldError = cps->userError; cps->userError = 0;
8508
8509 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8510     /*
8511      * Kludge to ignore BEL characters
8512      */
8513     while (*message == '\007') message++;
8514
8515     /*
8516      * [HGM] engine debug message: ignore lines starting with '#' character
8517      */
8518     if(cps->debug && *message == '#') return;
8519
8520     /*
8521      * Look for book output
8522      */
8523     if (cps == &first && bookRequested) {
8524         if (message[0] == '\t' || message[0] == ' ') {
8525             /* Part of the book output is here; append it */
8526             strcat(bookOutput, message);
8527             strcat(bookOutput, "  \n");
8528             return;
8529         } else if (bookOutput[0] != NULLCHAR) {
8530             /* All of book output has arrived; display it */
8531             char *p = bookOutput;
8532             while (*p != NULLCHAR) {
8533                 if (*p == '\t') *p = ' ';
8534                 p++;
8535             }
8536             DisplayInformation(bookOutput);
8537             bookRequested = FALSE;
8538             /* Fall through to parse the current output */
8539         }
8540     }
8541
8542     /*
8543      * Look for machine move.
8544      */
8545     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8546         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8547     {
8548         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8549             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8550             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8551             stalledEngine = cps;
8552             if(appData.ponderNextMove) { // bring opponent out of ponder
8553                 if(gameMode == TwoMachinesPlay) {
8554                     if(cps->other->pause)
8555                         PauseEngine(cps->other);
8556                     else
8557                         SendToProgram("easy\n", cps->other);
8558                 }
8559             }
8560             StopClocks();
8561             return;
8562         }
8563
8564         /* This method is only useful on engines that support ping */
8565         if (cps->lastPing != cps->lastPong) {
8566           if (gameMode == BeginningOfGame) {
8567             /* Extra move from before last new; ignore */
8568             if (appData.debugMode) {
8569                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8570             }
8571           } else {
8572             if (appData.debugMode) {
8573                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8574                         cps->which, gameMode);
8575             }
8576
8577             SendToProgram("undo\n", cps);
8578           }
8579           return;
8580         }
8581
8582         switch (gameMode) {
8583           case BeginningOfGame:
8584             /* Extra move from before last reset; ignore */
8585             if (appData.debugMode) {
8586                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8587             }
8588             return;
8589
8590           case EndOfGame:
8591           case IcsIdle:
8592           default:
8593             /* Extra move after we tried to stop.  The mode test is
8594                not a reliable way of detecting this problem, but it's
8595                the best we can do on engines that don't support ping.
8596             */
8597             if (appData.debugMode) {
8598                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8599                         cps->which, gameMode);
8600             }
8601             SendToProgram("undo\n", cps);
8602             return;
8603
8604           case MachinePlaysWhite:
8605           case IcsPlayingWhite:
8606             machineWhite = TRUE;
8607             break;
8608
8609           case MachinePlaysBlack:
8610           case IcsPlayingBlack:
8611             machineWhite = FALSE;
8612             break;
8613
8614           case TwoMachinesPlay:
8615             machineWhite = (cps->twoMachinesColor[0] == 'w');
8616             break;
8617         }
8618         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8619             if (appData.debugMode) {
8620                 fprintf(debugFP,
8621                         "Ignoring move out of turn by %s, gameMode %d"
8622                         ", forwardMost %d\n",
8623                         cps->which, gameMode, forwardMostMove);
8624             }
8625             return;
8626         }
8627
8628         if(cps->alphaRank) AlphaRank(machineMove, 4);
8629
8630         // [HGM] lion: (some very limited) support for Alien protocol
8631         killX = killY = -1;
8632         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8633             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8634             return;
8635         } else if(firstLeg[0]) { // there was a previous leg;
8636             // only support case where same piece makes two step (and don't even test that!)
8637             char buf[20], *p = machineMove+1, *q = buf+1, f;
8638             safeStrCpy(buf, machineMove, 20);
8639             while(isdigit(*q)) q++; // find start of to-square
8640             safeStrCpy(machineMove, firstLeg, 20);
8641             while(isdigit(*p)) p++;
8642             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8643             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8644             firstLeg[0] = NULLCHAR;
8645         }
8646
8647         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8648                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8649             /* Machine move could not be parsed; ignore it. */
8650           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8651                     machineMove, _(cps->which));
8652             DisplayMoveError(buf1);
8653             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8654                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8655             if (gameMode == TwoMachinesPlay) {
8656               GameEnds(machineWhite ? BlackWins : WhiteWins,
8657                        buf1, GE_XBOARD);
8658             }
8659             return;
8660         }
8661
8662         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8663         /* So we have to redo legality test with true e.p. status here,  */
8664         /* to make sure an illegal e.p. capture does not slip through,   */
8665         /* to cause a forfeit on a justified illegal-move complaint      */
8666         /* of the opponent.                                              */
8667         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8668            ChessMove moveType;
8669            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8670                              fromY, fromX, toY, toX, promoChar);
8671             if(moveType == IllegalMove) {
8672               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8673                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8674                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8675                            buf1, GE_XBOARD);
8676                 return;
8677            } else if(!appData.fischerCastling)
8678            /* [HGM] Kludge to handle engines that send FRC-style castling
8679               when they shouldn't (like TSCP-Gothic) */
8680            switch(moveType) {
8681              case WhiteASideCastleFR:
8682              case BlackASideCastleFR:
8683                toX+=2;
8684                currentMoveString[2]++;
8685                break;
8686              case WhiteHSideCastleFR:
8687              case BlackHSideCastleFR:
8688                toX--;
8689                currentMoveString[2]--;
8690                break;
8691              default: ; // nothing to do, but suppresses warning of pedantic compilers
8692            }
8693         }
8694         hintRequested = FALSE;
8695         lastHint[0] = NULLCHAR;
8696         bookRequested = FALSE;
8697         /* Program may be pondering now */
8698         cps->maybeThinking = TRUE;
8699         if (cps->sendTime == 2) cps->sendTime = 1;
8700         if (cps->offeredDraw) cps->offeredDraw--;
8701
8702         /* [AS] Save move info*/
8703         pvInfoList[ forwardMostMove ].score = programStats.score;
8704         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8705         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8706
8707         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8708
8709         /* Test suites abort the 'game' after one move */
8710         if(*appData.finger) {
8711            static FILE *f;
8712            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8713            if(!f) f = fopen(appData.finger, "w");
8714            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8715            else { DisplayFatalError("Bad output file", errno, 0); return; }
8716            free(fen);
8717            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8718         }
8719
8720         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8721         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8722             int count = 0;
8723
8724             while( count < adjudicateLossPlies ) {
8725                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8726
8727                 if( count & 1 ) {
8728                     score = -score; /* Flip score for winning side */
8729                 }
8730 printf("score=%d count=%d\n",score,count);
8731                 if( score > appData.adjudicateLossThreshold ) {
8732                     break;
8733                 }
8734
8735                 count++;
8736             }
8737
8738             if( count >= adjudicateLossPlies ) {
8739                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8740
8741                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8742                     "Xboard adjudication",
8743                     GE_XBOARD );
8744
8745                 return;
8746             }
8747         }
8748
8749         if(Adjudicate(cps)) {
8750             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8751             return; // [HGM] adjudicate: for all automatic game ends
8752         }
8753
8754 #if ZIPPY
8755         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8756             first.initDone) {
8757           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8758                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8759                 SendToICS("draw ");
8760                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8761           }
8762           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8763           ics_user_moved = 1;
8764           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8765                 char buf[3*MSG_SIZ];
8766
8767                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8768                         programStats.score / 100.,
8769                         programStats.depth,
8770                         programStats.time / 100.,
8771                         (unsigned int)programStats.nodes,
8772                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8773                         programStats.movelist);
8774                 SendToICS(buf);
8775           }
8776         }
8777 #endif
8778
8779         /* [AS] Clear stats for next move */
8780         ClearProgramStats();
8781         thinkOutput[0] = NULLCHAR;
8782         hiddenThinkOutputState = 0;
8783
8784         bookHit = NULL;
8785         if (gameMode == TwoMachinesPlay) {
8786             /* [HGM] relaying draw offers moved to after reception of move */
8787             /* and interpreting offer as claim if it brings draw condition */
8788             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8789                 SendToProgram("draw\n", cps->other);
8790             }
8791             if (cps->other->sendTime) {
8792                 SendTimeRemaining(cps->other,
8793                                   cps->other->twoMachinesColor[0] == 'w');
8794             }
8795             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8796             if (firstMove && !bookHit) {
8797                 firstMove = FALSE;
8798                 if (cps->other->useColors) {
8799                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8800                 }
8801                 SendToProgram("go\n", cps->other);
8802             }
8803             cps->other->maybeThinking = TRUE;
8804         }
8805
8806         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8807
8808         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8809
8810         if (!pausing && appData.ringBellAfterMoves) {
8811             if(!roar) RingBell();
8812         }
8813
8814         /*
8815          * Reenable menu items that were disabled while
8816          * machine was thinking
8817          */
8818         if (gameMode != TwoMachinesPlay)
8819             SetUserThinkingEnables();
8820
8821         // [HGM] book: after book hit opponent has received move and is now in force mode
8822         // force the book reply into it, and then fake that it outputted this move by jumping
8823         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8824         if(bookHit) {
8825                 static char bookMove[MSG_SIZ]; // a bit generous?
8826
8827                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8828                 strcat(bookMove, bookHit);
8829                 message = bookMove;
8830                 cps = cps->other;
8831                 programStats.nodes = programStats.depth = programStats.time =
8832                 programStats.score = programStats.got_only_move = 0;
8833                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8834
8835                 if(cps->lastPing != cps->lastPong) {
8836                     savedMessage = message; // args for deferred call
8837                     savedState = cps;
8838                     ScheduleDelayedEvent(DeferredBookMove, 10);
8839                     return;
8840                 }
8841                 goto FakeBookMove;
8842         }
8843
8844         return;
8845     }
8846
8847     /* Set special modes for chess engines.  Later something general
8848      *  could be added here; for now there is just one kludge feature,
8849      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8850      *  when "xboard" is given as an interactive command.
8851      */
8852     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8853         cps->useSigint = FALSE;
8854         cps->useSigterm = FALSE;
8855     }
8856     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8857       ParseFeatures(message+8, cps);
8858       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8859     }
8860
8861     if (!strncmp(message, "setup ", 6) && 
8862         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8863           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8864                                         ) { // [HGM] allow first engine to define opening position
8865       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8866       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8867       *buf = NULLCHAR;
8868       if(sscanf(message, "setup (%s", buf) == 1) {
8869         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8870         ASSIGN(appData.pieceToCharTable, buf);
8871       }
8872       if(startedFromSetupPosition) return;
8873       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8874       if(dummy >= 3) {
8875         while(message[s] && message[s++] != ' ');
8876         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8877            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8878             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8879             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8880           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8881           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8882         }
8883       }
8884       ParseFEN(boards[0], &dummy, message+s, FALSE);
8885       DrawPosition(TRUE, boards[0]);
8886       startedFromSetupPosition = TRUE;
8887       return;
8888     }
8889     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8890      * want this, I was asked to put it in, and obliged.
8891      */
8892     if (!strncmp(message, "setboard ", 9)) {
8893         Board initial_position;
8894
8895         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8896
8897         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8898             DisplayError(_("Bad FEN received from engine"), 0);
8899             return ;
8900         } else {
8901            Reset(TRUE, FALSE);
8902            CopyBoard(boards[0], initial_position);
8903            initialRulePlies = FENrulePlies;
8904            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8905            else gameMode = MachinePlaysBlack;
8906            DrawPosition(FALSE, boards[currentMove]);
8907         }
8908         return;
8909     }
8910
8911     /*
8912      * Look for communication commands
8913      */
8914     if (!strncmp(message, "telluser ", 9)) {
8915         if(message[9] == '\\' && message[10] == '\\')
8916             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8917         PlayTellSound();
8918         DisplayNote(message + 9);
8919         return;
8920     }
8921     if (!strncmp(message, "tellusererror ", 14)) {
8922         cps->userError = 1;
8923         if(message[14] == '\\' && message[15] == '\\')
8924             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8925         PlayTellSound();
8926         DisplayError(message + 14, 0);
8927         return;
8928     }
8929     if (!strncmp(message, "tellopponent ", 13)) {
8930       if (appData.icsActive) {
8931         if (loggedOn) {
8932           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8933           SendToICS(buf1);
8934         }
8935       } else {
8936         DisplayNote(message + 13);
8937       }
8938       return;
8939     }
8940     if (!strncmp(message, "tellothers ", 11)) {
8941       if (appData.icsActive) {
8942         if (loggedOn) {
8943           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8944           SendToICS(buf1);
8945         }
8946       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8947       return;
8948     }
8949     if (!strncmp(message, "tellall ", 8)) {
8950       if (appData.icsActive) {
8951         if (loggedOn) {
8952           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8953           SendToICS(buf1);
8954         }
8955       } else {
8956         DisplayNote(message + 8);
8957       }
8958       return;
8959     }
8960     if (strncmp(message, "warning", 7) == 0) {
8961         /* Undocumented feature, use tellusererror in new code */
8962         DisplayError(message, 0);
8963         return;
8964     }
8965     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8966         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8967         strcat(realname, " query");
8968         AskQuestion(realname, buf2, buf1, cps->pr);
8969         return;
8970     }
8971     /* Commands from the engine directly to ICS.  We don't allow these to be
8972      *  sent until we are logged on. Crafty kibitzes have been known to
8973      *  interfere with the login process.
8974      */
8975     if (loggedOn) {
8976         if (!strncmp(message, "tellics ", 8)) {
8977             SendToICS(message + 8);
8978             SendToICS("\n");
8979             return;
8980         }
8981         if (!strncmp(message, "tellicsnoalias ", 15)) {
8982             SendToICS(ics_prefix);
8983             SendToICS(message + 15);
8984             SendToICS("\n");
8985             return;
8986         }
8987         /* The following are for backward compatibility only */
8988         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8989             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8990             SendToICS(ics_prefix);
8991             SendToICS(message);
8992             SendToICS("\n");
8993             return;
8994         }
8995     }
8996     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8997         if(initPing == cps->lastPong) {
8998             if(gameInfo.variant == VariantUnknown) {
8999                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9000                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9001                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9002             }
9003             initPing = -1;
9004         }
9005         return;
9006     }
9007     if(!strncmp(message, "highlight ", 10)) {
9008         if(appData.testLegality && appData.markers) return;
9009         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9010         return;
9011     }
9012     if(!strncmp(message, "click ", 6)) {
9013         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9014         if(appData.testLegality || !appData.oneClick) return;
9015         sscanf(message+6, "%c%d%c", &f, &y, &c);
9016         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9017         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9018         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9019         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9020         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9021         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9022             LeftClick(Release, lastLeftX, lastLeftY);
9023         controlKey  = (c == ',');
9024         LeftClick(Press, x, y);
9025         LeftClick(Release, x, y);
9026         first.highlight = f;
9027         return;
9028     }
9029     /*
9030      * If the move is illegal, cancel it and redraw the board.
9031      * Also deal with other error cases.  Matching is rather loose
9032      * here to accommodate engines written before the spec.
9033      */
9034     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9035         strncmp(message, "Error", 5) == 0) {
9036         if (StrStr(message, "name") ||
9037             StrStr(message, "rating") || StrStr(message, "?") ||
9038             StrStr(message, "result") || StrStr(message, "board") ||
9039             StrStr(message, "bk") || StrStr(message, "computer") ||
9040             StrStr(message, "variant") || StrStr(message, "hint") ||
9041             StrStr(message, "random") || StrStr(message, "depth") ||
9042             StrStr(message, "accepted")) {
9043             return;
9044         }
9045         if (StrStr(message, "protover")) {
9046           /* Program is responding to input, so it's apparently done
9047              initializing, and this error message indicates it is
9048              protocol version 1.  So we don't need to wait any longer
9049              for it to initialize and send feature commands. */
9050           FeatureDone(cps, 1);
9051           cps->protocolVersion = 1;
9052           return;
9053         }
9054         cps->maybeThinking = FALSE;
9055
9056         if (StrStr(message, "draw")) {
9057             /* Program doesn't have "draw" command */
9058             cps->sendDrawOffers = 0;
9059             return;
9060         }
9061         if (cps->sendTime != 1 &&
9062             (StrStr(message, "time") || StrStr(message, "otim"))) {
9063           /* Program apparently doesn't have "time" or "otim" command */
9064           cps->sendTime = 0;
9065           return;
9066         }
9067         if (StrStr(message, "analyze")) {
9068             cps->analysisSupport = FALSE;
9069             cps->analyzing = FALSE;
9070 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9071             EditGameEvent(); // [HGM] try to preserve loaded game
9072             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9073             DisplayError(buf2, 0);
9074             return;
9075         }
9076         if (StrStr(message, "(no matching move)st")) {
9077           /* Special kludge for GNU Chess 4 only */
9078           cps->stKludge = TRUE;
9079           SendTimeControl(cps, movesPerSession, timeControl,
9080                           timeIncrement, appData.searchDepth,
9081                           searchTime);
9082           return;
9083         }
9084         if (StrStr(message, "(no matching move)sd")) {
9085           /* Special kludge for GNU Chess 4 only */
9086           cps->sdKludge = TRUE;
9087           SendTimeControl(cps, movesPerSession, timeControl,
9088                           timeIncrement, appData.searchDepth,
9089                           searchTime);
9090           return;
9091         }
9092         if (!StrStr(message, "llegal")) {
9093             return;
9094         }
9095         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9096             gameMode == IcsIdle) return;
9097         if (forwardMostMove <= backwardMostMove) return;
9098         if (pausing) PauseEvent();
9099       if(appData.forceIllegal) {
9100             // [HGM] illegal: machine refused move; force position after move into it
9101           SendToProgram("force\n", cps);
9102           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9103                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9104                 // when black is to move, while there might be nothing on a2 or black
9105                 // might already have the move. So send the board as if white has the move.
9106                 // But first we must change the stm of the engine, as it refused the last move
9107                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9108                 if(WhiteOnMove(forwardMostMove)) {
9109                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9110                     SendBoard(cps, forwardMostMove); // kludgeless board
9111                 } else {
9112                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9113                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9114                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9115                 }
9116           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9117             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9118                  gameMode == TwoMachinesPlay)
9119               SendToProgram("go\n", cps);
9120             return;
9121       } else
9122         if (gameMode == PlayFromGameFile) {
9123             /* Stop reading this game file */
9124             gameMode = EditGame;
9125             ModeHighlight();
9126         }
9127         /* [HGM] illegal-move claim should forfeit game when Xboard */
9128         /* only passes fully legal moves                            */
9129         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9130             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9131                                 "False illegal-move claim", GE_XBOARD );
9132             return; // do not take back move we tested as valid
9133         }
9134         currentMove = forwardMostMove-1;
9135         DisplayMove(currentMove-1); /* before DisplayMoveError */
9136         SwitchClocks(forwardMostMove-1); // [HGM] race
9137         DisplayBothClocks();
9138         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9139                 parseList[currentMove], _(cps->which));
9140         DisplayMoveError(buf1);
9141         DrawPosition(FALSE, boards[currentMove]);
9142
9143         SetUserThinkingEnables();
9144         return;
9145     }
9146     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9147         /* Program has a broken "time" command that
9148            outputs a string not ending in newline.
9149            Don't use it. */
9150         cps->sendTime = 0;
9151     }
9152     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9153         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9154             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9155     }
9156
9157     /*
9158      * If chess program startup fails, exit with an error message.
9159      * Attempts to recover here are futile. [HGM] Well, we try anyway
9160      */
9161     if ((StrStr(message, "unknown host") != NULL)
9162         || (StrStr(message, "No remote directory") != NULL)
9163         || (StrStr(message, "not found") != NULL)
9164         || (StrStr(message, "No such file") != NULL)
9165         || (StrStr(message, "can't alloc") != NULL)
9166         || (StrStr(message, "Permission denied") != NULL)) {
9167
9168         cps->maybeThinking = FALSE;
9169         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9170                 _(cps->which), cps->program, cps->host, message);
9171         RemoveInputSource(cps->isr);
9172         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9173             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9174             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9175         }
9176         return;
9177     }
9178
9179     /*
9180      * Look for hint output
9181      */
9182     if (sscanf(message, "Hint: %s", buf1) == 1) {
9183         if (cps == &first && hintRequested) {
9184             hintRequested = FALSE;
9185             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9186                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9187                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9188                                     PosFlags(forwardMostMove),
9189                                     fromY, fromX, toY, toX, promoChar, buf1);
9190                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9191                 DisplayInformation(buf2);
9192             } else {
9193                 /* Hint move could not be parsed!? */
9194               snprintf(buf2, sizeof(buf2),
9195                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9196                         buf1, _(cps->which));
9197                 DisplayError(buf2, 0);
9198             }
9199         } else {
9200           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9201         }
9202         return;
9203     }
9204
9205     /*
9206      * Ignore other messages if game is not in progress
9207      */
9208     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9209         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9210
9211     /*
9212      * look for win, lose, draw, or draw offer
9213      */
9214     if (strncmp(message, "1-0", 3) == 0) {
9215         char *p, *q, *r = "";
9216         p = strchr(message, '{');
9217         if (p) {
9218             q = strchr(p, '}');
9219             if (q) {
9220                 *q = NULLCHAR;
9221                 r = p + 1;
9222             }
9223         }
9224         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9225         return;
9226     } else if (strncmp(message, "0-1", 3) == 0) {
9227         char *p, *q, *r = "";
9228         p = strchr(message, '{');
9229         if (p) {
9230             q = strchr(p, '}');
9231             if (q) {
9232                 *q = NULLCHAR;
9233                 r = p + 1;
9234             }
9235         }
9236         /* Kludge for Arasan 4.1 bug */
9237         if (strcmp(r, "Black resigns") == 0) {
9238             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9239             return;
9240         }
9241         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9242         return;
9243     } else if (strncmp(message, "1/2", 3) == 0) {
9244         char *p, *q, *r = "";
9245         p = strchr(message, '{');
9246         if (p) {
9247             q = strchr(p, '}');
9248             if (q) {
9249                 *q = NULLCHAR;
9250                 r = p + 1;
9251             }
9252         }
9253
9254         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9255         return;
9256
9257     } else if (strncmp(message, "White resign", 12) == 0) {
9258         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9259         return;
9260     } else if (strncmp(message, "Black resign", 12) == 0) {
9261         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9262         return;
9263     } else if (strncmp(message, "White matches", 13) == 0 ||
9264                strncmp(message, "Black matches", 13) == 0   ) {
9265         /* [HGM] ignore GNUShogi noises */
9266         return;
9267     } else if (strncmp(message, "White", 5) == 0 &&
9268                message[5] != '(' &&
9269                StrStr(message, "Black") == NULL) {
9270         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9271         return;
9272     } else if (strncmp(message, "Black", 5) == 0 &&
9273                message[5] != '(') {
9274         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9275         return;
9276     } else if (strcmp(message, "resign") == 0 ||
9277                strcmp(message, "computer resigns") == 0) {
9278         switch (gameMode) {
9279           case MachinePlaysBlack:
9280           case IcsPlayingBlack:
9281             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9282             break;
9283           case MachinePlaysWhite:
9284           case IcsPlayingWhite:
9285             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9286             break;
9287           case TwoMachinesPlay:
9288             if (cps->twoMachinesColor[0] == 'w')
9289               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9290             else
9291               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9292             break;
9293           default:
9294             /* can't happen */
9295             break;
9296         }
9297         return;
9298     } else if (strncmp(message, "opponent mates", 14) == 0) {
9299         switch (gameMode) {
9300           case MachinePlaysBlack:
9301           case IcsPlayingBlack:
9302             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9303             break;
9304           case MachinePlaysWhite:
9305           case IcsPlayingWhite:
9306             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9307             break;
9308           case TwoMachinesPlay:
9309             if (cps->twoMachinesColor[0] == 'w')
9310               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9311             else
9312               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9313             break;
9314           default:
9315             /* can't happen */
9316             break;
9317         }
9318         return;
9319     } else if (strncmp(message, "computer mates", 14) == 0) {
9320         switch (gameMode) {
9321           case MachinePlaysBlack:
9322           case IcsPlayingBlack:
9323             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9324             break;
9325           case MachinePlaysWhite:
9326           case IcsPlayingWhite:
9327             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9328             break;
9329           case TwoMachinesPlay:
9330             if (cps->twoMachinesColor[0] == 'w')
9331               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9332             else
9333               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9334             break;
9335           default:
9336             /* can't happen */
9337             break;
9338         }
9339         return;
9340     } else if (strncmp(message, "checkmate", 9) == 0) {
9341         if (WhiteOnMove(forwardMostMove)) {
9342             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9343         } else {
9344             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9345         }
9346         return;
9347     } else if (strstr(message, "Draw") != NULL ||
9348                strstr(message, "game is a draw") != NULL) {
9349         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9350         return;
9351     } else if (strstr(message, "offer") != NULL &&
9352                strstr(message, "draw") != NULL) {
9353 #if ZIPPY
9354         if (appData.zippyPlay && first.initDone) {
9355             /* Relay offer to ICS */
9356             SendToICS(ics_prefix);
9357             SendToICS("draw\n");
9358         }
9359 #endif
9360         cps->offeredDraw = 2; /* valid until this engine moves twice */
9361         if (gameMode == TwoMachinesPlay) {
9362             if (cps->other->offeredDraw) {
9363                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9364             /* [HGM] in two-machine mode we delay relaying draw offer      */
9365             /* until after we also have move, to see if it is really claim */
9366             }
9367         } else if (gameMode == MachinePlaysWhite ||
9368                    gameMode == MachinePlaysBlack) {
9369           if (userOfferedDraw) {
9370             DisplayInformation(_("Machine accepts your draw offer"));
9371             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9372           } else {
9373             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9374           }
9375         }
9376     }
9377
9378
9379     /*
9380      * Look for thinking output
9381      */
9382     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9383           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9384                                 ) {
9385         int plylev, mvleft, mvtot, curscore, time;
9386         char mvname[MOVE_LEN];
9387         u64 nodes; // [DM]
9388         char plyext;
9389         int ignore = FALSE;
9390         int prefixHint = FALSE;
9391         mvname[0] = NULLCHAR;
9392
9393         switch (gameMode) {
9394           case MachinePlaysBlack:
9395           case IcsPlayingBlack:
9396             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9397             break;
9398           case MachinePlaysWhite:
9399           case IcsPlayingWhite:
9400             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9401             break;
9402           case AnalyzeMode:
9403           case AnalyzeFile:
9404             break;
9405           case IcsObserving: /* [DM] icsEngineAnalyze */
9406             if (!appData.icsEngineAnalyze) ignore = TRUE;
9407             break;
9408           case TwoMachinesPlay:
9409             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9410                 ignore = TRUE;
9411             }
9412             break;
9413           default:
9414             ignore = TRUE;
9415             break;
9416         }
9417
9418         if (!ignore) {
9419             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9420             buf1[0] = NULLCHAR;
9421             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9422                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9423
9424                 if (plyext != ' ' && plyext != '\t') {
9425                     time *= 100;
9426                 }
9427
9428                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9429                 if( cps->scoreIsAbsolute &&
9430                     ( gameMode == MachinePlaysBlack ||
9431                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9432                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9433                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9434                      !WhiteOnMove(currentMove)
9435                     ) )
9436                 {
9437                     curscore = -curscore;
9438                 }
9439
9440                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9441
9442                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9443                         char buf[MSG_SIZ];
9444                         FILE *f;
9445                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9446                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9447                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9448                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9449                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9450                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9451                                 fclose(f);
9452                         }
9453                         else
9454                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9455                           DisplayError(_("failed writing PV"), 0);
9456                 }
9457
9458                 tempStats.depth = plylev;
9459                 tempStats.nodes = nodes;
9460                 tempStats.time = time;
9461                 tempStats.score = curscore;
9462                 tempStats.got_only_move = 0;
9463
9464                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9465                         int ticklen;
9466
9467                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9468                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9469                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9470                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9471                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9472                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9473                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9474                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9475                 }
9476
9477                 /* Buffer overflow protection */
9478                 if (pv[0] != NULLCHAR) {
9479                     if (strlen(pv) >= sizeof(tempStats.movelist)
9480                         && appData.debugMode) {
9481                         fprintf(debugFP,
9482                                 "PV is too long; using the first %u bytes.\n",
9483                                 (unsigned) sizeof(tempStats.movelist) - 1);
9484                     }
9485
9486                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9487                 } else {
9488                     sprintf(tempStats.movelist, " no PV\n");
9489                 }
9490
9491                 if (tempStats.seen_stat) {
9492                     tempStats.ok_to_send = 1;
9493                 }
9494
9495                 if (strchr(tempStats.movelist, '(') != NULL) {
9496                     tempStats.line_is_book = 1;
9497                     tempStats.nr_moves = 0;
9498                     tempStats.moves_left = 0;
9499                 } else {
9500                     tempStats.line_is_book = 0;
9501                 }
9502
9503                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9504                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9505
9506                 SendProgramStatsToFrontend( cps, &tempStats );
9507
9508                 /*
9509                     [AS] Protect the thinkOutput buffer from overflow... this
9510                     is only useful if buf1 hasn't overflowed first!
9511                 */
9512                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9513                          plylev,
9514                          (gameMode == TwoMachinesPlay ?
9515                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9516                          ((double) curscore) / 100.0,
9517                          prefixHint ? lastHint : "",
9518                          prefixHint ? " " : "" );
9519
9520                 if( buf1[0] != NULLCHAR ) {
9521                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9522
9523                     if( strlen(pv) > max_len ) {
9524                         if( appData.debugMode) {
9525                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9526                         }
9527                         pv[max_len+1] = '\0';
9528                     }
9529
9530                     strcat( thinkOutput, pv);
9531                 }
9532
9533                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9534                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9535                     DisplayMove(currentMove - 1);
9536                 }
9537                 return;
9538
9539             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9540                 /* crafty (9.25+) says "(only move) <move>"
9541                  * if there is only 1 legal move
9542                  */
9543                 sscanf(p, "(only move) %s", buf1);
9544                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9545                 sprintf(programStats.movelist, "%s (only move)", buf1);
9546                 programStats.depth = 1;
9547                 programStats.nr_moves = 1;
9548                 programStats.moves_left = 1;
9549                 programStats.nodes = 1;
9550                 programStats.time = 1;
9551                 programStats.got_only_move = 1;
9552
9553                 /* Not really, but we also use this member to
9554                    mean "line isn't going to change" (Crafty
9555                    isn't searching, so stats won't change) */
9556                 programStats.line_is_book = 1;
9557
9558                 SendProgramStatsToFrontend( cps, &programStats );
9559
9560                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9561                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9562                     DisplayMove(currentMove - 1);
9563                 }
9564                 return;
9565             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9566                               &time, &nodes, &plylev, &mvleft,
9567                               &mvtot, mvname) >= 5) {
9568                 /* The stat01: line is from Crafty (9.29+) in response
9569                    to the "." command */
9570                 programStats.seen_stat = 1;
9571                 cps->maybeThinking = TRUE;
9572
9573                 if (programStats.got_only_move || !appData.periodicUpdates)
9574                   return;
9575
9576                 programStats.depth = plylev;
9577                 programStats.time = time;
9578                 programStats.nodes = nodes;
9579                 programStats.moves_left = mvleft;
9580                 programStats.nr_moves = mvtot;
9581                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9582                 programStats.ok_to_send = 1;
9583                 programStats.movelist[0] = '\0';
9584
9585                 SendProgramStatsToFrontend( cps, &programStats );
9586
9587                 return;
9588
9589             } else if (strncmp(message,"++",2) == 0) {
9590                 /* Crafty 9.29+ outputs this */
9591                 programStats.got_fail = 2;
9592                 return;
9593
9594             } else if (strncmp(message,"--",2) == 0) {
9595                 /* Crafty 9.29+ outputs this */
9596                 programStats.got_fail = 1;
9597                 return;
9598
9599             } else if (thinkOutput[0] != NULLCHAR &&
9600                        strncmp(message, "    ", 4) == 0) {
9601                 unsigned message_len;
9602
9603                 p = message;
9604                 while (*p && *p == ' ') p++;
9605
9606                 message_len = strlen( p );
9607
9608                 /* [AS] Avoid buffer overflow */
9609                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9610                     strcat(thinkOutput, " ");
9611                     strcat(thinkOutput, p);
9612                 }
9613
9614                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9615                     strcat(programStats.movelist, " ");
9616                     strcat(programStats.movelist, p);
9617                 }
9618
9619                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9620                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9621                     DisplayMove(currentMove - 1);
9622                 }
9623                 return;
9624             }
9625         }
9626         else {
9627             buf1[0] = NULLCHAR;
9628
9629             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9630                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9631             {
9632                 ChessProgramStats cpstats;
9633
9634                 if (plyext != ' ' && plyext != '\t') {
9635                     time *= 100;
9636                 }
9637
9638                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9639                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9640                     curscore = -curscore;
9641                 }
9642
9643                 cpstats.depth = plylev;
9644                 cpstats.nodes = nodes;
9645                 cpstats.time = time;
9646                 cpstats.score = curscore;
9647                 cpstats.got_only_move = 0;
9648                 cpstats.movelist[0] = '\0';
9649
9650                 if (buf1[0] != NULLCHAR) {
9651                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9652                 }
9653
9654                 cpstats.ok_to_send = 0;
9655                 cpstats.line_is_book = 0;
9656                 cpstats.nr_moves = 0;
9657                 cpstats.moves_left = 0;
9658
9659                 SendProgramStatsToFrontend( cps, &cpstats );
9660             }
9661         }
9662     }
9663 }
9664
9665
9666 /* Parse a game score from the character string "game", and
9667    record it as the history of the current game.  The game
9668    score is NOT assumed to start from the standard position.
9669    The display is not updated in any way.
9670    */
9671 void
9672 ParseGameHistory (char *game)
9673 {
9674     ChessMove moveType;
9675     int fromX, fromY, toX, toY, boardIndex;
9676     char promoChar;
9677     char *p, *q;
9678     char buf[MSG_SIZ];
9679
9680     if (appData.debugMode)
9681       fprintf(debugFP, "Parsing game history: %s\n", game);
9682
9683     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9684     gameInfo.site = StrSave(appData.icsHost);
9685     gameInfo.date = PGNDate();
9686     gameInfo.round = StrSave("-");
9687
9688     /* Parse out names of players */
9689     while (*game == ' ') game++;
9690     p = buf;
9691     while (*game != ' ') *p++ = *game++;
9692     *p = NULLCHAR;
9693     gameInfo.white = StrSave(buf);
9694     while (*game == ' ') game++;
9695     p = buf;
9696     while (*game != ' ' && *game != '\n') *p++ = *game++;
9697     *p = NULLCHAR;
9698     gameInfo.black = StrSave(buf);
9699
9700     /* Parse moves */
9701     boardIndex = blackPlaysFirst ? 1 : 0;
9702     yynewstr(game);
9703     for (;;) {
9704         yyboardindex = boardIndex;
9705         moveType = (ChessMove) Myylex();
9706         switch (moveType) {
9707           case IllegalMove:             /* maybe suicide chess, etc. */
9708   if (appData.debugMode) {
9709     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9710     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9711     setbuf(debugFP, NULL);
9712   }
9713           case WhitePromotion:
9714           case BlackPromotion:
9715           case WhiteNonPromotion:
9716           case BlackNonPromotion:
9717           case NormalMove:
9718           case FirstLeg:
9719           case WhiteCapturesEnPassant:
9720           case BlackCapturesEnPassant:
9721           case WhiteKingSideCastle:
9722           case WhiteQueenSideCastle:
9723           case BlackKingSideCastle:
9724           case BlackQueenSideCastle:
9725           case WhiteKingSideCastleWild:
9726           case WhiteQueenSideCastleWild:
9727           case BlackKingSideCastleWild:
9728           case BlackQueenSideCastleWild:
9729           /* PUSH Fabien */
9730           case WhiteHSideCastleFR:
9731           case WhiteASideCastleFR:
9732           case BlackHSideCastleFR:
9733           case BlackASideCastleFR:
9734           /* POP Fabien */
9735             fromX = currentMoveString[0] - AAA;
9736             fromY = currentMoveString[1] - ONE;
9737             toX = currentMoveString[2] - AAA;
9738             toY = currentMoveString[3] - ONE;
9739             promoChar = currentMoveString[4];
9740             break;
9741           case WhiteDrop:
9742           case BlackDrop:
9743             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9744             fromX = moveType == WhiteDrop ?
9745               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9746             (int) CharToPiece(ToLower(currentMoveString[0]));
9747             fromY = DROP_RANK;
9748             toX = currentMoveString[2] - AAA;
9749             toY = currentMoveString[3] - ONE;
9750             promoChar = NULLCHAR;
9751             break;
9752           case AmbiguousMove:
9753             /* bug? */
9754             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9755   if (appData.debugMode) {
9756     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9757     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9758     setbuf(debugFP, NULL);
9759   }
9760             DisplayError(buf, 0);
9761             return;
9762           case ImpossibleMove:
9763             /* bug? */
9764             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9765   if (appData.debugMode) {
9766     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9767     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9768     setbuf(debugFP, NULL);
9769   }
9770             DisplayError(buf, 0);
9771             return;
9772           case EndOfFile:
9773             if (boardIndex < backwardMostMove) {
9774                 /* Oops, gap.  How did that happen? */
9775                 DisplayError(_("Gap in move list"), 0);
9776                 return;
9777             }
9778             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9779             if (boardIndex > forwardMostMove) {
9780                 forwardMostMove = boardIndex;
9781             }
9782             return;
9783           case ElapsedTime:
9784             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9785                 strcat(parseList[boardIndex-1], " ");
9786                 strcat(parseList[boardIndex-1], yy_text);
9787             }
9788             continue;
9789           case Comment:
9790           case PGNTag:
9791           case NAG:
9792           default:
9793             /* ignore */
9794             continue;
9795           case WhiteWins:
9796           case BlackWins:
9797           case GameIsDrawn:
9798           case GameUnfinished:
9799             if (gameMode == IcsExamining) {
9800                 if (boardIndex < backwardMostMove) {
9801                     /* Oops, gap.  How did that happen? */
9802                     return;
9803                 }
9804                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9805                 return;
9806             }
9807             gameInfo.result = moveType;
9808             p = strchr(yy_text, '{');
9809             if (p == NULL) p = strchr(yy_text, '(');
9810             if (p == NULL) {
9811                 p = yy_text;
9812                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9813             } else {
9814                 q = strchr(p, *p == '{' ? '}' : ')');
9815                 if (q != NULL) *q = NULLCHAR;
9816                 p++;
9817             }
9818             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9819             gameInfo.resultDetails = StrSave(p);
9820             continue;
9821         }
9822         if (boardIndex >= forwardMostMove &&
9823             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9824             backwardMostMove = blackPlaysFirst ? 1 : 0;
9825             return;
9826         }
9827         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9828                                  fromY, fromX, toY, toX, promoChar,
9829                                  parseList[boardIndex]);
9830         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9831         /* currentMoveString is set as a side-effect of yylex */
9832         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9833         strcat(moveList[boardIndex], "\n");
9834         boardIndex++;
9835         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9836         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9837           case MT_NONE:
9838           case MT_STALEMATE:
9839           default:
9840             break;
9841           case MT_CHECK:
9842             if(!IS_SHOGI(gameInfo.variant))
9843                 strcat(parseList[boardIndex - 1], "+");
9844             break;
9845           case MT_CHECKMATE:
9846           case MT_STAINMATE:
9847             strcat(parseList[boardIndex - 1], "#");
9848             break;
9849         }
9850     }
9851 }
9852
9853
9854 /* Apply a move to the given board  */
9855 void
9856 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9857 {
9858   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9859   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9860
9861     /* [HGM] compute & store e.p. status and castling rights for new position */
9862     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9863
9864       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9865       oldEP = (signed char)board[EP_STATUS];
9866       board[EP_STATUS] = EP_NONE;
9867
9868   if (fromY == DROP_RANK) {
9869         /* must be first */
9870         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9871             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9872             return;
9873         }
9874         piece = board[toY][toX] = (ChessSquare) fromX;
9875   } else {
9876 //      ChessSquare victim;
9877       int i;
9878
9879       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9880 //           victim = board[killY][killX],
9881            board[killY][killX] = EmptySquare,
9882            board[EP_STATUS] = EP_CAPTURE;
9883
9884       if( board[toY][toX] != EmptySquare ) {
9885            board[EP_STATUS] = EP_CAPTURE;
9886            if( (fromX != toX || fromY != toY) && // not igui!
9887                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9888                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9889                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9890            }
9891       }
9892
9893       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9894            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9895                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9896       } else
9897       if( board[fromY][fromX] == WhitePawn ) {
9898            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9899                board[EP_STATUS] = EP_PAWN_MOVE;
9900            if( toY-fromY==2) {
9901                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9902                         gameInfo.variant != VariantBerolina || toX < fromX)
9903                       board[EP_STATUS] = toX | berolina;
9904                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9905                         gameInfo.variant != VariantBerolina || toX > fromX)
9906                       board[EP_STATUS] = toX;
9907            }
9908       } else
9909       if( board[fromY][fromX] == BlackPawn ) {
9910            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9911                board[EP_STATUS] = EP_PAWN_MOVE;
9912            if( toY-fromY== -2) {
9913                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9914                         gameInfo.variant != VariantBerolina || toX < fromX)
9915                       board[EP_STATUS] = toX | berolina;
9916                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9917                         gameInfo.variant != VariantBerolina || toX > fromX)
9918                       board[EP_STATUS] = toX;
9919            }
9920        }
9921
9922        for(i=0; i<nrCastlingRights; i++) {
9923            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9924               board[CASTLING][i] == toX   && castlingRank[i] == toY
9925              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9926        }
9927
9928        if(gameInfo.variant == VariantSChess) { // update virginity
9929            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9930            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9931            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9932            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9933        }
9934
9935      if (fromX == toX && fromY == toY) return;
9936
9937      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9938      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9939      if(gameInfo.variant == VariantKnightmate)
9940          king += (int) WhiteUnicorn - (int) WhiteKing;
9941
9942     /* Code added by Tord: */
9943     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9944     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9945         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9946       board[fromY][fromX] = EmptySquare;
9947       board[toY][toX] = EmptySquare;
9948       if((toX > fromX) != (piece == WhiteRook)) {
9949         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9950       } else {
9951         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9952       }
9953     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9954                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9955       board[fromY][fromX] = EmptySquare;
9956       board[toY][toX] = EmptySquare;
9957       if((toX > fromX) != (piece == BlackRook)) {
9958         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9959       } else {
9960         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9961       }
9962     /* End of code added by Tord */
9963
9964     } else if (board[fromY][fromX] == king
9965         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9966         && toY == fromY && toX > fromX+1) {
9967         board[fromY][fromX] = EmptySquare;
9968         board[toY][toX] = king;
9969         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9970         board[fromY][BOARD_RGHT-1] = EmptySquare;
9971     } else if (board[fromY][fromX] == king
9972         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9973                && toY == fromY && toX < fromX-1) {
9974         board[fromY][fromX] = EmptySquare;
9975         board[toY][toX] = king;
9976         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9977         board[fromY][BOARD_LEFT] = EmptySquare;
9978     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9979                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9980                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9981                ) {
9982         /* white pawn promotion */
9983         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9984         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9985             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9986         board[fromY][fromX] = EmptySquare;
9987     } else if ((fromY >= BOARD_HEIGHT>>1)
9988                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9989                && (toX != fromX)
9990                && gameInfo.variant != VariantXiangqi
9991                && gameInfo.variant != VariantBerolina
9992                && (board[fromY][fromX] == WhitePawn)
9993                && (board[toY][toX] == EmptySquare)) {
9994         board[fromY][fromX] = EmptySquare;
9995         board[toY][toX] = WhitePawn;
9996         captured = board[toY - 1][toX];
9997         board[toY - 1][toX] = EmptySquare;
9998     } else if ((fromY == BOARD_HEIGHT-4)
9999                && (toX == fromX)
10000                && gameInfo.variant == VariantBerolina
10001                && (board[fromY][fromX] == WhitePawn)
10002                && (board[toY][toX] == EmptySquare)) {
10003         board[fromY][fromX] = EmptySquare;
10004         board[toY][toX] = WhitePawn;
10005         if(oldEP & EP_BEROLIN_A) {
10006                 captured = board[fromY][fromX-1];
10007                 board[fromY][fromX-1] = EmptySquare;
10008         }else{  captured = board[fromY][fromX+1];
10009                 board[fromY][fromX+1] = EmptySquare;
10010         }
10011     } else if (board[fromY][fromX] == king
10012         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10013                && toY == fromY && toX > fromX+1) {
10014         board[fromY][fromX] = EmptySquare;
10015         board[toY][toX] = king;
10016         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10017         board[fromY][BOARD_RGHT-1] = EmptySquare;
10018     } else if (board[fromY][fromX] == king
10019         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10020                && toY == fromY && toX < fromX-1) {
10021         board[fromY][fromX] = EmptySquare;
10022         board[toY][toX] = king;
10023         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10024         board[fromY][BOARD_LEFT] = EmptySquare;
10025     } else if (fromY == 7 && fromX == 3
10026                && board[fromY][fromX] == BlackKing
10027                && toY == 7 && toX == 5) {
10028         board[fromY][fromX] = EmptySquare;
10029         board[toY][toX] = BlackKing;
10030         board[fromY][7] = EmptySquare;
10031         board[toY][4] = BlackRook;
10032     } else if (fromY == 7 && fromX == 3
10033                && board[fromY][fromX] == BlackKing
10034                && toY == 7 && toX == 1) {
10035         board[fromY][fromX] = EmptySquare;
10036         board[toY][toX] = BlackKing;
10037         board[fromY][0] = EmptySquare;
10038         board[toY][2] = BlackRook;
10039     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10040                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10041                && toY < promoRank && promoChar
10042                ) {
10043         /* black pawn promotion */
10044         board[toY][toX] = CharToPiece(ToLower(promoChar));
10045         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10046             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10047         board[fromY][fromX] = EmptySquare;
10048     } else if ((fromY < BOARD_HEIGHT>>1)
10049                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10050                && (toX != fromX)
10051                && gameInfo.variant != VariantXiangqi
10052                && gameInfo.variant != VariantBerolina
10053                && (board[fromY][fromX] == BlackPawn)
10054                && (board[toY][toX] == EmptySquare)) {
10055         board[fromY][fromX] = EmptySquare;
10056         board[toY][toX] = BlackPawn;
10057         captured = board[toY + 1][toX];
10058         board[toY + 1][toX] = EmptySquare;
10059     } else if ((fromY == 3)
10060                && (toX == fromX)
10061                && gameInfo.variant == VariantBerolina
10062                && (board[fromY][fromX] == BlackPawn)
10063                && (board[toY][toX] == EmptySquare)) {
10064         board[fromY][fromX] = EmptySquare;
10065         board[toY][toX] = BlackPawn;
10066         if(oldEP & EP_BEROLIN_A) {
10067                 captured = board[fromY][fromX-1];
10068                 board[fromY][fromX-1] = EmptySquare;
10069         }else{  captured = board[fromY][fromX+1];
10070                 board[fromY][fromX+1] = EmptySquare;
10071         }
10072     } else {
10073         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10074         board[fromY][fromX] = EmptySquare;
10075         board[toY][toX] = piece;
10076     }
10077   }
10078
10079     if (gameInfo.holdingsWidth != 0) {
10080
10081       /* !!A lot more code needs to be written to support holdings  */
10082       /* [HGM] OK, so I have written it. Holdings are stored in the */
10083       /* penultimate board files, so they are automaticlly stored   */
10084       /* in the game history.                                       */
10085       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10086                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10087         /* Delete from holdings, by decreasing count */
10088         /* and erasing image if necessary            */
10089         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10090         if(p < (int) BlackPawn) { /* white drop */
10091              p -= (int)WhitePawn;
10092                  p = PieceToNumber((ChessSquare)p);
10093              if(p >= gameInfo.holdingsSize) p = 0;
10094              if(--board[p][BOARD_WIDTH-2] <= 0)
10095                   board[p][BOARD_WIDTH-1] = EmptySquare;
10096              if((int)board[p][BOARD_WIDTH-2] < 0)
10097                         board[p][BOARD_WIDTH-2] = 0;
10098         } else {                  /* black drop */
10099              p -= (int)BlackPawn;
10100                  p = PieceToNumber((ChessSquare)p);
10101              if(p >= gameInfo.holdingsSize) p = 0;
10102              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10103                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10104              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10105                         board[BOARD_HEIGHT-1-p][1] = 0;
10106         }
10107       }
10108       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10109           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10110         /* [HGM] holdings: Add to holdings, if holdings exist */
10111         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10112                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10113                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10114         }
10115         p = (int) captured;
10116         if (p >= (int) BlackPawn) {
10117           p -= (int)BlackPawn;
10118           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10119                   /* in Shogi restore piece to its original  first */
10120                   captured = (ChessSquare) (DEMOTED captured);
10121                   p = DEMOTED p;
10122           }
10123           p = PieceToNumber((ChessSquare)p);
10124           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10125           board[p][BOARD_WIDTH-2]++;
10126           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10127         } else {
10128           p -= (int)WhitePawn;
10129           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10130                   captured = (ChessSquare) (DEMOTED captured);
10131                   p = DEMOTED p;
10132           }
10133           p = PieceToNumber((ChessSquare)p);
10134           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10135           board[BOARD_HEIGHT-1-p][1]++;
10136           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10137         }
10138       }
10139     } else if (gameInfo.variant == VariantAtomic) {
10140       if (captured != EmptySquare) {
10141         int y, x;
10142         for (y = toY-1; y <= toY+1; y++) {
10143           for (x = toX-1; x <= toX+1; x++) {
10144             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10145                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10146               board[y][x] = EmptySquare;
10147             }
10148           }
10149         }
10150         board[toY][toX] = EmptySquare;
10151       }
10152     }
10153
10154     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10155         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10156     } else
10157     if(promoChar == '+') {
10158         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10159         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10160         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10161           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10162     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10163         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10164         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10165            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10166         board[toY][toX] = newPiece;
10167     }
10168     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10169                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10170         // [HGM] superchess: take promotion piece out of holdings
10171         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10172         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10173             if(!--board[k][BOARD_WIDTH-2])
10174                 board[k][BOARD_WIDTH-1] = EmptySquare;
10175         } else {
10176             if(!--board[BOARD_HEIGHT-1-k][1])
10177                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10178         }
10179     }
10180 }
10181
10182 /* Updates forwardMostMove */
10183 void
10184 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10185 {
10186     int x = toX, y = toY;
10187     char *s = parseList[forwardMostMove];
10188     ChessSquare p = boards[forwardMostMove][toY][toX];
10189 //    forwardMostMove++; // [HGM] bare: moved downstream
10190
10191     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10192     (void) CoordsToAlgebraic(boards[forwardMostMove],
10193                              PosFlags(forwardMostMove),
10194                              fromY, fromX, y, x, promoChar,
10195                              s);
10196     if(killX >= 0 && killY >= 0)
10197         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10198
10199     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10200         int timeLeft; static int lastLoadFlag=0; int king, piece;
10201         piece = boards[forwardMostMove][fromY][fromX];
10202         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10203         if(gameInfo.variant == VariantKnightmate)
10204             king += (int) WhiteUnicorn - (int) WhiteKing;
10205         if(forwardMostMove == 0) {
10206             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10207                 fprintf(serverMoves, "%s;", UserName());
10208             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10209                 fprintf(serverMoves, "%s;", second.tidy);
10210             fprintf(serverMoves, "%s;", first.tidy);
10211             if(gameMode == MachinePlaysWhite)
10212                 fprintf(serverMoves, "%s;", UserName());
10213             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10214                 fprintf(serverMoves, "%s;", second.tidy);
10215         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10216         lastLoadFlag = loadFlag;
10217         // print base move
10218         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10219         // print castling suffix
10220         if( toY == fromY && piece == king ) {
10221             if(toX-fromX > 1)
10222                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10223             if(fromX-toX >1)
10224                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10225         }
10226         // e.p. suffix
10227         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10228              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10229              boards[forwardMostMove][toY][toX] == EmptySquare
10230              && fromX != toX && fromY != toY)
10231                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10232         // promotion suffix
10233         if(promoChar != NULLCHAR) {
10234             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10235                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10236                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10237             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10238         }
10239         if(!loadFlag) {
10240                 char buf[MOVE_LEN*2], *p; int len;
10241             fprintf(serverMoves, "/%d/%d",
10242                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10243             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10244             else                      timeLeft = blackTimeRemaining/1000;
10245             fprintf(serverMoves, "/%d", timeLeft);
10246                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10247                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10248                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10249                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10250             fprintf(serverMoves, "/%s", buf);
10251         }
10252         fflush(serverMoves);
10253     }
10254
10255     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10256         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10257       return;
10258     }
10259     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10260     if (commentList[forwardMostMove+1] != NULL) {
10261         free(commentList[forwardMostMove+1]);
10262         commentList[forwardMostMove+1] = NULL;
10263     }
10264     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10265     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10266     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10267     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10268     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10269     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10270     adjustedClock = FALSE;
10271     gameInfo.result = GameUnfinished;
10272     if (gameInfo.resultDetails != NULL) {
10273         free(gameInfo.resultDetails);
10274         gameInfo.resultDetails = NULL;
10275     }
10276     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10277                               moveList[forwardMostMove - 1]);
10278     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10279       case MT_NONE:
10280       case MT_STALEMATE:
10281       default:
10282         break;
10283       case MT_CHECK:
10284         if(!IS_SHOGI(gameInfo.variant))
10285             strcat(parseList[forwardMostMove - 1], "+");
10286         break;
10287       case MT_CHECKMATE:
10288       case MT_STAINMATE:
10289         strcat(parseList[forwardMostMove - 1], "#");
10290         break;
10291     }
10292 }
10293
10294 /* Updates currentMove if not pausing */
10295 void
10296 ShowMove (int fromX, int fromY, int toX, int toY)
10297 {
10298     int instant = (gameMode == PlayFromGameFile) ?
10299         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10300     if(appData.noGUI) return;
10301     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10302         if (!instant) {
10303             if (forwardMostMove == currentMove + 1) {
10304                 AnimateMove(boards[forwardMostMove - 1],
10305                             fromX, fromY, toX, toY);
10306             }
10307         }
10308         currentMove = forwardMostMove;
10309     }
10310
10311     killX = killY = -1; // [HGM] lion: used up
10312
10313     if (instant) return;
10314
10315     DisplayMove(currentMove - 1);
10316     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10317             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10318                 SetHighlights(fromX, fromY, toX, toY);
10319             }
10320     }
10321     DrawPosition(FALSE, boards[currentMove]);
10322     DisplayBothClocks();
10323     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10324 }
10325
10326 void
10327 SendEgtPath (ChessProgramState *cps)
10328 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10329         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10330
10331         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10332
10333         while(*p) {
10334             char c, *q = name+1, *r, *s;
10335
10336             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10337             while(*p && *p != ',') *q++ = *p++;
10338             *q++ = ':'; *q = 0;
10339             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10340                 strcmp(name, ",nalimov:") == 0 ) {
10341                 // take nalimov path from the menu-changeable option first, if it is defined
10342               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10343                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10344             } else
10345             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10346                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10347                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10348                 s = r = StrStr(s, ":") + 1; // beginning of path info
10349                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10350                 c = *r; *r = 0;             // temporarily null-terminate path info
10351                     *--q = 0;               // strip of trailig ':' from name
10352                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10353                 *r = c;
10354                 SendToProgram(buf,cps);     // send egtbpath command for this format
10355             }
10356             if(*p == ',') p++; // read away comma to position for next format name
10357         }
10358 }
10359
10360 static int
10361 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10362 {
10363       int width = 8, height = 8, holdings = 0;             // most common sizes
10364       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10365       // correct the deviations default for each variant
10366       if( v == VariantXiangqi ) width = 9,  height = 10;
10367       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10368       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10369       if( v == VariantCapablanca || v == VariantCapaRandom ||
10370           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10371                                 width = 10;
10372       if( v == VariantCourier ) width = 12;
10373       if( v == VariantSuper )                            holdings = 8;
10374       if( v == VariantGreat )   width = 10,              holdings = 8;
10375       if( v == VariantSChess )                           holdings = 7;
10376       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10377       if( v == VariantChuChess) width = 10, height = 10;
10378       if( v == VariantChu )     width = 12, height = 12;
10379       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10380              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10381              holdingsSize >= 0 && holdingsSize != holdings;
10382 }
10383
10384 char variantError[MSG_SIZ];
10385
10386 char *
10387 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10388 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10389       char *p, *variant = VariantName(v);
10390       static char b[MSG_SIZ];
10391       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10392            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10393                                                holdingsSize, variant); // cook up sized variant name
10394            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10395            if(StrStr(list, b) == NULL) {
10396                // specific sized variant not known, check if general sizing allowed
10397                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10398                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10399                             boardWidth, boardHeight, holdingsSize, engine);
10400                    return NULL;
10401                }
10402                /* [HGM] here we really should compare with the maximum supported board size */
10403            }
10404       } else snprintf(b, MSG_SIZ,"%s", variant);
10405       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10406       p = StrStr(list, b);
10407       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10408       if(p == NULL) {
10409           // occurs not at all in list, or only as sub-string
10410           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10411           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10412               int l = strlen(variantError);
10413               char *q;
10414               while(p != list && p[-1] != ',') p--;
10415               q = strchr(p, ',');
10416               if(q) *q = NULLCHAR;
10417               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10418               if(q) *q= ',';
10419           }
10420           return NULL;
10421       }
10422       return b;
10423 }
10424
10425 void
10426 InitChessProgram (ChessProgramState *cps, int setup)
10427 /* setup needed to setup FRC opening position */
10428 {
10429     char buf[MSG_SIZ], *b;
10430     if (appData.noChessProgram) return;
10431     hintRequested = FALSE;
10432     bookRequested = FALSE;
10433
10434     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10435     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10436     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10437     if(cps->memSize) { /* [HGM] memory */
10438       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10439         SendToProgram(buf, cps);
10440     }
10441     SendEgtPath(cps); /* [HGM] EGT */
10442     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10443       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10444         SendToProgram(buf, cps);
10445     }
10446
10447     setboardSpoiledMachineBlack = FALSE;
10448     SendToProgram(cps->initString, cps);
10449     if (gameInfo.variant != VariantNormal &&
10450         gameInfo.variant != VariantLoadable
10451         /* [HGM] also send variant if board size non-standard */
10452         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10453
10454       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10455                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10456       if (b == NULL) {
10457         DisplayFatalError(variantError, 0, 1);
10458         return;
10459       }
10460
10461       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10462       SendToProgram(buf, cps);
10463     }
10464     currentlyInitializedVariant = gameInfo.variant;
10465
10466     /* [HGM] send opening position in FRC to first engine */
10467     if(setup) {
10468           SendToProgram("force\n", cps);
10469           SendBoard(cps, 0);
10470           /* engine is now in force mode! Set flag to wake it up after first move. */
10471           setboardSpoiledMachineBlack = 1;
10472     }
10473
10474     if (cps->sendICS) {
10475       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10476       SendToProgram(buf, cps);
10477     }
10478     cps->maybeThinking = FALSE;
10479     cps->offeredDraw = 0;
10480     if (!appData.icsActive) {
10481         SendTimeControl(cps, movesPerSession, timeControl,
10482                         timeIncrement, appData.searchDepth,
10483                         searchTime);
10484     }
10485     if (appData.showThinking
10486         // [HGM] thinking: four options require thinking output to be sent
10487         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10488                                 ) {
10489         SendToProgram("post\n", cps);
10490     }
10491     SendToProgram("hard\n", cps);
10492     if (!appData.ponderNextMove) {
10493         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10494            it without being sure what state we are in first.  "hard"
10495            is not a toggle, so that one is OK.
10496          */
10497         SendToProgram("easy\n", cps);
10498     }
10499     if (cps->usePing) {
10500       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10501       SendToProgram(buf, cps);
10502     }
10503     cps->initDone = TRUE;
10504     ClearEngineOutputPane(cps == &second);
10505 }
10506
10507
10508 void
10509 ResendOptions (ChessProgramState *cps)
10510 { // send the stored value of the options
10511   int i;
10512   char buf[MSG_SIZ];
10513   Option *opt = cps->option;
10514   for(i=0; i<cps->nrOptions; i++, opt++) {
10515       switch(opt->type) {
10516         case Spin:
10517         case Slider:
10518         case CheckBox:
10519             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10520           break;
10521         case ComboBox:
10522           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10523           break;
10524         default:
10525             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10526           break;
10527         case Button:
10528         case SaveButton:
10529           continue;
10530       }
10531       SendToProgram(buf, cps);
10532   }
10533 }
10534
10535 void
10536 StartChessProgram (ChessProgramState *cps)
10537 {
10538     char buf[MSG_SIZ];
10539     int err;
10540
10541     if (appData.noChessProgram) return;
10542     cps->initDone = FALSE;
10543
10544     if (strcmp(cps->host, "localhost") == 0) {
10545         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10546     } else if (*appData.remoteShell == NULLCHAR) {
10547         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10548     } else {
10549         if (*appData.remoteUser == NULLCHAR) {
10550           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10551                     cps->program);
10552         } else {
10553           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10554                     cps->host, appData.remoteUser, cps->program);
10555         }
10556         err = StartChildProcess(buf, "", &cps->pr);
10557     }
10558
10559     if (err != 0) {
10560       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10561         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10562         if(cps != &first) return;
10563         appData.noChessProgram = TRUE;
10564         ThawUI();
10565         SetNCPMode();
10566 //      DisplayFatalError(buf, err, 1);
10567 //      cps->pr = NoProc;
10568 //      cps->isr = NULL;
10569         return;
10570     }
10571
10572     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10573     if (cps->protocolVersion > 1) {
10574       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10575       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10576         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10577         cps->comboCnt = 0;  //                and values of combo boxes
10578       }
10579       SendToProgram(buf, cps);
10580       if(cps->reload) ResendOptions(cps);
10581     } else {
10582       SendToProgram("xboard\n", cps);
10583     }
10584 }
10585
10586 void
10587 TwoMachinesEventIfReady P((void))
10588 {
10589   static int curMess = 0;
10590   if (first.lastPing != first.lastPong) {
10591     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10592     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10593     return;
10594   }
10595   if (second.lastPing != second.lastPong) {
10596     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10597     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10598     return;
10599   }
10600   DisplayMessage("", ""); curMess = 0;
10601   TwoMachinesEvent();
10602 }
10603
10604 char *
10605 MakeName (char *template)
10606 {
10607     time_t clock;
10608     struct tm *tm;
10609     static char buf[MSG_SIZ];
10610     char *p = buf;
10611     int i;
10612
10613     clock = time((time_t *)NULL);
10614     tm = localtime(&clock);
10615
10616     while(*p++ = *template++) if(p[-1] == '%') {
10617         switch(*template++) {
10618           case 0:   *p = 0; return buf;
10619           case 'Y': i = tm->tm_year+1900; break;
10620           case 'y': i = tm->tm_year-100; break;
10621           case 'M': i = tm->tm_mon+1; break;
10622           case 'd': i = tm->tm_mday; break;
10623           case 'h': i = tm->tm_hour; break;
10624           case 'm': i = tm->tm_min; break;
10625           case 's': i = tm->tm_sec; break;
10626           default:  i = 0;
10627         }
10628         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10629     }
10630     return buf;
10631 }
10632
10633 int
10634 CountPlayers (char *p)
10635 {
10636     int n = 0;
10637     while(p = strchr(p, '\n')) p++, n++; // count participants
10638     return n;
10639 }
10640
10641 FILE *
10642 WriteTourneyFile (char *results, FILE *f)
10643 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10644     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10645     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10646         // create a file with tournament description
10647         fprintf(f, "-participants {%s}\n", appData.participants);
10648         fprintf(f, "-seedBase %d\n", appData.seedBase);
10649         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10650         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10651         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10652         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10653         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10654         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10655         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10656         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10657         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10658         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10659         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10660         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10661         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10662         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10663         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10664         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10665         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10666         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10667         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10668         fprintf(f, "-smpCores %d\n", appData.smpCores);
10669         if(searchTime > 0)
10670                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10671         else {
10672                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10673                 fprintf(f, "-tc %s\n", appData.timeControl);
10674                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10675         }
10676         fprintf(f, "-results \"%s\"\n", results);
10677     }
10678     return f;
10679 }
10680
10681 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10682
10683 void
10684 Substitute (char *participants, int expunge)
10685 {
10686     int i, changed, changes=0, nPlayers=0;
10687     char *p, *q, *r, buf[MSG_SIZ];
10688     if(participants == NULL) return;
10689     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10690     r = p = participants; q = appData.participants;
10691     while(*p && *p == *q) {
10692         if(*p == '\n') r = p+1, nPlayers++;
10693         p++; q++;
10694     }
10695     if(*p) { // difference
10696         while(*p && *p++ != '\n');
10697         while(*q && *q++ != '\n');
10698       changed = nPlayers;
10699         changes = 1 + (strcmp(p, q) != 0);
10700     }
10701     if(changes == 1) { // a single engine mnemonic was changed
10702         q = r; while(*q) nPlayers += (*q++ == '\n');
10703         p = buf; while(*r && (*p = *r++) != '\n') p++;
10704         *p = NULLCHAR;
10705         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10706         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10707         if(mnemonic[i]) { // The substitute is valid
10708             FILE *f;
10709             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10710                 flock(fileno(f), LOCK_EX);
10711                 ParseArgsFromFile(f);
10712                 fseek(f, 0, SEEK_SET);
10713                 FREE(appData.participants); appData.participants = participants;
10714                 if(expunge) { // erase results of replaced engine
10715                     int len = strlen(appData.results), w, b, dummy;
10716                     for(i=0; i<len; i++) {
10717                         Pairing(i, nPlayers, &w, &b, &dummy);
10718                         if((w == changed || b == changed) && appData.results[i] == '*') {
10719                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10720                             fclose(f);
10721                             return;
10722                         }
10723                     }
10724                     for(i=0; i<len; i++) {
10725                         Pairing(i, nPlayers, &w, &b, &dummy);
10726                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10727                     }
10728                 }
10729                 WriteTourneyFile(appData.results, f);
10730                 fclose(f); // release lock
10731                 return;
10732             }
10733         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10734     }
10735     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10736     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10737     free(participants);
10738     return;
10739 }
10740
10741 int
10742 CheckPlayers (char *participants)
10743 {
10744         int i;
10745         char buf[MSG_SIZ], *p;
10746         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10747         while(p = strchr(participants, '\n')) {
10748             *p = NULLCHAR;
10749             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10750             if(!mnemonic[i]) {
10751                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10752                 *p = '\n';
10753                 DisplayError(buf, 0);
10754                 return 1;
10755             }
10756             *p = '\n';
10757             participants = p + 1;
10758         }
10759         return 0;
10760 }
10761
10762 int
10763 CreateTourney (char *name)
10764 {
10765         FILE *f;
10766         if(matchMode && strcmp(name, appData.tourneyFile)) {
10767              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10768         }
10769         if(name[0] == NULLCHAR) {
10770             if(appData.participants[0])
10771                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10772             return 0;
10773         }
10774         f = fopen(name, "r");
10775         if(f) { // file exists
10776             ASSIGN(appData.tourneyFile, name);
10777             ParseArgsFromFile(f); // parse it
10778         } else {
10779             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10780             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10781                 DisplayError(_("Not enough participants"), 0);
10782                 return 0;
10783             }
10784             if(CheckPlayers(appData.participants)) return 0;
10785             ASSIGN(appData.tourneyFile, name);
10786             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10787             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10788         }
10789         fclose(f);
10790         appData.noChessProgram = FALSE;
10791         appData.clockMode = TRUE;
10792         SetGNUMode();
10793         return 1;
10794 }
10795
10796 int
10797 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10798 {
10799     char buf[MSG_SIZ], *p, *q;
10800     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10801     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10802     skip = !all && group[0]; // if group requested, we start in skip mode
10803     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10804         p = names; q = buf; header = 0;
10805         while(*p && *p != '\n') *q++ = *p++;
10806         *q = 0;
10807         if(*p == '\n') p++;
10808         if(buf[0] == '#') {
10809             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10810             depth++; // we must be entering a new group
10811             if(all) continue; // suppress printing group headers when complete list requested
10812             header = 1;
10813             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10814         }
10815         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10816         if(engineList[i]) free(engineList[i]);
10817         engineList[i] = strdup(buf);
10818         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10819         if(engineMnemonic[i]) free(engineMnemonic[i]);
10820         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10821             strcat(buf, " (");
10822             sscanf(q + 8, "%s", buf + strlen(buf));
10823             strcat(buf, ")");
10824         }
10825         engineMnemonic[i] = strdup(buf);
10826         i++;
10827     }
10828     engineList[i] = engineMnemonic[i] = NULL;
10829     return i;
10830 }
10831
10832 // following implemented as macro to avoid type limitations
10833 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10834
10835 void
10836 SwapEngines (int n)
10837 {   // swap settings for first engine and other engine (so far only some selected options)
10838     int h;
10839     char *p;
10840     if(n == 0) return;
10841     SWAP(directory, p)
10842     SWAP(chessProgram, p)
10843     SWAP(isUCI, h)
10844     SWAP(hasOwnBookUCI, h)
10845     SWAP(protocolVersion, h)
10846     SWAP(reuse, h)
10847     SWAP(scoreIsAbsolute, h)
10848     SWAP(timeOdds, h)
10849     SWAP(logo, p)
10850     SWAP(pgnName, p)
10851     SWAP(pvSAN, h)
10852     SWAP(engOptions, p)
10853     SWAP(engInitString, p)
10854     SWAP(computerString, p)
10855     SWAP(features, p)
10856     SWAP(fenOverride, p)
10857     SWAP(NPS, h)
10858     SWAP(accumulateTC, h)
10859     SWAP(drawDepth, h)
10860     SWAP(host, p)
10861     SWAP(pseudo, h)
10862 }
10863
10864 int
10865 GetEngineLine (char *s, int n)
10866 {
10867     int i;
10868     char buf[MSG_SIZ];
10869     extern char *icsNames;
10870     if(!s || !*s) return 0;
10871     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10872     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10873     if(!mnemonic[i]) return 0;
10874     if(n == 11) return 1; // just testing if there was a match
10875     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10876     if(n == 1) SwapEngines(n);
10877     ParseArgsFromString(buf);
10878     if(n == 1) SwapEngines(n);
10879     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10880         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10881         ParseArgsFromString(buf);
10882     }
10883     return 1;
10884 }
10885
10886 int
10887 SetPlayer (int player, char *p)
10888 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10889     int i;
10890     char buf[MSG_SIZ], *engineName;
10891     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10892     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10893     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10894     if(mnemonic[i]) {
10895         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10896         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10897         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10898         ParseArgsFromString(buf);
10899     } else { // no engine with this nickname is installed!
10900         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10901         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10902         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10903         ModeHighlight();
10904         DisplayError(buf, 0);
10905         return 0;
10906     }
10907     free(engineName);
10908     return i;
10909 }
10910
10911 char *recentEngines;
10912
10913 void
10914 RecentEngineEvent (int nr)
10915 {
10916     int n;
10917 //    SwapEngines(1); // bump first to second
10918 //    ReplaceEngine(&second, 1); // and load it there
10919     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10920     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10921     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10922         ReplaceEngine(&first, 0);
10923         FloatToFront(&appData.recentEngineList, command[n]);
10924     }
10925 }
10926
10927 int
10928 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10929 {   // determine players from game number
10930     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10931
10932     if(appData.tourneyType == 0) {
10933         roundsPerCycle = (nPlayers - 1) | 1;
10934         pairingsPerRound = nPlayers / 2;
10935     } else if(appData.tourneyType > 0) {
10936         roundsPerCycle = nPlayers - appData.tourneyType;
10937         pairingsPerRound = appData.tourneyType;
10938     }
10939     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10940     gamesPerCycle = gamesPerRound * roundsPerCycle;
10941     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10942     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10943     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10944     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10945     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10946     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10947
10948     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10949     if(appData.roundSync) *syncInterval = gamesPerRound;
10950
10951     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10952
10953     if(appData.tourneyType == 0) {
10954         if(curPairing == (nPlayers-1)/2 ) {
10955             *whitePlayer = curRound;
10956             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10957         } else {
10958             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10959             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10960             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10961             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10962         }
10963     } else if(appData.tourneyType > 1) {
10964         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10965         *whitePlayer = curRound + appData.tourneyType;
10966     } else if(appData.tourneyType > 0) {
10967         *whitePlayer = curPairing;
10968         *blackPlayer = curRound + appData.tourneyType;
10969     }
10970
10971     // take care of white/black alternation per round.
10972     // For cycles and games this is already taken care of by default, derived from matchGame!
10973     return curRound & 1;
10974 }
10975
10976 int
10977 NextTourneyGame (int nr, int *swapColors)
10978 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10979     char *p, *q;
10980     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10981     FILE *tf;
10982     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10983     tf = fopen(appData.tourneyFile, "r");
10984     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10985     ParseArgsFromFile(tf); fclose(tf);
10986     InitTimeControls(); // TC might be altered from tourney file
10987
10988     nPlayers = CountPlayers(appData.participants); // count participants
10989     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10990     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10991
10992     if(syncInterval) {
10993         p = q = appData.results;
10994         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10995         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10996             DisplayMessage(_("Waiting for other game(s)"),"");
10997             waitingForGame = TRUE;
10998             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10999             return 0;
11000         }
11001         waitingForGame = FALSE;
11002     }
11003
11004     if(appData.tourneyType < 0) {
11005         if(nr>=0 && !pairingReceived) {
11006             char buf[1<<16];
11007             if(pairing.pr == NoProc) {
11008                 if(!appData.pairingEngine[0]) {
11009                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11010                     return 0;
11011                 }
11012                 StartChessProgram(&pairing); // starts the pairing engine
11013             }
11014             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11015             SendToProgram(buf, &pairing);
11016             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11017             SendToProgram(buf, &pairing);
11018             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11019         }
11020         pairingReceived = 0;                              // ... so we continue here
11021         *swapColors = 0;
11022         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11023         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11024         matchGame = 1; roundNr = nr / syncInterval + 1;
11025     }
11026
11027     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11028
11029     // redefine engines, engine dir, etc.
11030     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11031     if(first.pr == NoProc) {
11032       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11033       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11034     }
11035     if(second.pr == NoProc) {
11036       SwapEngines(1);
11037       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11038       SwapEngines(1);         // and make that valid for second engine by swapping
11039       InitEngine(&second, 1);
11040     }
11041     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11042     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11043     return OK;
11044 }
11045
11046 void
11047 NextMatchGame ()
11048 {   // performs game initialization that does not invoke engines, and then tries to start the game
11049     int res, firstWhite, swapColors = 0;
11050     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11051     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
11052         char buf[MSG_SIZ];
11053         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11054         if(strcmp(buf, currentDebugFile)) { // name has changed
11055             FILE *f = fopen(buf, "w");
11056             if(f) { // if opening the new file failed, just keep using the old one
11057                 ASSIGN(currentDebugFile, buf);
11058                 fclose(debugFP);
11059                 debugFP = f;
11060             }
11061             if(appData.serverFileName) {
11062                 if(serverFP) fclose(serverFP);
11063                 serverFP = fopen(appData.serverFileName, "w");
11064                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11065                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11066             }
11067         }
11068     }
11069     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11070     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11071     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11072     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11073     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11074     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11075     Reset(FALSE, first.pr != NoProc);
11076     res = LoadGameOrPosition(matchGame); // setup game
11077     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11078     if(!res) return; // abort when bad game/pos file
11079     TwoMachinesEvent();
11080 }
11081
11082 void
11083 UserAdjudicationEvent (int result)
11084 {
11085     ChessMove gameResult = GameIsDrawn;
11086
11087     if( result > 0 ) {
11088         gameResult = WhiteWins;
11089     }
11090     else if( result < 0 ) {
11091         gameResult = BlackWins;
11092     }
11093
11094     if( gameMode == TwoMachinesPlay ) {
11095         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11096     }
11097 }
11098
11099
11100 // [HGM] save: calculate checksum of game to make games easily identifiable
11101 int
11102 StringCheckSum (char *s)
11103 {
11104         int i = 0;
11105         if(s==NULL) return 0;
11106         while(*s) i = i*259 + *s++;
11107         return i;
11108 }
11109
11110 int
11111 GameCheckSum ()
11112 {
11113         int i, sum=0;
11114         for(i=backwardMostMove; i<forwardMostMove; i++) {
11115                 sum += pvInfoList[i].depth;
11116                 sum += StringCheckSum(parseList[i]);
11117                 sum += StringCheckSum(commentList[i]);
11118                 sum *= 261;
11119         }
11120         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11121         return sum + StringCheckSum(commentList[i]);
11122 } // end of save patch
11123
11124 void
11125 GameEnds (ChessMove result, char *resultDetails, int whosays)
11126 {
11127     GameMode nextGameMode;
11128     int isIcsGame;
11129     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11130
11131     if(endingGame) return; /* [HGM] crash: forbid recursion */
11132     endingGame = 1;
11133     if(twoBoards) { // [HGM] dual: switch back to one board
11134         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11135         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11136     }
11137     if (appData.debugMode) {
11138       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11139               result, resultDetails ? resultDetails : "(null)", whosays);
11140     }
11141
11142     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11143
11144     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11145
11146     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11147         /* If we are playing on ICS, the server decides when the
11148            game is over, but the engine can offer to draw, claim
11149            a draw, or resign.
11150          */
11151 #if ZIPPY
11152         if (appData.zippyPlay && first.initDone) {
11153             if (result == GameIsDrawn) {
11154                 /* In case draw still needs to be claimed */
11155                 SendToICS(ics_prefix);
11156                 SendToICS("draw\n");
11157             } else if (StrCaseStr(resultDetails, "resign")) {
11158                 SendToICS(ics_prefix);
11159                 SendToICS("resign\n");
11160             }
11161         }
11162 #endif
11163         endingGame = 0; /* [HGM] crash */
11164         return;
11165     }
11166
11167     /* If we're loading the game from a file, stop */
11168     if (whosays == GE_FILE) {
11169       (void) StopLoadGameTimer();
11170       gameFileFP = NULL;
11171     }
11172
11173     /* Cancel draw offers */
11174     first.offeredDraw = second.offeredDraw = 0;
11175
11176     /* If this is an ICS game, only ICS can really say it's done;
11177        if not, anyone can. */
11178     isIcsGame = (gameMode == IcsPlayingWhite ||
11179                  gameMode == IcsPlayingBlack ||
11180                  gameMode == IcsObserving    ||
11181                  gameMode == IcsExamining);
11182
11183     if (!isIcsGame || whosays == GE_ICS) {
11184         /* OK -- not an ICS game, or ICS said it was done */
11185         StopClocks();
11186         if (!isIcsGame && !appData.noChessProgram)
11187           SetUserThinkingEnables();
11188
11189         /* [HGM] if a machine claims the game end we verify this claim */
11190         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11191             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11192                 char claimer;
11193                 ChessMove trueResult = (ChessMove) -1;
11194
11195                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11196                                             first.twoMachinesColor[0] :
11197                                             second.twoMachinesColor[0] ;
11198
11199                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11200                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11201                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11202                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11203                 } else
11204                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11205                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11206                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11207                 } else
11208                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11209                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11210                 }
11211
11212                 // now verify win claims, but not in drop games, as we don't understand those yet
11213                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11214                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11215                     (result == WhiteWins && claimer == 'w' ||
11216                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11217                       if (appData.debugMode) {
11218                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11219                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11220                       }
11221                       if(result != trueResult) {
11222                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11223                               result = claimer == 'w' ? BlackWins : WhiteWins;
11224                               resultDetails = buf;
11225                       }
11226                 } else
11227                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11228                     && (forwardMostMove <= backwardMostMove ||
11229                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11230                         (claimer=='b')==(forwardMostMove&1))
11231                                                                                   ) {
11232                       /* [HGM] verify: draws that were not flagged are false claims */
11233                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11234                       result = claimer == 'w' ? BlackWins : WhiteWins;
11235                       resultDetails = buf;
11236                 }
11237                 /* (Claiming a loss is accepted no questions asked!) */
11238             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11239                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11240                 result = GameUnfinished;
11241                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11242             }
11243             /* [HGM] bare: don't allow bare King to win */
11244             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11245                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11246                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11247                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11248                && result != GameIsDrawn)
11249             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11250                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11251                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11252                         if(p >= 0 && p <= (int)WhiteKing) k++;
11253                 }
11254                 if (appData.debugMode) {
11255                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11256                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11257                 }
11258                 if(k <= 1) {
11259                         result = GameIsDrawn;
11260                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11261                         resultDetails = buf;
11262                 }
11263             }
11264         }
11265
11266
11267         if(serverMoves != NULL && !loadFlag) { char c = '=';
11268             if(result==WhiteWins) c = '+';
11269             if(result==BlackWins) c = '-';
11270             if(resultDetails != NULL)
11271                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11272         }
11273         if (resultDetails != NULL) {
11274             gameInfo.result = result;
11275             gameInfo.resultDetails = StrSave(resultDetails);
11276
11277             /* display last move only if game was not loaded from file */
11278             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11279                 DisplayMove(currentMove - 1);
11280
11281             if (forwardMostMove != 0) {
11282                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11283                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11284                                                                 ) {
11285                     if (*appData.saveGameFile != NULLCHAR) {
11286                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11287                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11288                         else
11289                         SaveGameToFile(appData.saveGameFile, TRUE);
11290                     } else if (appData.autoSaveGames) {
11291                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11292                     }
11293                     if (*appData.savePositionFile != NULLCHAR) {
11294                         SavePositionToFile(appData.savePositionFile);
11295                     }
11296                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11297                 }
11298             }
11299
11300             /* Tell program how game ended in case it is learning */
11301             /* [HGM] Moved this to after saving the PGN, just in case */
11302             /* engine died and we got here through time loss. In that */
11303             /* case we will get a fatal error writing the pipe, which */
11304             /* would otherwise lose us the PGN.                       */
11305             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11306             /* output during GameEnds should never be fatal anymore   */
11307             if (gameMode == MachinePlaysWhite ||
11308                 gameMode == MachinePlaysBlack ||
11309                 gameMode == TwoMachinesPlay ||
11310                 gameMode == IcsPlayingWhite ||
11311                 gameMode == IcsPlayingBlack ||
11312                 gameMode == BeginningOfGame) {
11313                 char buf[MSG_SIZ];
11314                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11315                         resultDetails);
11316                 if (first.pr != NoProc) {
11317                     SendToProgram(buf, &first);
11318                 }
11319                 if (second.pr != NoProc &&
11320                     gameMode == TwoMachinesPlay) {
11321                     SendToProgram(buf, &second);
11322                 }
11323             }
11324         }
11325
11326         if (appData.icsActive) {
11327             if (appData.quietPlay &&
11328                 (gameMode == IcsPlayingWhite ||
11329                  gameMode == IcsPlayingBlack)) {
11330                 SendToICS(ics_prefix);
11331                 SendToICS("set shout 1\n");
11332             }
11333             nextGameMode = IcsIdle;
11334             ics_user_moved = FALSE;
11335             /* clean up premove.  It's ugly when the game has ended and the
11336              * premove highlights are still on the board.
11337              */
11338             if (gotPremove) {
11339               gotPremove = FALSE;
11340               ClearPremoveHighlights();
11341               DrawPosition(FALSE, boards[currentMove]);
11342             }
11343             if (whosays == GE_ICS) {
11344                 switch (result) {
11345                 case WhiteWins:
11346                     if (gameMode == IcsPlayingWhite)
11347                         PlayIcsWinSound();
11348                     else if(gameMode == IcsPlayingBlack)
11349                         PlayIcsLossSound();
11350                     break;
11351                 case BlackWins:
11352                     if (gameMode == IcsPlayingBlack)
11353                         PlayIcsWinSound();
11354                     else if(gameMode == IcsPlayingWhite)
11355                         PlayIcsLossSound();
11356                     break;
11357                 case GameIsDrawn:
11358                     PlayIcsDrawSound();
11359                     break;
11360                 default:
11361                     PlayIcsUnfinishedSound();
11362                 }
11363             }
11364             if(appData.quitNext) { ExitEvent(0); return; }
11365         } else if (gameMode == EditGame ||
11366                    gameMode == PlayFromGameFile ||
11367                    gameMode == AnalyzeMode ||
11368                    gameMode == AnalyzeFile) {
11369             nextGameMode = gameMode;
11370         } else {
11371             nextGameMode = EndOfGame;
11372         }
11373         pausing = FALSE;
11374         ModeHighlight();
11375     } else {
11376         nextGameMode = gameMode;
11377     }
11378
11379     if (appData.noChessProgram) {
11380         gameMode = nextGameMode;
11381         ModeHighlight();
11382         endingGame = 0; /* [HGM] crash */
11383         return;
11384     }
11385
11386     if (first.reuse) {
11387         /* Put first chess program into idle state */
11388         if (first.pr != NoProc &&
11389             (gameMode == MachinePlaysWhite ||
11390              gameMode == MachinePlaysBlack ||
11391              gameMode == TwoMachinesPlay ||
11392              gameMode == IcsPlayingWhite ||
11393              gameMode == IcsPlayingBlack ||
11394              gameMode == BeginningOfGame)) {
11395             SendToProgram("force\n", &first);
11396             if (first.usePing) {
11397               char buf[MSG_SIZ];
11398               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11399               SendToProgram(buf, &first);
11400             }
11401         }
11402     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11403         /* Kill off first chess program */
11404         if (first.isr != NULL)
11405           RemoveInputSource(first.isr);
11406         first.isr = NULL;
11407
11408         if (first.pr != NoProc) {
11409             ExitAnalyzeMode();
11410             DoSleep( appData.delayBeforeQuit );
11411             SendToProgram("quit\n", &first);
11412             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11413             first.reload = TRUE;
11414         }
11415         first.pr = NoProc;
11416     }
11417     if (second.reuse) {
11418         /* Put second chess program into idle state */
11419         if (second.pr != NoProc &&
11420             gameMode == TwoMachinesPlay) {
11421             SendToProgram("force\n", &second);
11422             if (second.usePing) {
11423               char buf[MSG_SIZ];
11424               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11425               SendToProgram(buf, &second);
11426             }
11427         }
11428     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11429         /* Kill off second chess program */
11430         if (second.isr != NULL)
11431           RemoveInputSource(second.isr);
11432         second.isr = NULL;
11433
11434         if (second.pr != NoProc) {
11435             DoSleep( appData.delayBeforeQuit );
11436             SendToProgram("quit\n", &second);
11437             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11438             second.reload = TRUE;
11439         }
11440         second.pr = NoProc;
11441     }
11442
11443     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11444         char resChar = '=';
11445         switch (result) {
11446         case WhiteWins:
11447           resChar = '+';
11448           if (first.twoMachinesColor[0] == 'w') {
11449             first.matchWins++;
11450           } else {
11451             second.matchWins++;
11452           }
11453           break;
11454         case BlackWins:
11455           resChar = '-';
11456           if (first.twoMachinesColor[0] == 'b') {
11457             first.matchWins++;
11458           } else {
11459             second.matchWins++;
11460           }
11461           break;
11462         case GameUnfinished:
11463           resChar = ' ';
11464         default:
11465           break;
11466         }
11467
11468         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11469         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11470             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11471             ReserveGame(nextGame, resChar); // sets nextGame
11472             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11473             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11474         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11475
11476         if (nextGame <= appData.matchGames && !abortMatch) {
11477             gameMode = nextGameMode;
11478             matchGame = nextGame; // this will be overruled in tourney mode!
11479             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11480             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11481             endingGame = 0; /* [HGM] crash */
11482             return;
11483         } else {
11484             gameMode = nextGameMode;
11485             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11486                      first.tidy, second.tidy,
11487                      first.matchWins, second.matchWins,
11488                      appData.matchGames - (first.matchWins + second.matchWins));
11489             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11490             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11491             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11492             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11493                 first.twoMachinesColor = "black\n";
11494                 second.twoMachinesColor = "white\n";
11495             } else {
11496                 first.twoMachinesColor = "white\n";
11497                 second.twoMachinesColor = "black\n";
11498             }
11499         }
11500     }
11501     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11502         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11503       ExitAnalyzeMode();
11504     gameMode = nextGameMode;
11505     ModeHighlight();
11506     endingGame = 0;  /* [HGM] crash */
11507     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11508         if(matchMode == TRUE) { // match through command line: exit with or without popup
11509             if(ranking) {
11510                 ToNrEvent(forwardMostMove);
11511                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11512                 else ExitEvent(0);
11513             } else DisplayFatalError(buf, 0, 0);
11514         } else { // match through menu; just stop, with or without popup
11515             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11516             ModeHighlight();
11517             if(ranking){
11518                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11519             } else DisplayNote(buf);
11520       }
11521       if(ranking) free(ranking);
11522     }
11523 }
11524
11525 /* Assumes program was just initialized (initString sent).
11526    Leaves program in force mode. */
11527 void
11528 FeedMovesToProgram (ChessProgramState *cps, int upto)
11529 {
11530     int i;
11531
11532     if (appData.debugMode)
11533       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11534               startedFromSetupPosition ? "position and " : "",
11535               backwardMostMove, upto, cps->which);
11536     if(currentlyInitializedVariant != gameInfo.variant) {
11537       char buf[MSG_SIZ];
11538         // [HGM] variantswitch: make engine aware of new variant
11539         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11540                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11541                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11542         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11543         SendToProgram(buf, cps);
11544         currentlyInitializedVariant = gameInfo.variant;
11545     }
11546     SendToProgram("force\n", cps);
11547     if (startedFromSetupPosition) {
11548         SendBoard(cps, backwardMostMove);
11549     if (appData.debugMode) {
11550         fprintf(debugFP, "feedMoves\n");
11551     }
11552     }
11553     for (i = backwardMostMove; i < upto; i++) {
11554         SendMoveToProgram(i, cps);
11555     }
11556 }
11557
11558
11559 int
11560 ResurrectChessProgram ()
11561 {
11562      /* The chess program may have exited.
11563         If so, restart it and feed it all the moves made so far. */
11564     static int doInit = 0;
11565
11566     if (appData.noChessProgram) return 1;
11567
11568     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11569         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11570         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11571         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11572     } else {
11573         if (first.pr != NoProc) return 1;
11574         StartChessProgram(&first);
11575     }
11576     InitChessProgram(&first, FALSE);
11577     FeedMovesToProgram(&first, currentMove);
11578
11579     if (!first.sendTime) {
11580         /* can't tell gnuchess what its clock should read,
11581            so we bow to its notion. */
11582         ResetClocks();
11583         timeRemaining[0][currentMove] = whiteTimeRemaining;
11584         timeRemaining[1][currentMove] = blackTimeRemaining;
11585     }
11586
11587     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11588                 appData.icsEngineAnalyze) && first.analysisSupport) {
11589       SendToProgram("analyze\n", &first);
11590       first.analyzing = TRUE;
11591     }
11592     return 1;
11593 }
11594
11595 /*
11596  * Button procedures
11597  */
11598 void
11599 Reset (int redraw, int init)
11600 {
11601     int i;
11602
11603     if (appData.debugMode) {
11604         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11605                 redraw, init, gameMode);
11606     }
11607     CleanupTail(); // [HGM] vari: delete any stored variations
11608     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11609     pausing = pauseExamInvalid = FALSE;
11610     startedFromSetupPosition = blackPlaysFirst = FALSE;
11611     firstMove = TRUE;
11612     whiteFlag = blackFlag = FALSE;
11613     userOfferedDraw = FALSE;
11614     hintRequested = bookRequested = FALSE;
11615     first.maybeThinking = FALSE;
11616     second.maybeThinking = FALSE;
11617     first.bookSuspend = FALSE; // [HGM] book
11618     second.bookSuspend = FALSE;
11619     thinkOutput[0] = NULLCHAR;
11620     lastHint[0] = NULLCHAR;
11621     ClearGameInfo(&gameInfo);
11622     gameInfo.variant = StringToVariant(appData.variant);
11623     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11624     ics_user_moved = ics_clock_paused = FALSE;
11625     ics_getting_history = H_FALSE;
11626     ics_gamenum = -1;
11627     white_holding[0] = black_holding[0] = NULLCHAR;
11628     ClearProgramStats();
11629     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11630
11631     ResetFrontEnd();
11632     ClearHighlights();
11633     flipView = appData.flipView;
11634     ClearPremoveHighlights();
11635     gotPremove = FALSE;
11636     alarmSounded = FALSE;
11637     killX = killY = -1; // [HGM] lion
11638
11639     GameEnds(EndOfFile, NULL, GE_PLAYER);
11640     if(appData.serverMovesName != NULL) {
11641         /* [HGM] prepare to make moves file for broadcasting */
11642         clock_t t = clock();
11643         if(serverMoves != NULL) fclose(serverMoves);
11644         serverMoves = fopen(appData.serverMovesName, "r");
11645         if(serverMoves != NULL) {
11646             fclose(serverMoves);
11647             /* delay 15 sec before overwriting, so all clients can see end */
11648             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11649         }
11650         serverMoves = fopen(appData.serverMovesName, "w");
11651     }
11652
11653     ExitAnalyzeMode();
11654     gameMode = BeginningOfGame;
11655     ModeHighlight();
11656     if(appData.icsActive) gameInfo.variant = VariantNormal;
11657     currentMove = forwardMostMove = backwardMostMove = 0;
11658     MarkTargetSquares(1);
11659     InitPosition(redraw);
11660     for (i = 0; i < MAX_MOVES; i++) {
11661         if (commentList[i] != NULL) {
11662             free(commentList[i]);
11663             commentList[i] = NULL;
11664         }
11665     }
11666     ResetClocks();
11667     timeRemaining[0][0] = whiteTimeRemaining;
11668     timeRemaining[1][0] = blackTimeRemaining;
11669
11670     if (first.pr == NoProc) {
11671         StartChessProgram(&first);
11672     }
11673     if (init) {
11674             InitChessProgram(&first, startedFromSetupPosition);
11675     }
11676     DisplayTitle("");
11677     DisplayMessage("", "");
11678     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11679     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11680     ClearMap();        // [HGM] exclude: invalidate map
11681 }
11682
11683 void
11684 AutoPlayGameLoop ()
11685 {
11686     for (;;) {
11687         if (!AutoPlayOneMove())
11688           return;
11689         if (matchMode || appData.timeDelay == 0)
11690           continue;
11691         if (appData.timeDelay < 0)
11692           return;
11693         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11694         break;
11695     }
11696 }
11697
11698 void
11699 AnalyzeNextGame()
11700 {
11701     ReloadGame(1); // next game
11702 }
11703
11704 int
11705 AutoPlayOneMove ()
11706 {
11707     int fromX, fromY, toX, toY;
11708
11709     if (appData.debugMode) {
11710       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11711     }
11712
11713     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11714       return FALSE;
11715
11716     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11717       pvInfoList[currentMove].depth = programStats.depth;
11718       pvInfoList[currentMove].score = programStats.score;
11719       pvInfoList[currentMove].time  = 0;
11720       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11721       else { // append analysis of final position as comment
11722         char buf[MSG_SIZ];
11723         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11724         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11725       }
11726       programStats.depth = 0;
11727     }
11728
11729     if (currentMove >= forwardMostMove) {
11730       if(gameMode == AnalyzeFile) {
11731           if(appData.loadGameIndex == -1) {
11732             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11733           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11734           } else {
11735           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11736         }
11737       }
11738 //      gameMode = EndOfGame;
11739 //      ModeHighlight();
11740
11741       /* [AS] Clear current move marker at the end of a game */
11742       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11743
11744       return FALSE;
11745     }
11746
11747     toX = moveList[currentMove][2] - AAA;
11748     toY = moveList[currentMove][3] - ONE;
11749
11750     if (moveList[currentMove][1] == '@') {
11751         if (appData.highlightLastMove) {
11752             SetHighlights(-1, -1, toX, toY);
11753         }
11754     } else {
11755         fromX = moveList[currentMove][0] - AAA;
11756         fromY = moveList[currentMove][1] - ONE;
11757
11758         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11759
11760         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11761
11762         if (appData.highlightLastMove) {
11763             SetHighlights(fromX, fromY, toX, toY);
11764         }
11765     }
11766     DisplayMove(currentMove);
11767     SendMoveToProgram(currentMove++, &first);
11768     DisplayBothClocks();
11769     DrawPosition(FALSE, boards[currentMove]);
11770     // [HGM] PV info: always display, routine tests if empty
11771     DisplayComment(currentMove - 1, commentList[currentMove]);
11772     return TRUE;
11773 }
11774
11775
11776 int
11777 LoadGameOneMove (ChessMove readAhead)
11778 {
11779     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11780     char promoChar = NULLCHAR;
11781     ChessMove moveType;
11782     char move[MSG_SIZ];
11783     char *p, *q;
11784
11785     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11786         gameMode != AnalyzeMode && gameMode != Training) {
11787         gameFileFP = NULL;
11788         return FALSE;
11789     }
11790
11791     yyboardindex = forwardMostMove;
11792     if (readAhead != EndOfFile) {
11793       moveType = readAhead;
11794     } else {
11795       if (gameFileFP == NULL)
11796           return FALSE;
11797       moveType = (ChessMove) Myylex();
11798     }
11799
11800     done = FALSE;
11801     switch (moveType) {
11802       case Comment:
11803         if (appData.debugMode)
11804           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11805         p = yy_text;
11806
11807         /* append the comment but don't display it */
11808         AppendComment(currentMove, p, FALSE);
11809         return TRUE;
11810
11811       case WhiteCapturesEnPassant:
11812       case BlackCapturesEnPassant:
11813       case WhitePromotion:
11814       case BlackPromotion:
11815       case WhiteNonPromotion:
11816       case BlackNonPromotion:
11817       case NormalMove:
11818       case FirstLeg:
11819       case WhiteKingSideCastle:
11820       case WhiteQueenSideCastle:
11821       case BlackKingSideCastle:
11822       case BlackQueenSideCastle:
11823       case WhiteKingSideCastleWild:
11824       case WhiteQueenSideCastleWild:
11825       case BlackKingSideCastleWild:
11826       case BlackQueenSideCastleWild:
11827       /* PUSH Fabien */
11828       case WhiteHSideCastleFR:
11829       case WhiteASideCastleFR:
11830       case BlackHSideCastleFR:
11831       case BlackASideCastleFR:
11832       /* POP Fabien */
11833         if (appData.debugMode)
11834           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11835         fromX = currentMoveString[0] - AAA;
11836         fromY = currentMoveString[1] - ONE;
11837         toX = currentMoveString[2] - AAA;
11838         toY = currentMoveString[3] - ONE;
11839         promoChar = currentMoveString[4];
11840         if(promoChar == ';') promoChar = NULLCHAR;
11841         break;
11842
11843       case WhiteDrop:
11844       case BlackDrop:
11845         if (appData.debugMode)
11846           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11847         fromX = moveType == WhiteDrop ?
11848           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11849         (int) CharToPiece(ToLower(currentMoveString[0]));
11850         fromY = DROP_RANK;
11851         toX = currentMoveString[2] - AAA;
11852         toY = currentMoveString[3] - ONE;
11853         break;
11854
11855       case WhiteWins:
11856       case BlackWins:
11857       case GameIsDrawn:
11858       case GameUnfinished:
11859         if (appData.debugMode)
11860           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11861         p = strchr(yy_text, '{');
11862         if (p == NULL) p = strchr(yy_text, '(');
11863         if (p == NULL) {
11864             p = yy_text;
11865             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11866         } else {
11867             q = strchr(p, *p == '{' ? '}' : ')');
11868             if (q != NULL) *q = NULLCHAR;
11869             p++;
11870         }
11871         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11872         GameEnds(moveType, p, GE_FILE);
11873         done = TRUE;
11874         if (cmailMsgLoaded) {
11875             ClearHighlights();
11876             flipView = WhiteOnMove(currentMove);
11877             if (moveType == GameUnfinished) flipView = !flipView;
11878             if (appData.debugMode)
11879               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11880         }
11881         break;
11882
11883       case EndOfFile:
11884         if (appData.debugMode)
11885           fprintf(debugFP, "Parser hit end of file\n");
11886         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11887           case MT_NONE:
11888           case MT_CHECK:
11889             break;
11890           case MT_CHECKMATE:
11891           case MT_STAINMATE:
11892             if (WhiteOnMove(currentMove)) {
11893                 GameEnds(BlackWins, "Black mates", GE_FILE);
11894             } else {
11895                 GameEnds(WhiteWins, "White mates", GE_FILE);
11896             }
11897             break;
11898           case MT_STALEMATE:
11899             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11900             break;
11901         }
11902         done = TRUE;
11903         break;
11904
11905       case MoveNumberOne:
11906         if (lastLoadGameStart == GNUChessGame) {
11907             /* GNUChessGames have numbers, but they aren't move numbers */
11908             if (appData.debugMode)
11909               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11910                       yy_text, (int) moveType);
11911             return LoadGameOneMove(EndOfFile); /* tail recursion */
11912         }
11913         /* else fall thru */
11914
11915       case XBoardGame:
11916       case GNUChessGame:
11917       case PGNTag:
11918         /* Reached start of next game in file */
11919         if (appData.debugMode)
11920           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11921         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11922           case MT_NONE:
11923           case MT_CHECK:
11924             break;
11925           case MT_CHECKMATE:
11926           case MT_STAINMATE:
11927             if (WhiteOnMove(currentMove)) {
11928                 GameEnds(BlackWins, "Black mates", GE_FILE);
11929             } else {
11930                 GameEnds(WhiteWins, "White mates", GE_FILE);
11931             }
11932             break;
11933           case MT_STALEMATE:
11934             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11935             break;
11936         }
11937         done = TRUE;
11938         break;
11939
11940       case PositionDiagram:     /* should not happen; ignore */
11941       case ElapsedTime:         /* ignore */
11942       case NAG:                 /* ignore */
11943         if (appData.debugMode)
11944           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11945                   yy_text, (int) moveType);
11946         return LoadGameOneMove(EndOfFile); /* tail recursion */
11947
11948       case IllegalMove:
11949         if (appData.testLegality) {
11950             if (appData.debugMode)
11951               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11952             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11953                     (forwardMostMove / 2) + 1,
11954                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11955             DisplayError(move, 0);
11956             done = TRUE;
11957         } else {
11958             if (appData.debugMode)
11959               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11960                       yy_text, currentMoveString);
11961             fromX = currentMoveString[0] - AAA;
11962             fromY = currentMoveString[1] - ONE;
11963             toX = currentMoveString[2] - AAA;
11964             toY = currentMoveString[3] - ONE;
11965             promoChar = currentMoveString[4];
11966         }
11967         break;
11968
11969       case AmbiguousMove:
11970         if (appData.debugMode)
11971           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11972         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11973                 (forwardMostMove / 2) + 1,
11974                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11975         DisplayError(move, 0);
11976         done = TRUE;
11977         break;
11978
11979       default:
11980       case ImpossibleMove:
11981         if (appData.debugMode)
11982           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11983         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11984                 (forwardMostMove / 2) + 1,
11985                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11986         DisplayError(move, 0);
11987         done = TRUE;
11988         break;
11989     }
11990
11991     if (done) {
11992         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11993             DrawPosition(FALSE, boards[currentMove]);
11994             DisplayBothClocks();
11995             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11996               DisplayComment(currentMove - 1, commentList[currentMove]);
11997         }
11998         (void) StopLoadGameTimer();
11999         gameFileFP = NULL;
12000         cmailOldMove = forwardMostMove;
12001         return FALSE;
12002     } else {
12003         /* currentMoveString is set as a side-effect of yylex */
12004
12005         thinkOutput[0] = NULLCHAR;
12006         MakeMove(fromX, fromY, toX, toY, promoChar);
12007         killX = killY = -1; // [HGM] lion: used up
12008         currentMove = forwardMostMove;
12009         return TRUE;
12010     }
12011 }
12012
12013 /* Load the nth game from the given file */
12014 int
12015 LoadGameFromFile (char *filename, int n, char *title, int useList)
12016 {
12017     FILE *f;
12018     char buf[MSG_SIZ];
12019
12020     if (strcmp(filename, "-") == 0) {
12021         f = stdin;
12022         title = "stdin";
12023     } else {
12024         f = fopen(filename, "rb");
12025         if (f == NULL) {
12026           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12027             DisplayError(buf, errno);
12028             return FALSE;
12029         }
12030     }
12031     if (fseek(f, 0, 0) == -1) {
12032         /* f is not seekable; probably a pipe */
12033         useList = FALSE;
12034     }
12035     if (useList && n == 0) {
12036         int error = GameListBuild(f);
12037         if (error) {
12038             DisplayError(_("Cannot build game list"), error);
12039         } else if (!ListEmpty(&gameList) &&
12040                    ((ListGame *) gameList.tailPred)->number > 1) {
12041             GameListPopUp(f, title);
12042             return TRUE;
12043         }
12044         GameListDestroy();
12045         n = 1;
12046     }
12047     if (n == 0) n = 1;
12048     return LoadGame(f, n, title, FALSE);
12049 }
12050
12051
12052 void
12053 MakeRegisteredMove ()
12054 {
12055     int fromX, fromY, toX, toY;
12056     char promoChar;
12057     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12058         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12059           case CMAIL_MOVE:
12060           case CMAIL_DRAW:
12061             if (appData.debugMode)
12062               fprintf(debugFP, "Restoring %s for game %d\n",
12063                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12064
12065             thinkOutput[0] = NULLCHAR;
12066             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12067             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12068             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12069             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12070             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12071             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12072             MakeMove(fromX, fromY, toX, toY, promoChar);
12073             ShowMove(fromX, fromY, toX, toY);
12074
12075             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12076               case MT_NONE:
12077               case MT_CHECK:
12078                 break;
12079
12080               case MT_CHECKMATE:
12081               case MT_STAINMATE:
12082                 if (WhiteOnMove(currentMove)) {
12083                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12084                 } else {
12085                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12086                 }
12087                 break;
12088
12089               case MT_STALEMATE:
12090                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12091                 break;
12092             }
12093
12094             break;
12095
12096           case CMAIL_RESIGN:
12097             if (WhiteOnMove(currentMove)) {
12098                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12099             } else {
12100                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12101             }
12102             break;
12103
12104           case CMAIL_ACCEPT:
12105             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12106             break;
12107
12108           default:
12109             break;
12110         }
12111     }
12112
12113     return;
12114 }
12115
12116 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12117 int
12118 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12119 {
12120     int retVal;
12121
12122     if (gameNumber > nCmailGames) {
12123         DisplayError(_("No more games in this message"), 0);
12124         return FALSE;
12125     }
12126     if (f == lastLoadGameFP) {
12127         int offset = gameNumber - lastLoadGameNumber;
12128         if (offset == 0) {
12129             cmailMsg[0] = NULLCHAR;
12130             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12131                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12132                 nCmailMovesRegistered--;
12133             }
12134             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12135             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12136                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12137             }
12138         } else {
12139             if (! RegisterMove()) return FALSE;
12140         }
12141     }
12142
12143     retVal = LoadGame(f, gameNumber, title, useList);
12144
12145     /* Make move registered during previous look at this game, if any */
12146     MakeRegisteredMove();
12147
12148     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12149         commentList[currentMove]
12150           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12151         DisplayComment(currentMove - 1, commentList[currentMove]);
12152     }
12153
12154     return retVal;
12155 }
12156
12157 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12158 int
12159 ReloadGame (int offset)
12160 {
12161     int gameNumber = lastLoadGameNumber + offset;
12162     if (lastLoadGameFP == NULL) {
12163         DisplayError(_("No game has been loaded yet"), 0);
12164         return FALSE;
12165     }
12166     if (gameNumber <= 0) {
12167         DisplayError(_("Can't back up any further"), 0);
12168         return FALSE;
12169     }
12170     if (cmailMsgLoaded) {
12171         return CmailLoadGame(lastLoadGameFP, gameNumber,
12172                              lastLoadGameTitle, lastLoadGameUseList);
12173     } else {
12174         return LoadGame(lastLoadGameFP, gameNumber,
12175                         lastLoadGameTitle, lastLoadGameUseList);
12176     }
12177 }
12178
12179 int keys[EmptySquare+1];
12180
12181 int
12182 PositionMatches (Board b1, Board b2)
12183 {
12184     int r, f, sum=0;
12185     switch(appData.searchMode) {
12186         case 1: return CompareWithRights(b1, b2);
12187         case 2:
12188             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12189                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12190             }
12191             return TRUE;
12192         case 3:
12193             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12194               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12195                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12196             }
12197             return sum==0;
12198         case 4:
12199             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12200                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12201             }
12202             return sum==0;
12203     }
12204     return TRUE;
12205 }
12206
12207 #define Q_PROMO  4
12208 #define Q_EP     3
12209 #define Q_BCASTL 2
12210 #define Q_WCASTL 1
12211
12212 int pieceList[256], quickBoard[256];
12213 ChessSquare pieceType[256] = { EmptySquare };
12214 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12215 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12216 int soughtTotal, turn;
12217 Boolean epOK, flipSearch;
12218
12219 typedef struct {
12220     unsigned char piece, to;
12221 } Move;
12222
12223 #define DSIZE (250000)
12224
12225 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12226 Move *moveDatabase = initialSpace;
12227 unsigned int movePtr, dataSize = DSIZE;
12228
12229 int
12230 MakePieceList (Board board, int *counts)
12231 {
12232     int r, f, n=Q_PROMO, total=0;
12233     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12234     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12235         int sq = f + (r<<4);
12236         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12237             quickBoard[sq] = ++n;
12238             pieceList[n] = sq;
12239             pieceType[n] = board[r][f];
12240             counts[board[r][f]]++;
12241             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12242             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12243             total++;
12244         }
12245     }
12246     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12247     return total;
12248 }
12249
12250 void
12251 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12252 {
12253     int sq = fromX + (fromY<<4);
12254     int piece = quickBoard[sq], rook;
12255     quickBoard[sq] = 0;
12256     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12257     if(piece == pieceList[1] && fromY == toY) {
12258       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12259         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12260         moveDatabase[movePtr++].piece = Q_WCASTL;
12261         quickBoard[sq] = piece;
12262         piece = quickBoard[from]; quickBoard[from] = 0;
12263         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12264       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12265         quickBoard[sq] = 0; // remove Rook
12266         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12267         moveDatabase[movePtr++].piece = Q_WCASTL;
12268         quickBoard[sq] = pieceList[1]; // put King
12269         piece = rook;
12270         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12271       }
12272     } else
12273     if(piece == pieceList[2] && fromY == toY) {
12274       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12275         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12276         moveDatabase[movePtr++].piece = Q_BCASTL;
12277         quickBoard[sq] = piece;
12278         piece = quickBoard[from]; quickBoard[from] = 0;
12279         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12280       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12281         quickBoard[sq] = 0; // remove Rook
12282         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12283         moveDatabase[movePtr++].piece = Q_BCASTL;
12284         quickBoard[sq] = pieceList[2]; // put King
12285         piece = rook;
12286         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12287       }
12288     } else
12289     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12290         quickBoard[(fromY<<4)+toX] = 0;
12291         moveDatabase[movePtr].piece = Q_EP;
12292         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12293         moveDatabase[movePtr].to = sq;
12294     } else
12295     if(promoPiece != pieceType[piece]) {
12296         moveDatabase[movePtr++].piece = Q_PROMO;
12297         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12298     }
12299     moveDatabase[movePtr].piece = piece;
12300     quickBoard[sq] = piece;
12301     movePtr++;
12302 }
12303
12304 int
12305 PackGame (Board board)
12306 {
12307     Move *newSpace = NULL;
12308     moveDatabase[movePtr].piece = 0; // terminate previous game
12309     if(movePtr > dataSize) {
12310         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12311         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12312         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12313         if(newSpace) {
12314             int i;
12315             Move *p = moveDatabase, *q = newSpace;
12316             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12317             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12318             moveDatabase = newSpace;
12319         } else { // calloc failed, we must be out of memory. Too bad...
12320             dataSize = 0; // prevent calloc events for all subsequent games
12321             return 0;     // and signal this one isn't cached
12322         }
12323     }
12324     movePtr++;
12325     MakePieceList(board, counts);
12326     return movePtr;
12327 }
12328
12329 int
12330 QuickCompare (Board board, int *minCounts, int *maxCounts)
12331 {   // compare according to search mode
12332     int r, f;
12333     switch(appData.searchMode)
12334     {
12335       case 1: // exact position match
12336         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12337         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12338             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12339         }
12340         break;
12341       case 2: // can have extra material on empty squares
12342         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12343             if(board[r][f] == EmptySquare) continue;
12344             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12345         }
12346         break;
12347       case 3: // material with exact Pawn structure
12348         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12349             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12350             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12351         } // fall through to material comparison
12352       case 4: // exact material
12353         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12354         break;
12355       case 6: // material range with given imbalance
12356         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12357         // fall through to range comparison
12358       case 5: // material range
12359         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12360     }
12361     return TRUE;
12362 }
12363
12364 int
12365 QuickScan (Board board, Move *move)
12366 {   // reconstruct game,and compare all positions in it
12367     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12368     do {
12369         int piece = move->piece;
12370         int to = move->to, from = pieceList[piece];
12371         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12372           if(!piece) return -1;
12373           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12374             piece = (++move)->piece;
12375             from = pieceList[piece];
12376             counts[pieceType[piece]]--;
12377             pieceType[piece] = (ChessSquare) move->to;
12378             counts[move->to]++;
12379           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12380             counts[pieceType[quickBoard[to]]]--;
12381             quickBoard[to] = 0; total--;
12382             move++;
12383             continue;
12384           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12385             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12386             from  = pieceList[piece]; // so this must be King
12387             quickBoard[from] = 0;
12388             pieceList[piece] = to;
12389             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12390             quickBoard[from] = 0; // rook
12391             quickBoard[to] = piece;
12392             to = move->to; piece = move->piece;
12393             goto aftercastle;
12394           }
12395         }
12396         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12397         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12398         quickBoard[from] = 0;
12399       aftercastle:
12400         quickBoard[to] = piece;
12401         pieceList[piece] = to;
12402         cnt++; turn ^= 3;
12403         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12404            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12405            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12406                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12407           ) {
12408             static int lastCounts[EmptySquare+1];
12409             int i;
12410             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12411             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12412         } else stretch = 0;
12413         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12414         move++;
12415     } while(1);
12416 }
12417
12418 void
12419 InitSearch ()
12420 {
12421     int r, f;
12422     flipSearch = FALSE;
12423     CopyBoard(soughtBoard, boards[currentMove]);
12424     soughtTotal = MakePieceList(soughtBoard, maxSought);
12425     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12426     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12427     CopyBoard(reverseBoard, boards[currentMove]);
12428     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12429         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12430         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12431         reverseBoard[r][f] = piece;
12432     }
12433     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12434     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12435     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12436                  || (boards[currentMove][CASTLING][2] == NoRights ||
12437                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12438                  && (boards[currentMove][CASTLING][5] == NoRights ||
12439                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12440       ) {
12441         flipSearch = TRUE;
12442         CopyBoard(flipBoard, soughtBoard);
12443         CopyBoard(rotateBoard, reverseBoard);
12444         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12445             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12446             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12447         }
12448     }
12449     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12450     if(appData.searchMode >= 5) {
12451         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12452         MakePieceList(soughtBoard, minSought);
12453         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12454     }
12455     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12456         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12457 }
12458
12459 GameInfo dummyInfo;
12460 static int creatingBook;
12461
12462 int
12463 GameContainsPosition (FILE *f, ListGame *lg)
12464 {
12465     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12466     int fromX, fromY, toX, toY;
12467     char promoChar;
12468     static int initDone=FALSE;
12469
12470     // weed out games based on numerical tag comparison
12471     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12472     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12473     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12474     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12475     if(!initDone) {
12476         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12477         initDone = TRUE;
12478     }
12479     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12480     else CopyBoard(boards[scratch], initialPosition); // default start position
12481     if(lg->moves) {
12482         turn = btm + 1;
12483         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12484         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12485     }
12486     if(btm) plyNr++;
12487     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12488     fseek(f, lg->offset, 0);
12489     yynewfile(f);
12490     while(1) {
12491         yyboardindex = scratch;
12492         quickFlag = plyNr+1;
12493         next = Myylex();
12494         quickFlag = 0;
12495         switch(next) {
12496             case PGNTag:
12497                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12498             default:
12499                 continue;
12500
12501             case XBoardGame:
12502             case GNUChessGame:
12503                 if(plyNr) return -1; // after we have seen moves, this is for new game
12504               continue;
12505
12506             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12507             case ImpossibleMove:
12508             case WhiteWins: // game ends here with these four
12509             case BlackWins:
12510             case GameIsDrawn:
12511             case GameUnfinished:
12512                 return -1;
12513
12514             case IllegalMove:
12515                 if(appData.testLegality) return -1;
12516             case WhiteCapturesEnPassant:
12517             case BlackCapturesEnPassant:
12518             case WhitePromotion:
12519             case BlackPromotion:
12520             case WhiteNonPromotion:
12521             case BlackNonPromotion:
12522             case NormalMove:
12523             case FirstLeg:
12524             case WhiteKingSideCastle:
12525             case WhiteQueenSideCastle:
12526             case BlackKingSideCastle:
12527             case BlackQueenSideCastle:
12528             case WhiteKingSideCastleWild:
12529             case WhiteQueenSideCastleWild:
12530             case BlackKingSideCastleWild:
12531             case BlackQueenSideCastleWild:
12532             case WhiteHSideCastleFR:
12533             case WhiteASideCastleFR:
12534             case BlackHSideCastleFR:
12535             case BlackASideCastleFR:
12536                 fromX = currentMoveString[0] - AAA;
12537                 fromY = currentMoveString[1] - ONE;
12538                 toX = currentMoveString[2] - AAA;
12539                 toY = currentMoveString[3] - ONE;
12540                 promoChar = currentMoveString[4];
12541                 break;
12542             case WhiteDrop:
12543             case BlackDrop:
12544                 fromX = next == WhiteDrop ?
12545                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12546                   (int) CharToPiece(ToLower(currentMoveString[0]));
12547                 fromY = DROP_RANK;
12548                 toX = currentMoveString[2] - AAA;
12549                 toY = currentMoveString[3] - ONE;
12550                 promoChar = 0;
12551                 break;
12552         }
12553         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12554         plyNr++;
12555         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12556         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12557         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12558         if(appData.findMirror) {
12559             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12560             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12561         }
12562     }
12563 }
12564
12565 /* Load the nth game from open file f */
12566 int
12567 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12568 {
12569     ChessMove cm;
12570     char buf[MSG_SIZ];
12571     int gn = gameNumber;
12572     ListGame *lg = NULL;
12573     int numPGNTags = 0;
12574     int err, pos = -1;
12575     GameMode oldGameMode;
12576     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12577
12578     if (appData.debugMode)
12579         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12580
12581     if (gameMode == Training )
12582         SetTrainingModeOff();
12583
12584     oldGameMode = gameMode;
12585     if (gameMode != BeginningOfGame) {
12586       Reset(FALSE, TRUE);
12587     }
12588     killX = killY = -1; // [HGM] lion: in case we did not Reset
12589
12590     gameFileFP = f;
12591     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12592         fclose(lastLoadGameFP);
12593     }
12594
12595     if (useList) {
12596         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12597
12598         if (lg) {
12599             fseek(f, lg->offset, 0);
12600             GameListHighlight(gameNumber);
12601             pos = lg->position;
12602             gn = 1;
12603         }
12604         else {
12605             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12606               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12607             else
12608             DisplayError(_("Game number out of range"), 0);
12609             return FALSE;
12610         }
12611     } else {
12612         GameListDestroy();
12613         if (fseek(f, 0, 0) == -1) {
12614             if (f == lastLoadGameFP ?
12615                 gameNumber == lastLoadGameNumber + 1 :
12616                 gameNumber == 1) {
12617                 gn = 1;
12618             } else {
12619                 DisplayError(_("Can't seek on game file"), 0);
12620                 return FALSE;
12621             }
12622         }
12623     }
12624     lastLoadGameFP = f;
12625     lastLoadGameNumber = gameNumber;
12626     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12627     lastLoadGameUseList = useList;
12628
12629     yynewfile(f);
12630
12631     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12632       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12633                 lg->gameInfo.black);
12634             DisplayTitle(buf);
12635     } else if (*title != NULLCHAR) {
12636         if (gameNumber > 1) {
12637           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12638             DisplayTitle(buf);
12639         } else {
12640             DisplayTitle(title);
12641         }
12642     }
12643
12644     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12645         gameMode = PlayFromGameFile;
12646         ModeHighlight();
12647     }
12648
12649     currentMove = forwardMostMove = backwardMostMove = 0;
12650     CopyBoard(boards[0], initialPosition);
12651     StopClocks();
12652
12653     /*
12654      * Skip the first gn-1 games in the file.
12655      * Also skip over anything that precedes an identifiable
12656      * start of game marker, to avoid being confused by
12657      * garbage at the start of the file.  Currently
12658      * recognized start of game markers are the move number "1",
12659      * the pattern "gnuchess .* game", the pattern
12660      * "^[#;%] [^ ]* game file", and a PGN tag block.
12661      * A game that starts with one of the latter two patterns
12662      * will also have a move number 1, possibly
12663      * following a position diagram.
12664      * 5-4-02: Let's try being more lenient and allowing a game to
12665      * start with an unnumbered move.  Does that break anything?
12666      */
12667     cm = lastLoadGameStart = EndOfFile;
12668     while (gn > 0) {
12669         yyboardindex = forwardMostMove;
12670         cm = (ChessMove) Myylex();
12671         switch (cm) {
12672           case EndOfFile:
12673             if (cmailMsgLoaded) {
12674                 nCmailGames = CMAIL_MAX_GAMES - gn;
12675             } else {
12676                 Reset(TRUE, TRUE);
12677                 DisplayError(_("Game not found in file"), 0);
12678             }
12679             return FALSE;
12680
12681           case GNUChessGame:
12682           case XBoardGame:
12683             gn--;
12684             lastLoadGameStart = cm;
12685             break;
12686
12687           case MoveNumberOne:
12688             switch (lastLoadGameStart) {
12689               case GNUChessGame:
12690               case XBoardGame:
12691               case PGNTag:
12692                 break;
12693               case MoveNumberOne:
12694               case EndOfFile:
12695                 gn--;           /* count this game */
12696                 lastLoadGameStart = cm;
12697                 break;
12698               default:
12699                 /* impossible */
12700                 break;
12701             }
12702             break;
12703
12704           case PGNTag:
12705             switch (lastLoadGameStart) {
12706               case GNUChessGame:
12707               case PGNTag:
12708               case MoveNumberOne:
12709               case EndOfFile:
12710                 gn--;           /* count this game */
12711                 lastLoadGameStart = cm;
12712                 break;
12713               case XBoardGame:
12714                 lastLoadGameStart = cm; /* game counted already */
12715                 break;
12716               default:
12717                 /* impossible */
12718                 break;
12719             }
12720             if (gn > 0) {
12721                 do {
12722                     yyboardindex = forwardMostMove;
12723                     cm = (ChessMove) Myylex();
12724                 } while (cm == PGNTag || cm == Comment);
12725             }
12726             break;
12727
12728           case WhiteWins:
12729           case BlackWins:
12730           case GameIsDrawn:
12731             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12732                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12733                     != CMAIL_OLD_RESULT) {
12734                     nCmailResults ++ ;
12735                     cmailResult[  CMAIL_MAX_GAMES
12736                                 - gn - 1] = CMAIL_OLD_RESULT;
12737                 }
12738             }
12739             break;
12740
12741           case NormalMove:
12742           case FirstLeg:
12743             /* Only a NormalMove can be at the start of a game
12744              * without a position diagram. */
12745             if (lastLoadGameStart == EndOfFile ) {
12746               gn--;
12747               lastLoadGameStart = MoveNumberOne;
12748             }
12749             break;
12750
12751           default:
12752             break;
12753         }
12754     }
12755
12756     if (appData.debugMode)
12757       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12758
12759     if (cm == XBoardGame) {
12760         /* Skip any header junk before position diagram and/or move 1 */
12761         for (;;) {
12762             yyboardindex = forwardMostMove;
12763             cm = (ChessMove) Myylex();
12764
12765             if (cm == EndOfFile ||
12766                 cm == GNUChessGame || cm == XBoardGame) {
12767                 /* Empty game; pretend end-of-file and handle later */
12768                 cm = EndOfFile;
12769                 break;
12770             }
12771
12772             if (cm == MoveNumberOne || cm == PositionDiagram ||
12773                 cm == PGNTag || cm == Comment)
12774               break;
12775         }
12776     } else if (cm == GNUChessGame) {
12777         if (gameInfo.event != NULL) {
12778             free(gameInfo.event);
12779         }
12780         gameInfo.event = StrSave(yy_text);
12781     }
12782
12783     startedFromSetupPosition = FALSE;
12784     while (cm == PGNTag) {
12785         if (appData.debugMode)
12786           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12787         err = ParsePGNTag(yy_text, &gameInfo);
12788         if (!err) numPGNTags++;
12789
12790         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12791         if(gameInfo.variant != oldVariant) {
12792             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12793             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12794             InitPosition(TRUE);
12795             oldVariant = gameInfo.variant;
12796             if (appData.debugMode)
12797               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12798         }
12799
12800
12801         if (gameInfo.fen != NULL) {
12802           Board initial_position;
12803           startedFromSetupPosition = TRUE;
12804           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12805             Reset(TRUE, TRUE);
12806             DisplayError(_("Bad FEN position in file"), 0);
12807             return FALSE;
12808           }
12809           CopyBoard(boards[0], initial_position);
12810           if (blackPlaysFirst) {
12811             currentMove = forwardMostMove = backwardMostMove = 1;
12812             CopyBoard(boards[1], initial_position);
12813             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12814             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12815             timeRemaining[0][1] = whiteTimeRemaining;
12816             timeRemaining[1][1] = blackTimeRemaining;
12817             if (commentList[0] != NULL) {
12818               commentList[1] = commentList[0];
12819               commentList[0] = NULL;
12820             }
12821           } else {
12822             currentMove = forwardMostMove = backwardMostMove = 0;
12823           }
12824           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12825           {   int i;
12826               initialRulePlies = FENrulePlies;
12827               for( i=0; i< nrCastlingRights; i++ )
12828                   initialRights[i] = initial_position[CASTLING][i];
12829           }
12830           yyboardindex = forwardMostMove;
12831           free(gameInfo.fen);
12832           gameInfo.fen = NULL;
12833         }
12834
12835         yyboardindex = forwardMostMove;
12836         cm = (ChessMove) Myylex();
12837
12838         /* Handle comments interspersed among the tags */
12839         while (cm == Comment) {
12840             char *p;
12841             if (appData.debugMode)
12842               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12843             p = yy_text;
12844             AppendComment(currentMove, p, FALSE);
12845             yyboardindex = forwardMostMove;
12846             cm = (ChessMove) Myylex();
12847         }
12848     }
12849
12850     /* don't rely on existence of Event tag since if game was
12851      * pasted from clipboard the Event tag may not exist
12852      */
12853     if (numPGNTags > 0){
12854         char *tags;
12855         if (gameInfo.variant == VariantNormal) {
12856           VariantClass v = StringToVariant(gameInfo.event);
12857           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12858           if(v < VariantShogi) gameInfo.variant = v;
12859         }
12860         if (!matchMode) {
12861           if( appData.autoDisplayTags ) {
12862             tags = PGNTags(&gameInfo);
12863             TagsPopUp(tags, CmailMsg());
12864             free(tags);
12865           }
12866         }
12867     } else {
12868         /* Make something up, but don't display it now */
12869         SetGameInfo();
12870         TagsPopDown();
12871     }
12872
12873     if (cm == PositionDiagram) {
12874         int i, j;
12875         char *p;
12876         Board initial_position;
12877
12878         if (appData.debugMode)
12879           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12880
12881         if (!startedFromSetupPosition) {
12882             p = yy_text;
12883             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12884               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12885                 switch (*p) {
12886                   case '{':
12887                   case '[':
12888                   case '-':
12889                   case ' ':
12890                   case '\t':
12891                   case '\n':
12892                   case '\r':
12893                     break;
12894                   default:
12895                     initial_position[i][j++] = CharToPiece(*p);
12896                     break;
12897                 }
12898             while (*p == ' ' || *p == '\t' ||
12899                    *p == '\n' || *p == '\r') p++;
12900
12901             if (strncmp(p, "black", strlen("black"))==0)
12902               blackPlaysFirst = TRUE;
12903             else
12904               blackPlaysFirst = FALSE;
12905             startedFromSetupPosition = TRUE;
12906
12907             CopyBoard(boards[0], initial_position);
12908             if (blackPlaysFirst) {
12909                 currentMove = forwardMostMove = backwardMostMove = 1;
12910                 CopyBoard(boards[1], initial_position);
12911                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12912                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12913                 timeRemaining[0][1] = whiteTimeRemaining;
12914                 timeRemaining[1][1] = blackTimeRemaining;
12915                 if (commentList[0] != NULL) {
12916                     commentList[1] = commentList[0];
12917                     commentList[0] = NULL;
12918                 }
12919             } else {
12920                 currentMove = forwardMostMove = backwardMostMove = 0;
12921             }
12922         }
12923         yyboardindex = forwardMostMove;
12924         cm = (ChessMove) Myylex();
12925     }
12926
12927   if(!creatingBook) {
12928     if (first.pr == NoProc) {
12929         StartChessProgram(&first);
12930     }
12931     InitChessProgram(&first, FALSE);
12932     SendToProgram("force\n", &first);
12933     if (startedFromSetupPosition) {
12934         SendBoard(&first, forwardMostMove);
12935     if (appData.debugMode) {
12936         fprintf(debugFP, "Load Game\n");
12937     }
12938         DisplayBothClocks();
12939     }
12940   }
12941
12942     /* [HGM] server: flag to write setup moves in broadcast file as one */
12943     loadFlag = appData.suppressLoadMoves;
12944
12945     while (cm == Comment) {
12946         char *p;
12947         if (appData.debugMode)
12948           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12949         p = yy_text;
12950         AppendComment(currentMove, p, FALSE);
12951         yyboardindex = forwardMostMove;
12952         cm = (ChessMove) Myylex();
12953     }
12954
12955     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12956         cm == WhiteWins || cm == BlackWins ||
12957         cm == GameIsDrawn || cm == GameUnfinished) {
12958         DisplayMessage("", _("No moves in game"));
12959         if (cmailMsgLoaded) {
12960             if (appData.debugMode)
12961               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12962             ClearHighlights();
12963             flipView = FALSE;
12964         }
12965         DrawPosition(FALSE, boards[currentMove]);
12966         DisplayBothClocks();
12967         gameMode = EditGame;
12968         ModeHighlight();
12969         gameFileFP = NULL;
12970         cmailOldMove = 0;
12971         return TRUE;
12972     }
12973
12974     // [HGM] PV info: routine tests if comment empty
12975     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12976         DisplayComment(currentMove - 1, commentList[currentMove]);
12977     }
12978     if (!matchMode && appData.timeDelay != 0)
12979       DrawPosition(FALSE, boards[currentMove]);
12980
12981     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12982       programStats.ok_to_send = 1;
12983     }
12984
12985     /* if the first token after the PGN tags is a move
12986      * and not move number 1, retrieve it from the parser
12987      */
12988     if (cm != MoveNumberOne)
12989         LoadGameOneMove(cm);
12990
12991     /* load the remaining moves from the file */
12992     while (LoadGameOneMove(EndOfFile)) {
12993       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12994       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12995     }
12996
12997     /* rewind to the start of the game */
12998     currentMove = backwardMostMove;
12999
13000     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13001
13002     if (oldGameMode == AnalyzeFile) {
13003       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13004       AnalyzeFileEvent();
13005     } else
13006     if (oldGameMode == AnalyzeMode) {
13007       AnalyzeFileEvent();
13008     }
13009
13010     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13011         long int w, b; // [HGM] adjourn: restore saved clock times
13012         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13013         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13014             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13015             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13016         }
13017     }
13018
13019     if(creatingBook) return TRUE;
13020     if (!matchMode && pos > 0) {
13021         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13022     } else
13023     if (matchMode || appData.timeDelay == 0) {
13024       ToEndEvent();
13025     } else if (appData.timeDelay > 0) {
13026       AutoPlayGameLoop();
13027     }
13028
13029     if (appData.debugMode)
13030         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13031
13032     loadFlag = 0; /* [HGM] true game starts */
13033     return TRUE;
13034 }
13035
13036 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13037 int
13038 ReloadPosition (int offset)
13039 {
13040     int positionNumber = lastLoadPositionNumber + offset;
13041     if (lastLoadPositionFP == NULL) {
13042         DisplayError(_("No position has been loaded yet"), 0);
13043         return FALSE;
13044     }
13045     if (positionNumber <= 0) {
13046         DisplayError(_("Can't back up any further"), 0);
13047         return FALSE;
13048     }
13049     return LoadPosition(lastLoadPositionFP, positionNumber,
13050                         lastLoadPositionTitle);
13051 }
13052
13053 /* Load the nth position from the given file */
13054 int
13055 LoadPositionFromFile (char *filename, int n, char *title)
13056 {
13057     FILE *f;
13058     char buf[MSG_SIZ];
13059
13060     if (strcmp(filename, "-") == 0) {
13061         return LoadPosition(stdin, n, "stdin");
13062     } else {
13063         f = fopen(filename, "rb");
13064         if (f == NULL) {
13065             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13066             DisplayError(buf, errno);
13067             return FALSE;
13068         } else {
13069             return LoadPosition(f, n, title);
13070         }
13071     }
13072 }
13073
13074 /* Load the nth position from the given open file, and close it */
13075 int
13076 LoadPosition (FILE *f, int positionNumber, char *title)
13077 {
13078     char *p, line[MSG_SIZ];
13079     Board initial_position;
13080     int i, j, fenMode, pn;
13081
13082     if (gameMode == Training )
13083         SetTrainingModeOff();
13084
13085     if (gameMode != BeginningOfGame) {
13086         Reset(FALSE, TRUE);
13087     }
13088     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13089         fclose(lastLoadPositionFP);
13090     }
13091     if (positionNumber == 0) positionNumber = 1;
13092     lastLoadPositionFP = f;
13093     lastLoadPositionNumber = positionNumber;
13094     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13095     if (first.pr == NoProc && !appData.noChessProgram) {
13096       StartChessProgram(&first);
13097       InitChessProgram(&first, FALSE);
13098     }
13099     pn = positionNumber;
13100     if (positionNumber < 0) {
13101         /* Negative position number means to seek to that byte offset */
13102         if (fseek(f, -positionNumber, 0) == -1) {
13103             DisplayError(_("Can't seek on position file"), 0);
13104             return FALSE;
13105         };
13106         pn = 1;
13107     } else {
13108         if (fseek(f, 0, 0) == -1) {
13109             if (f == lastLoadPositionFP ?
13110                 positionNumber == lastLoadPositionNumber + 1 :
13111                 positionNumber == 1) {
13112                 pn = 1;
13113             } else {
13114                 DisplayError(_("Can't seek on position file"), 0);
13115                 return FALSE;
13116             }
13117         }
13118     }
13119     /* See if this file is FEN or old-style xboard */
13120     if (fgets(line, MSG_SIZ, f) == NULL) {
13121         DisplayError(_("Position not found in file"), 0);
13122         return FALSE;
13123     }
13124     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13125     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13126
13127     if (pn >= 2) {
13128         if (fenMode || line[0] == '#') pn--;
13129         while (pn > 0) {
13130             /* skip positions before number pn */
13131             if (fgets(line, MSG_SIZ, f) == NULL) {
13132                 Reset(TRUE, TRUE);
13133                 DisplayError(_("Position not found in file"), 0);
13134                 return FALSE;
13135             }
13136             if (fenMode || line[0] == '#') pn--;
13137         }
13138     }
13139
13140     if (fenMode) {
13141         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13142             DisplayError(_("Bad FEN position in file"), 0);
13143             return FALSE;
13144         }
13145     } else {
13146         (void) fgets(line, MSG_SIZ, f);
13147         (void) fgets(line, MSG_SIZ, f);
13148
13149         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13150             (void) fgets(line, MSG_SIZ, f);
13151             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13152                 if (*p == ' ')
13153                   continue;
13154                 initial_position[i][j++] = CharToPiece(*p);
13155             }
13156         }
13157
13158         blackPlaysFirst = FALSE;
13159         if (!feof(f)) {
13160             (void) fgets(line, MSG_SIZ, f);
13161             if (strncmp(line, "black", strlen("black"))==0)
13162               blackPlaysFirst = TRUE;
13163         }
13164     }
13165     startedFromSetupPosition = TRUE;
13166
13167     CopyBoard(boards[0], initial_position);
13168     if (blackPlaysFirst) {
13169         currentMove = forwardMostMove = backwardMostMove = 1;
13170         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13171         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13172         CopyBoard(boards[1], initial_position);
13173         DisplayMessage("", _("Black to play"));
13174     } else {
13175         currentMove = forwardMostMove = backwardMostMove = 0;
13176         DisplayMessage("", _("White to play"));
13177     }
13178     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13179     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13180         SendToProgram("force\n", &first);
13181         SendBoard(&first, forwardMostMove);
13182     }
13183     if (appData.debugMode) {
13184 int i, j;
13185   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13186   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13187         fprintf(debugFP, "Load Position\n");
13188     }
13189
13190     if (positionNumber > 1) {
13191       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13192         DisplayTitle(line);
13193     } else {
13194         DisplayTitle(title);
13195     }
13196     gameMode = EditGame;
13197     ModeHighlight();
13198     ResetClocks();
13199     timeRemaining[0][1] = whiteTimeRemaining;
13200     timeRemaining[1][1] = blackTimeRemaining;
13201     DrawPosition(FALSE, boards[currentMove]);
13202
13203     return TRUE;
13204 }
13205
13206
13207 void
13208 CopyPlayerNameIntoFileName (char **dest, char *src)
13209 {
13210     while (*src != NULLCHAR && *src != ',') {
13211         if (*src == ' ') {
13212             *(*dest)++ = '_';
13213             src++;
13214         } else {
13215             *(*dest)++ = *src++;
13216         }
13217     }
13218 }
13219
13220 char *
13221 DefaultFileName (char *ext)
13222 {
13223     static char def[MSG_SIZ];
13224     char *p;
13225
13226     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13227         p = def;
13228         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13229         *p++ = '-';
13230         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13231         *p++ = '.';
13232         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13233     } else {
13234         def[0] = NULLCHAR;
13235     }
13236     return def;
13237 }
13238
13239 /* Save the current game to the given file */
13240 int
13241 SaveGameToFile (char *filename, int append)
13242 {
13243     FILE *f;
13244     char buf[MSG_SIZ];
13245     int result, i, t,tot=0;
13246
13247     if (strcmp(filename, "-") == 0) {
13248         return SaveGame(stdout, 0, NULL);
13249     } else {
13250         for(i=0; i<10; i++) { // upto 10 tries
13251              f = fopen(filename, append ? "a" : "w");
13252              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13253              if(f || errno != 13) break;
13254              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13255              tot += t;
13256         }
13257         if (f == NULL) {
13258             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13259             DisplayError(buf, errno);
13260             return FALSE;
13261         } else {
13262             safeStrCpy(buf, lastMsg, MSG_SIZ);
13263             DisplayMessage(_("Waiting for access to save file"), "");
13264             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13265             DisplayMessage(_("Saving game"), "");
13266             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13267             result = SaveGame(f, 0, NULL);
13268             DisplayMessage(buf, "");
13269             return result;
13270         }
13271     }
13272 }
13273
13274 char *
13275 SavePart (char *str)
13276 {
13277     static char buf[MSG_SIZ];
13278     char *p;
13279
13280     p = strchr(str, ' ');
13281     if (p == NULL) return str;
13282     strncpy(buf, str, p - str);
13283     buf[p - str] = NULLCHAR;
13284     return buf;
13285 }
13286
13287 #define PGN_MAX_LINE 75
13288
13289 #define PGN_SIDE_WHITE  0
13290 #define PGN_SIDE_BLACK  1
13291
13292 static int
13293 FindFirstMoveOutOfBook (int side)
13294 {
13295     int result = -1;
13296
13297     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13298         int index = backwardMostMove;
13299         int has_book_hit = 0;
13300
13301         if( (index % 2) != side ) {
13302             index++;
13303         }
13304
13305         while( index < forwardMostMove ) {
13306             /* Check to see if engine is in book */
13307             int depth = pvInfoList[index].depth;
13308             int score = pvInfoList[index].score;
13309             int in_book = 0;
13310
13311             if( depth <= 2 ) {
13312                 in_book = 1;
13313             }
13314             else if( score == 0 && depth == 63 ) {
13315                 in_book = 1; /* Zappa */
13316             }
13317             else if( score == 2 && depth == 99 ) {
13318                 in_book = 1; /* Abrok */
13319             }
13320
13321             has_book_hit += in_book;
13322
13323             if( ! in_book ) {
13324                 result = index;
13325
13326                 break;
13327             }
13328
13329             index += 2;
13330         }
13331     }
13332
13333     return result;
13334 }
13335
13336 void
13337 GetOutOfBookInfo (char * buf)
13338 {
13339     int oob[2];
13340     int i;
13341     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13342
13343     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13344     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13345
13346     *buf = '\0';
13347
13348     if( oob[0] >= 0 || oob[1] >= 0 ) {
13349         for( i=0; i<2; i++ ) {
13350             int idx = oob[i];
13351
13352             if( idx >= 0 ) {
13353                 if( i > 0 && oob[0] >= 0 ) {
13354                     strcat( buf, "   " );
13355                 }
13356
13357                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13358                 sprintf( buf+strlen(buf), "%s%.2f",
13359                     pvInfoList[idx].score >= 0 ? "+" : "",
13360                     pvInfoList[idx].score / 100.0 );
13361             }
13362         }
13363     }
13364 }
13365
13366 /* Save game in PGN style and close the file */
13367 int
13368 SaveGamePGN (FILE *f)
13369 {
13370     int i, offset, linelen, newblock;
13371 //    char *movetext;
13372     char numtext[32];
13373     int movelen, numlen, blank;
13374     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13375
13376     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13377
13378     PrintPGNTags(f, &gameInfo);
13379
13380     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13381
13382     if (backwardMostMove > 0 || startedFromSetupPosition) {
13383         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13384         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13385         fprintf(f, "\n{--------------\n");
13386         PrintPosition(f, backwardMostMove);
13387         fprintf(f, "--------------}\n");
13388         free(fen);
13389     }
13390     else {
13391         /* [AS] Out of book annotation */
13392         if( appData.saveOutOfBookInfo ) {
13393             char buf[64];
13394
13395             GetOutOfBookInfo( buf );
13396
13397             if( buf[0] != '\0' ) {
13398                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13399             }
13400         }
13401
13402         fprintf(f, "\n");
13403     }
13404
13405     i = backwardMostMove;
13406     linelen = 0;
13407     newblock = TRUE;
13408
13409     while (i < forwardMostMove) {
13410         /* Print comments preceding this move */
13411         if (commentList[i] != NULL) {
13412             if (linelen > 0) fprintf(f, "\n");
13413             fprintf(f, "%s", commentList[i]);
13414             linelen = 0;
13415             newblock = TRUE;
13416         }
13417
13418         /* Format move number */
13419         if ((i % 2) == 0)
13420           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13421         else
13422           if (newblock)
13423             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13424           else
13425             numtext[0] = NULLCHAR;
13426
13427         numlen = strlen(numtext);
13428         newblock = FALSE;
13429
13430         /* Print move number */
13431         blank = linelen > 0 && numlen > 0;
13432         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13433             fprintf(f, "\n");
13434             linelen = 0;
13435             blank = 0;
13436         }
13437         if (blank) {
13438             fprintf(f, " ");
13439             linelen++;
13440         }
13441         fprintf(f, "%s", numtext);
13442         linelen += numlen;
13443
13444         /* Get move */
13445         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13446         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13447
13448         /* Print move */
13449         blank = linelen > 0 && movelen > 0;
13450         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13451             fprintf(f, "\n");
13452             linelen = 0;
13453             blank = 0;
13454         }
13455         if (blank) {
13456             fprintf(f, " ");
13457             linelen++;
13458         }
13459         fprintf(f, "%s", move_buffer);
13460         linelen += movelen;
13461
13462         /* [AS] Add PV info if present */
13463         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13464             /* [HGM] add time */
13465             char buf[MSG_SIZ]; int seconds;
13466
13467             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13468
13469             if( seconds <= 0)
13470               buf[0] = 0;
13471             else
13472               if( seconds < 30 )
13473                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13474               else
13475                 {
13476                   seconds = (seconds + 4)/10; // round to full seconds
13477                   if( seconds < 60 )
13478                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13479                   else
13480                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13481                 }
13482
13483             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13484                       pvInfoList[i].score >= 0 ? "+" : "",
13485                       pvInfoList[i].score / 100.0,
13486                       pvInfoList[i].depth,
13487                       buf );
13488
13489             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13490
13491             /* Print score/depth */
13492             blank = linelen > 0 && movelen > 0;
13493             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13494                 fprintf(f, "\n");
13495                 linelen = 0;
13496                 blank = 0;
13497             }
13498             if (blank) {
13499                 fprintf(f, " ");
13500                 linelen++;
13501             }
13502             fprintf(f, "%s", move_buffer);
13503             linelen += movelen;
13504         }
13505
13506         i++;
13507     }
13508
13509     /* Start a new line */
13510     if (linelen > 0) fprintf(f, "\n");
13511
13512     /* Print comments after last move */
13513     if (commentList[i] != NULL) {
13514         fprintf(f, "%s\n", commentList[i]);
13515     }
13516
13517     /* Print result */
13518     if (gameInfo.resultDetails != NULL &&
13519         gameInfo.resultDetails[0] != NULLCHAR) {
13520         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13521         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13522            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13523             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13524         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13525     } else {
13526         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13527     }
13528
13529     fclose(f);
13530     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13531     return TRUE;
13532 }
13533
13534 /* Save game in old style and close the file */
13535 int
13536 SaveGameOldStyle (FILE *f)
13537 {
13538     int i, offset;
13539     time_t tm;
13540
13541     tm = time((time_t *) NULL);
13542
13543     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13544     PrintOpponents(f);
13545
13546     if (backwardMostMove > 0 || startedFromSetupPosition) {
13547         fprintf(f, "\n[--------------\n");
13548         PrintPosition(f, backwardMostMove);
13549         fprintf(f, "--------------]\n");
13550     } else {
13551         fprintf(f, "\n");
13552     }
13553
13554     i = backwardMostMove;
13555     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13556
13557     while (i < forwardMostMove) {
13558         if (commentList[i] != NULL) {
13559             fprintf(f, "[%s]\n", commentList[i]);
13560         }
13561
13562         if ((i % 2) == 1) {
13563             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13564             i++;
13565         } else {
13566             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13567             i++;
13568             if (commentList[i] != NULL) {
13569                 fprintf(f, "\n");
13570                 continue;
13571             }
13572             if (i >= forwardMostMove) {
13573                 fprintf(f, "\n");
13574                 break;
13575             }
13576             fprintf(f, "%s\n", parseList[i]);
13577             i++;
13578         }
13579     }
13580
13581     if (commentList[i] != NULL) {
13582         fprintf(f, "[%s]\n", commentList[i]);
13583     }
13584
13585     /* This isn't really the old style, but it's close enough */
13586     if (gameInfo.resultDetails != NULL &&
13587         gameInfo.resultDetails[0] != NULLCHAR) {
13588         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13589                 gameInfo.resultDetails);
13590     } else {
13591         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13592     }
13593
13594     fclose(f);
13595     return TRUE;
13596 }
13597
13598 /* Save the current game to open file f and close the file */
13599 int
13600 SaveGame (FILE *f, int dummy, char *dummy2)
13601 {
13602     if (gameMode == EditPosition) EditPositionDone(TRUE);
13603     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13604     if (appData.oldSaveStyle)
13605       return SaveGameOldStyle(f);
13606     else
13607       return SaveGamePGN(f);
13608 }
13609
13610 /* Save the current position to the given file */
13611 int
13612 SavePositionToFile (char *filename)
13613 {
13614     FILE *f;
13615     char buf[MSG_SIZ];
13616
13617     if (strcmp(filename, "-") == 0) {
13618         return SavePosition(stdout, 0, NULL);
13619     } else {
13620         f = fopen(filename, "a");
13621         if (f == NULL) {
13622             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13623             DisplayError(buf, errno);
13624             return FALSE;
13625         } else {
13626             safeStrCpy(buf, lastMsg, MSG_SIZ);
13627             DisplayMessage(_("Waiting for access to save file"), "");
13628             flock(fileno(f), LOCK_EX); // [HGM] lock
13629             DisplayMessage(_("Saving position"), "");
13630             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13631             SavePosition(f, 0, NULL);
13632             DisplayMessage(buf, "");
13633             return TRUE;
13634         }
13635     }
13636 }
13637
13638 /* Save the current position to the given open file and close the file */
13639 int
13640 SavePosition (FILE *f, int dummy, char *dummy2)
13641 {
13642     time_t tm;
13643     char *fen;
13644
13645     if (gameMode == EditPosition) EditPositionDone(TRUE);
13646     if (appData.oldSaveStyle) {
13647         tm = time((time_t *) NULL);
13648
13649         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13650         PrintOpponents(f);
13651         fprintf(f, "[--------------\n");
13652         PrintPosition(f, currentMove);
13653         fprintf(f, "--------------]\n");
13654     } else {
13655         fen = PositionToFEN(currentMove, NULL, 1);
13656         fprintf(f, "%s\n", fen);
13657         free(fen);
13658     }
13659     fclose(f);
13660     return TRUE;
13661 }
13662
13663 void
13664 ReloadCmailMsgEvent (int unregister)
13665 {
13666 #if !WIN32
13667     static char *inFilename = NULL;
13668     static char *outFilename;
13669     int i;
13670     struct stat inbuf, outbuf;
13671     int status;
13672
13673     /* Any registered moves are unregistered if unregister is set, */
13674     /* i.e. invoked by the signal handler */
13675     if (unregister) {
13676         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13677             cmailMoveRegistered[i] = FALSE;
13678             if (cmailCommentList[i] != NULL) {
13679                 free(cmailCommentList[i]);
13680                 cmailCommentList[i] = NULL;
13681             }
13682         }
13683         nCmailMovesRegistered = 0;
13684     }
13685
13686     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13687         cmailResult[i] = CMAIL_NOT_RESULT;
13688     }
13689     nCmailResults = 0;
13690
13691     if (inFilename == NULL) {
13692         /* Because the filenames are static they only get malloced once  */
13693         /* and they never get freed                                      */
13694         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13695         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13696
13697         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13698         sprintf(outFilename, "%s.out", appData.cmailGameName);
13699     }
13700
13701     status = stat(outFilename, &outbuf);
13702     if (status < 0) {
13703         cmailMailedMove = FALSE;
13704     } else {
13705         status = stat(inFilename, &inbuf);
13706         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13707     }
13708
13709     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13710        counts the games, notes how each one terminated, etc.
13711
13712        It would be nice to remove this kludge and instead gather all
13713        the information while building the game list.  (And to keep it
13714        in the game list nodes instead of having a bunch of fixed-size
13715        parallel arrays.)  Note this will require getting each game's
13716        termination from the PGN tags, as the game list builder does
13717        not process the game moves.  --mann
13718        */
13719     cmailMsgLoaded = TRUE;
13720     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13721
13722     /* Load first game in the file or popup game menu */
13723     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13724
13725 #endif /* !WIN32 */
13726     return;
13727 }
13728
13729 int
13730 RegisterMove ()
13731 {
13732     FILE *f;
13733     char string[MSG_SIZ];
13734
13735     if (   cmailMailedMove
13736         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13737         return TRUE;            /* Allow free viewing  */
13738     }
13739
13740     /* Unregister move to ensure that we don't leave RegisterMove        */
13741     /* with the move registered when the conditions for registering no   */
13742     /* longer hold                                                       */
13743     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13744         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13745         nCmailMovesRegistered --;
13746
13747         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13748           {
13749               free(cmailCommentList[lastLoadGameNumber - 1]);
13750               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13751           }
13752     }
13753
13754     if (cmailOldMove == -1) {
13755         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13756         return FALSE;
13757     }
13758
13759     if (currentMove > cmailOldMove + 1) {
13760         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13761         return FALSE;
13762     }
13763
13764     if (currentMove < cmailOldMove) {
13765         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13766         return FALSE;
13767     }
13768
13769     if (forwardMostMove > currentMove) {
13770         /* Silently truncate extra moves */
13771         TruncateGame();
13772     }
13773
13774     if (   (currentMove == cmailOldMove + 1)
13775         || (   (currentMove == cmailOldMove)
13776             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13777                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13778         if (gameInfo.result != GameUnfinished) {
13779             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13780         }
13781
13782         if (commentList[currentMove] != NULL) {
13783             cmailCommentList[lastLoadGameNumber - 1]
13784               = StrSave(commentList[currentMove]);
13785         }
13786         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13787
13788         if (appData.debugMode)
13789           fprintf(debugFP, "Saving %s for game %d\n",
13790                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13791
13792         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13793
13794         f = fopen(string, "w");
13795         if (appData.oldSaveStyle) {
13796             SaveGameOldStyle(f); /* also closes the file */
13797
13798             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13799             f = fopen(string, "w");
13800             SavePosition(f, 0, NULL); /* also closes the file */
13801         } else {
13802             fprintf(f, "{--------------\n");
13803             PrintPosition(f, currentMove);
13804             fprintf(f, "--------------}\n\n");
13805
13806             SaveGame(f, 0, NULL); /* also closes the file*/
13807         }
13808
13809         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13810         nCmailMovesRegistered ++;
13811     } else if (nCmailGames == 1) {
13812         DisplayError(_("You have not made a move yet"), 0);
13813         return FALSE;
13814     }
13815
13816     return TRUE;
13817 }
13818
13819 void
13820 MailMoveEvent ()
13821 {
13822 #if !WIN32
13823     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13824     FILE *commandOutput;
13825     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13826     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13827     int nBuffers;
13828     int i;
13829     int archived;
13830     char *arcDir;
13831
13832     if (! cmailMsgLoaded) {
13833         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13834         return;
13835     }
13836
13837     if (nCmailGames == nCmailResults) {
13838         DisplayError(_("No unfinished games"), 0);
13839         return;
13840     }
13841
13842 #if CMAIL_PROHIBIT_REMAIL
13843     if (cmailMailedMove) {
13844       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);
13845         DisplayError(msg, 0);
13846         return;
13847     }
13848 #endif
13849
13850     if (! (cmailMailedMove || RegisterMove())) return;
13851
13852     if (   cmailMailedMove
13853         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13854       snprintf(string, MSG_SIZ, partCommandString,
13855                appData.debugMode ? " -v" : "", appData.cmailGameName);
13856         commandOutput = popen(string, "r");
13857
13858         if (commandOutput == NULL) {
13859             DisplayError(_("Failed to invoke cmail"), 0);
13860         } else {
13861             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13862                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13863             }
13864             if (nBuffers > 1) {
13865                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13866                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13867                 nBytes = MSG_SIZ - 1;
13868             } else {
13869                 (void) memcpy(msg, buffer, nBytes);
13870             }
13871             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13872
13873             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13874                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13875
13876                 archived = TRUE;
13877                 for (i = 0; i < nCmailGames; i ++) {
13878                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13879                         archived = FALSE;
13880                     }
13881                 }
13882                 if (   archived
13883                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13884                         != NULL)) {
13885                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13886                            arcDir,
13887                            appData.cmailGameName,
13888                            gameInfo.date);
13889                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13890                     cmailMsgLoaded = FALSE;
13891                 }
13892             }
13893
13894             DisplayInformation(msg);
13895             pclose(commandOutput);
13896         }
13897     } else {
13898         if ((*cmailMsg) != '\0') {
13899             DisplayInformation(cmailMsg);
13900         }
13901     }
13902
13903     return;
13904 #endif /* !WIN32 */
13905 }
13906
13907 char *
13908 CmailMsg ()
13909 {
13910 #if WIN32
13911     return NULL;
13912 #else
13913     int  prependComma = 0;
13914     char number[5];
13915     char string[MSG_SIZ];       /* Space for game-list */
13916     int  i;
13917
13918     if (!cmailMsgLoaded) return "";
13919
13920     if (cmailMailedMove) {
13921       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13922     } else {
13923         /* Create a list of games left */
13924       snprintf(string, MSG_SIZ, "[");
13925         for (i = 0; i < nCmailGames; i ++) {
13926             if (! (   cmailMoveRegistered[i]
13927                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13928                 if (prependComma) {
13929                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13930                 } else {
13931                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13932                     prependComma = 1;
13933                 }
13934
13935                 strcat(string, number);
13936             }
13937         }
13938         strcat(string, "]");
13939
13940         if (nCmailMovesRegistered + nCmailResults == 0) {
13941             switch (nCmailGames) {
13942               case 1:
13943                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13944                 break;
13945
13946               case 2:
13947                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13948                 break;
13949
13950               default:
13951                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13952                          nCmailGames);
13953                 break;
13954             }
13955         } else {
13956             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13957               case 1:
13958                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13959                          string);
13960                 break;
13961
13962               case 0:
13963                 if (nCmailResults == nCmailGames) {
13964                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13965                 } else {
13966                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13967                 }
13968                 break;
13969
13970               default:
13971                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13972                          string);
13973             }
13974         }
13975     }
13976     return cmailMsg;
13977 #endif /* WIN32 */
13978 }
13979
13980 void
13981 ResetGameEvent ()
13982 {
13983     if (gameMode == Training)
13984       SetTrainingModeOff();
13985
13986     Reset(TRUE, TRUE);
13987     cmailMsgLoaded = FALSE;
13988     if (appData.icsActive) {
13989       SendToICS(ics_prefix);
13990       SendToICS("refresh\n");
13991     }
13992 }
13993
13994 void
13995 ExitEvent (int status)
13996 {
13997     exiting++;
13998     if (exiting > 2) {
13999       /* Give up on clean exit */
14000       exit(status);
14001     }
14002     if (exiting > 1) {
14003       /* Keep trying for clean exit */
14004       return;
14005     }
14006
14007     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14008     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14009
14010     if (telnetISR != NULL) {
14011       RemoveInputSource(telnetISR);
14012     }
14013     if (icsPR != NoProc) {
14014       DestroyChildProcess(icsPR, TRUE);
14015     }
14016
14017     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14018     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14019
14020     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14021     /* make sure this other one finishes before killing it!                  */
14022     if(endingGame) { int count = 0;
14023         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14024         while(endingGame && count++ < 10) DoSleep(1);
14025         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14026     }
14027
14028     /* Kill off chess programs */
14029     if (first.pr != NoProc) {
14030         ExitAnalyzeMode();
14031
14032         DoSleep( appData.delayBeforeQuit );
14033         SendToProgram("quit\n", &first);
14034         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14035     }
14036     if (second.pr != NoProc) {
14037         DoSleep( appData.delayBeforeQuit );
14038         SendToProgram("quit\n", &second);
14039         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14040     }
14041     if (first.isr != NULL) {
14042         RemoveInputSource(first.isr);
14043     }
14044     if (second.isr != NULL) {
14045         RemoveInputSource(second.isr);
14046     }
14047
14048     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14049     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14050
14051     ShutDownFrontEnd();
14052     exit(status);
14053 }
14054
14055 void
14056 PauseEngine (ChessProgramState *cps)
14057 {
14058     SendToProgram("pause\n", cps);
14059     cps->pause = 2;
14060 }
14061
14062 void
14063 UnPauseEngine (ChessProgramState *cps)
14064 {
14065     SendToProgram("resume\n", cps);
14066     cps->pause = 1;
14067 }
14068
14069 void
14070 PauseEvent ()
14071 {
14072     if (appData.debugMode)
14073         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14074     if (pausing) {
14075         pausing = FALSE;
14076         ModeHighlight();
14077         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14078             StartClocks();
14079             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14080                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14081                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14082             }
14083             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14084             HandleMachineMove(stashedInputMove, stalledEngine);
14085             stalledEngine = NULL;
14086             return;
14087         }
14088         if (gameMode == MachinePlaysWhite ||
14089             gameMode == TwoMachinesPlay   ||
14090             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14091             if(first.pause)  UnPauseEngine(&first);
14092             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14093             if(second.pause) UnPauseEngine(&second);
14094             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14095             StartClocks();
14096         } else {
14097             DisplayBothClocks();
14098         }
14099         if (gameMode == PlayFromGameFile) {
14100             if (appData.timeDelay >= 0)
14101                 AutoPlayGameLoop();
14102         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14103             Reset(FALSE, TRUE);
14104             SendToICS(ics_prefix);
14105             SendToICS("refresh\n");
14106         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14107             ForwardInner(forwardMostMove);
14108         }
14109         pauseExamInvalid = FALSE;
14110     } else {
14111         switch (gameMode) {
14112           default:
14113             return;
14114           case IcsExamining:
14115             pauseExamForwardMostMove = forwardMostMove;
14116             pauseExamInvalid = FALSE;
14117             /* fall through */
14118           case IcsObserving:
14119           case IcsPlayingWhite:
14120           case IcsPlayingBlack:
14121             pausing = TRUE;
14122             ModeHighlight();
14123             return;
14124           case PlayFromGameFile:
14125             (void) StopLoadGameTimer();
14126             pausing = TRUE;
14127             ModeHighlight();
14128             break;
14129           case BeginningOfGame:
14130             if (appData.icsActive) return;
14131             /* else fall through */
14132           case MachinePlaysWhite:
14133           case MachinePlaysBlack:
14134           case TwoMachinesPlay:
14135             if (forwardMostMove == 0)
14136               return;           /* don't pause if no one has moved */
14137             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14138                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14139                 if(onMove->pause) {           // thinking engine can be paused
14140                     PauseEngine(onMove);      // do it
14141                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14142                         PauseEngine(onMove->other);
14143                     else
14144                         SendToProgram("easy\n", onMove->other);
14145                     StopClocks();
14146                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14147             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14148                 if(first.pause) {
14149                     PauseEngine(&first);
14150                     StopClocks();
14151                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14152             } else { // human on move, pause pondering by either method
14153                 if(first.pause)
14154                     PauseEngine(&first);
14155                 else if(appData.ponderNextMove)
14156                     SendToProgram("easy\n", &first);
14157                 StopClocks();
14158             }
14159             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14160           case AnalyzeMode:
14161             pausing = TRUE;
14162             ModeHighlight();
14163             break;
14164         }
14165     }
14166 }
14167
14168 void
14169 EditCommentEvent ()
14170 {
14171     char title[MSG_SIZ];
14172
14173     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14174       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14175     } else {
14176       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14177                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14178                parseList[currentMove - 1]);
14179     }
14180
14181     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14182 }
14183
14184
14185 void
14186 EditTagsEvent ()
14187 {
14188     char *tags = PGNTags(&gameInfo);
14189     bookUp = FALSE;
14190     EditTagsPopUp(tags, NULL);
14191     free(tags);
14192 }
14193
14194 void
14195 ToggleSecond ()
14196 {
14197   if(second.analyzing) {
14198     SendToProgram("exit\n", &second);
14199     second.analyzing = FALSE;
14200   } else {
14201     if (second.pr == NoProc) StartChessProgram(&second);
14202     InitChessProgram(&second, FALSE);
14203     FeedMovesToProgram(&second, currentMove);
14204
14205     SendToProgram("analyze\n", &second);
14206     second.analyzing = TRUE;
14207   }
14208 }
14209
14210 /* Toggle ShowThinking */
14211 void
14212 ToggleShowThinking()
14213 {
14214   appData.showThinking = !appData.showThinking;
14215   ShowThinkingEvent();
14216 }
14217
14218 int
14219 AnalyzeModeEvent ()
14220 {
14221     char buf[MSG_SIZ];
14222
14223     if (!first.analysisSupport) {
14224       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14225       DisplayError(buf, 0);
14226       return 0;
14227     }
14228     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14229     if (appData.icsActive) {
14230         if (gameMode != IcsObserving) {
14231           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14232             DisplayError(buf, 0);
14233             /* secure check */
14234             if (appData.icsEngineAnalyze) {
14235                 if (appData.debugMode)
14236                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14237                 ExitAnalyzeMode();
14238                 ModeHighlight();
14239             }
14240             return 0;
14241         }
14242         /* if enable, user wants to disable icsEngineAnalyze */
14243         if (appData.icsEngineAnalyze) {
14244                 ExitAnalyzeMode();
14245                 ModeHighlight();
14246                 return 0;
14247         }
14248         appData.icsEngineAnalyze = TRUE;
14249         if (appData.debugMode)
14250             fprintf(debugFP, "ICS engine analyze starting... \n");
14251     }
14252
14253     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14254     if (appData.noChessProgram || gameMode == AnalyzeMode)
14255       return 0;
14256
14257     if (gameMode != AnalyzeFile) {
14258         if (!appData.icsEngineAnalyze) {
14259                EditGameEvent();
14260                if (gameMode != EditGame) return 0;
14261         }
14262         if (!appData.showThinking) ToggleShowThinking();
14263         ResurrectChessProgram();
14264         SendToProgram("analyze\n", &first);
14265         first.analyzing = TRUE;
14266         /*first.maybeThinking = TRUE;*/
14267         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14268         EngineOutputPopUp();
14269     }
14270     if (!appData.icsEngineAnalyze) {
14271         gameMode = AnalyzeMode;
14272         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14273     }
14274     pausing = FALSE;
14275     ModeHighlight();
14276     SetGameInfo();
14277
14278     StartAnalysisClock();
14279     GetTimeMark(&lastNodeCountTime);
14280     lastNodeCount = 0;
14281     return 1;
14282 }
14283
14284 void
14285 AnalyzeFileEvent ()
14286 {
14287     if (appData.noChessProgram || gameMode == AnalyzeFile)
14288       return;
14289
14290     if (!first.analysisSupport) {
14291       char buf[MSG_SIZ];
14292       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14293       DisplayError(buf, 0);
14294       return;
14295     }
14296
14297     if (gameMode != AnalyzeMode) {
14298         keepInfo = 1; // mere annotating should not alter PGN tags
14299         EditGameEvent();
14300         keepInfo = 0;
14301         if (gameMode != EditGame) return;
14302         if (!appData.showThinking) ToggleShowThinking();
14303         ResurrectChessProgram();
14304         SendToProgram("analyze\n", &first);
14305         first.analyzing = TRUE;
14306         /*first.maybeThinking = TRUE;*/
14307         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14308         EngineOutputPopUp();
14309     }
14310     gameMode = AnalyzeFile;
14311     pausing = FALSE;
14312     ModeHighlight();
14313
14314     StartAnalysisClock();
14315     GetTimeMark(&lastNodeCountTime);
14316     lastNodeCount = 0;
14317     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14318     AnalysisPeriodicEvent(1);
14319 }
14320
14321 void
14322 MachineWhiteEvent ()
14323 {
14324     char buf[MSG_SIZ];
14325     char *bookHit = NULL;
14326
14327     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14328       return;
14329
14330
14331     if (gameMode == PlayFromGameFile ||
14332         gameMode == TwoMachinesPlay  ||
14333         gameMode == Training         ||
14334         gameMode == AnalyzeMode      ||
14335         gameMode == EndOfGame)
14336         EditGameEvent();
14337
14338     if (gameMode == EditPosition)
14339         EditPositionDone(TRUE);
14340
14341     if (!WhiteOnMove(currentMove)) {
14342         DisplayError(_("It is not White's turn"), 0);
14343         return;
14344     }
14345
14346     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14347       ExitAnalyzeMode();
14348
14349     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14350         gameMode == AnalyzeFile)
14351         TruncateGame();
14352
14353     ResurrectChessProgram();    /* in case it isn't running */
14354     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14355         gameMode = MachinePlaysWhite;
14356         ResetClocks();
14357     } else
14358     gameMode = MachinePlaysWhite;
14359     pausing = FALSE;
14360     ModeHighlight();
14361     SetGameInfo();
14362     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14363     DisplayTitle(buf);
14364     if (first.sendName) {
14365       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14366       SendToProgram(buf, &first);
14367     }
14368     if (first.sendTime) {
14369       if (first.useColors) {
14370         SendToProgram("black\n", &first); /*gnu kludge*/
14371       }
14372       SendTimeRemaining(&first, TRUE);
14373     }
14374     if (first.useColors) {
14375       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14376     }
14377     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14378     SetMachineThinkingEnables();
14379     first.maybeThinking = TRUE;
14380     StartClocks();
14381     firstMove = FALSE;
14382
14383     if (appData.autoFlipView && !flipView) {
14384       flipView = !flipView;
14385       DrawPosition(FALSE, NULL);
14386       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14387     }
14388
14389     if(bookHit) { // [HGM] book: simulate book reply
14390         static char bookMove[MSG_SIZ]; // a bit generous?
14391
14392         programStats.nodes = programStats.depth = programStats.time =
14393         programStats.score = programStats.got_only_move = 0;
14394         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14395
14396         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14397         strcat(bookMove, bookHit);
14398         HandleMachineMove(bookMove, &first);
14399     }
14400 }
14401
14402 void
14403 MachineBlackEvent ()
14404 {
14405   char buf[MSG_SIZ];
14406   char *bookHit = NULL;
14407
14408     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14409         return;
14410
14411
14412     if (gameMode == PlayFromGameFile ||
14413         gameMode == TwoMachinesPlay  ||
14414         gameMode == Training         ||
14415         gameMode == AnalyzeMode      ||
14416         gameMode == EndOfGame)
14417         EditGameEvent();
14418
14419     if (gameMode == EditPosition)
14420         EditPositionDone(TRUE);
14421
14422     if (WhiteOnMove(currentMove)) {
14423         DisplayError(_("It is not Black's turn"), 0);
14424         return;
14425     }
14426
14427     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14428       ExitAnalyzeMode();
14429
14430     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14431         gameMode == AnalyzeFile)
14432         TruncateGame();
14433
14434     ResurrectChessProgram();    /* in case it isn't running */
14435     gameMode = MachinePlaysBlack;
14436     pausing = FALSE;
14437     ModeHighlight();
14438     SetGameInfo();
14439     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14440     DisplayTitle(buf);
14441     if (first.sendName) {
14442       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14443       SendToProgram(buf, &first);
14444     }
14445     if (first.sendTime) {
14446       if (first.useColors) {
14447         SendToProgram("white\n", &first); /*gnu kludge*/
14448       }
14449       SendTimeRemaining(&first, FALSE);
14450     }
14451     if (first.useColors) {
14452       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14453     }
14454     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14455     SetMachineThinkingEnables();
14456     first.maybeThinking = TRUE;
14457     StartClocks();
14458
14459     if (appData.autoFlipView && flipView) {
14460       flipView = !flipView;
14461       DrawPosition(FALSE, NULL);
14462       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14463     }
14464     if(bookHit) { // [HGM] book: simulate book reply
14465         static char bookMove[MSG_SIZ]; // a bit generous?
14466
14467         programStats.nodes = programStats.depth = programStats.time =
14468         programStats.score = programStats.got_only_move = 0;
14469         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14470
14471         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14472         strcat(bookMove, bookHit);
14473         HandleMachineMove(bookMove, &first);
14474     }
14475 }
14476
14477
14478 void
14479 DisplayTwoMachinesTitle ()
14480 {
14481     char buf[MSG_SIZ];
14482     if (appData.matchGames > 0) {
14483         if(appData.tourneyFile[0]) {
14484           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14485                    gameInfo.white, _("vs."), gameInfo.black,
14486                    nextGame+1, appData.matchGames+1,
14487                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14488         } else
14489         if (first.twoMachinesColor[0] == 'w') {
14490           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14491                    gameInfo.white, _("vs."),  gameInfo.black,
14492                    first.matchWins, second.matchWins,
14493                    matchGame - 1 - (first.matchWins + second.matchWins));
14494         } else {
14495           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14496                    gameInfo.white, _("vs."), gameInfo.black,
14497                    second.matchWins, first.matchWins,
14498                    matchGame - 1 - (first.matchWins + second.matchWins));
14499         }
14500     } else {
14501       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14502     }
14503     DisplayTitle(buf);
14504 }
14505
14506 void
14507 SettingsMenuIfReady ()
14508 {
14509   if (second.lastPing != second.lastPong) {
14510     DisplayMessage("", _("Waiting for second chess program"));
14511     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14512     return;
14513   }
14514   ThawUI();
14515   DisplayMessage("", "");
14516   SettingsPopUp(&second);
14517 }
14518
14519 int
14520 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14521 {
14522     char buf[MSG_SIZ];
14523     if (cps->pr == NoProc) {
14524         StartChessProgram(cps);
14525         if (cps->protocolVersion == 1) {
14526           retry();
14527           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14528         } else {
14529           /* kludge: allow timeout for initial "feature" command */
14530           if(retry != TwoMachinesEventIfReady) FreezeUI();
14531           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14532           DisplayMessage("", buf);
14533           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14534         }
14535         return 1;
14536     }
14537     return 0;
14538 }
14539
14540 void
14541 TwoMachinesEvent P((void))
14542 {
14543     int i;
14544     char buf[MSG_SIZ];
14545     ChessProgramState *onmove;
14546     char *bookHit = NULL;
14547     static int stalling = 0;
14548     TimeMark now;
14549     long wait;
14550
14551     if (appData.noChessProgram) return;
14552
14553     switch (gameMode) {
14554       case TwoMachinesPlay:
14555         return;
14556       case MachinePlaysWhite:
14557       case MachinePlaysBlack:
14558         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14559             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14560             return;
14561         }
14562         /* fall through */
14563       case BeginningOfGame:
14564       case PlayFromGameFile:
14565       case EndOfGame:
14566         EditGameEvent();
14567         if (gameMode != EditGame) return;
14568         break;
14569       case EditPosition:
14570         EditPositionDone(TRUE);
14571         break;
14572       case AnalyzeMode:
14573       case AnalyzeFile:
14574         ExitAnalyzeMode();
14575         break;
14576       case EditGame:
14577       default:
14578         break;
14579     }
14580
14581 //    forwardMostMove = currentMove;
14582     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14583     startingEngine = TRUE;
14584
14585     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14586
14587     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14588     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14589       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14590       return;
14591     }
14592     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14593
14594     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14595                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14596         startingEngine = FALSE;
14597         DisplayError("second engine does not play this", 0);
14598         return;
14599     }
14600
14601     if(!stalling) {
14602       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14603       SendToProgram("force\n", &second);
14604       stalling = 1;
14605       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14606       return;
14607     }
14608     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14609     if(appData.matchPause>10000 || appData.matchPause<10)
14610                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14611     wait = SubtractTimeMarks(&now, &pauseStart);
14612     if(wait < appData.matchPause) {
14613         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14614         return;
14615     }
14616     // we are now committed to starting the game
14617     stalling = 0;
14618     DisplayMessage("", "");
14619     if (startedFromSetupPosition) {
14620         SendBoard(&second, backwardMostMove);
14621     if (appData.debugMode) {
14622         fprintf(debugFP, "Two Machines\n");
14623     }
14624     }
14625     for (i = backwardMostMove; i < forwardMostMove; i++) {
14626         SendMoveToProgram(i, &second);
14627     }
14628
14629     gameMode = TwoMachinesPlay;
14630     pausing = startingEngine = FALSE;
14631     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14632     SetGameInfo();
14633     DisplayTwoMachinesTitle();
14634     firstMove = TRUE;
14635     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14636         onmove = &first;
14637     } else {
14638         onmove = &second;
14639     }
14640     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14641     SendToProgram(first.computerString, &first);
14642     if (first.sendName) {
14643       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14644       SendToProgram(buf, &first);
14645     }
14646     SendToProgram(second.computerString, &second);
14647     if (second.sendName) {
14648       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14649       SendToProgram(buf, &second);
14650     }
14651
14652     ResetClocks();
14653     if (!first.sendTime || !second.sendTime) {
14654         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14655         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14656     }
14657     if (onmove->sendTime) {
14658       if (onmove->useColors) {
14659         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14660       }
14661       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14662     }
14663     if (onmove->useColors) {
14664       SendToProgram(onmove->twoMachinesColor, onmove);
14665     }
14666     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14667 //    SendToProgram("go\n", onmove);
14668     onmove->maybeThinking = TRUE;
14669     SetMachineThinkingEnables();
14670
14671     StartClocks();
14672
14673     if(bookHit) { // [HGM] book: simulate book reply
14674         static char bookMove[MSG_SIZ]; // a bit generous?
14675
14676         programStats.nodes = programStats.depth = programStats.time =
14677         programStats.score = programStats.got_only_move = 0;
14678         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14679
14680         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14681         strcat(bookMove, bookHit);
14682         savedMessage = bookMove; // args for deferred call
14683         savedState = onmove;
14684         ScheduleDelayedEvent(DeferredBookMove, 1);
14685     }
14686 }
14687
14688 void
14689 TrainingEvent ()
14690 {
14691     if (gameMode == Training) {
14692       SetTrainingModeOff();
14693       gameMode = PlayFromGameFile;
14694       DisplayMessage("", _("Training mode off"));
14695     } else {
14696       gameMode = Training;
14697       animateTraining = appData.animate;
14698
14699       /* make sure we are not already at the end of the game */
14700       if (currentMove < forwardMostMove) {
14701         SetTrainingModeOn();
14702         DisplayMessage("", _("Training mode on"));
14703       } else {
14704         gameMode = PlayFromGameFile;
14705         DisplayError(_("Already at end of game"), 0);
14706       }
14707     }
14708     ModeHighlight();
14709 }
14710
14711 void
14712 IcsClientEvent ()
14713 {
14714     if (!appData.icsActive) return;
14715     switch (gameMode) {
14716       case IcsPlayingWhite:
14717       case IcsPlayingBlack:
14718       case IcsObserving:
14719       case IcsIdle:
14720       case BeginningOfGame:
14721       case IcsExamining:
14722         return;
14723
14724       case EditGame:
14725         break;
14726
14727       case EditPosition:
14728         EditPositionDone(TRUE);
14729         break;
14730
14731       case AnalyzeMode:
14732       case AnalyzeFile:
14733         ExitAnalyzeMode();
14734         break;
14735
14736       default:
14737         EditGameEvent();
14738         break;
14739     }
14740
14741     gameMode = IcsIdle;
14742     ModeHighlight();
14743     return;
14744 }
14745
14746 void
14747 EditGameEvent ()
14748 {
14749     int i;
14750
14751     switch (gameMode) {
14752       case Training:
14753         SetTrainingModeOff();
14754         break;
14755       case MachinePlaysWhite:
14756       case MachinePlaysBlack:
14757       case BeginningOfGame:
14758         SendToProgram("force\n", &first);
14759         SetUserThinkingEnables();
14760         break;
14761       case PlayFromGameFile:
14762         (void) StopLoadGameTimer();
14763         if (gameFileFP != NULL) {
14764             gameFileFP = NULL;
14765         }
14766         break;
14767       case EditPosition:
14768         EditPositionDone(TRUE);
14769         break;
14770       case AnalyzeMode:
14771       case AnalyzeFile:
14772         ExitAnalyzeMode();
14773         SendToProgram("force\n", &first);
14774         break;
14775       case TwoMachinesPlay:
14776         GameEnds(EndOfFile, NULL, GE_PLAYER);
14777         ResurrectChessProgram();
14778         SetUserThinkingEnables();
14779         break;
14780       case EndOfGame:
14781         ResurrectChessProgram();
14782         break;
14783       case IcsPlayingBlack:
14784       case IcsPlayingWhite:
14785         DisplayError(_("Warning: You are still playing a game"), 0);
14786         break;
14787       case IcsObserving:
14788         DisplayError(_("Warning: You are still observing a game"), 0);
14789         break;
14790       case IcsExamining:
14791         DisplayError(_("Warning: You are still examining a game"), 0);
14792         break;
14793       case IcsIdle:
14794         break;
14795       case EditGame:
14796       default:
14797         return;
14798     }
14799
14800     pausing = FALSE;
14801     StopClocks();
14802     first.offeredDraw = second.offeredDraw = 0;
14803
14804     if (gameMode == PlayFromGameFile) {
14805         whiteTimeRemaining = timeRemaining[0][currentMove];
14806         blackTimeRemaining = timeRemaining[1][currentMove];
14807         DisplayTitle("");
14808     }
14809
14810     if (gameMode == MachinePlaysWhite ||
14811         gameMode == MachinePlaysBlack ||
14812         gameMode == TwoMachinesPlay ||
14813         gameMode == EndOfGame) {
14814         i = forwardMostMove;
14815         while (i > currentMove) {
14816             SendToProgram("undo\n", &first);
14817             i--;
14818         }
14819         if(!adjustedClock) {
14820         whiteTimeRemaining = timeRemaining[0][currentMove];
14821         blackTimeRemaining = timeRemaining[1][currentMove];
14822         DisplayBothClocks();
14823         }
14824         if (whiteFlag || blackFlag) {
14825             whiteFlag = blackFlag = 0;
14826         }
14827         DisplayTitle("");
14828     }
14829
14830     gameMode = EditGame;
14831     ModeHighlight();
14832     SetGameInfo();
14833 }
14834
14835
14836 void
14837 EditPositionEvent ()
14838 {
14839     if (gameMode == EditPosition) {
14840         EditGameEvent();
14841         return;
14842     }
14843
14844     EditGameEvent();
14845     if (gameMode != EditGame) return;
14846
14847     gameMode = EditPosition;
14848     ModeHighlight();
14849     SetGameInfo();
14850     if (currentMove > 0)
14851       CopyBoard(boards[0], boards[currentMove]);
14852
14853     blackPlaysFirst = !WhiteOnMove(currentMove);
14854     ResetClocks();
14855     currentMove = forwardMostMove = backwardMostMove = 0;
14856     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14857     DisplayMove(-1);
14858     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14859 }
14860
14861 void
14862 ExitAnalyzeMode ()
14863 {
14864     /* [DM] icsEngineAnalyze - possible call from other functions */
14865     if (appData.icsEngineAnalyze) {
14866         appData.icsEngineAnalyze = FALSE;
14867
14868         DisplayMessage("",_("Close ICS engine analyze..."));
14869     }
14870     if (first.analysisSupport && first.analyzing) {
14871       SendToBoth("exit\n");
14872       first.analyzing = second.analyzing = FALSE;
14873     }
14874     thinkOutput[0] = NULLCHAR;
14875 }
14876
14877 void
14878 EditPositionDone (Boolean fakeRights)
14879 {
14880     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14881
14882     startedFromSetupPosition = TRUE;
14883     InitChessProgram(&first, FALSE);
14884     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14885       boards[0][EP_STATUS] = EP_NONE;
14886       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14887       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14888         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14889         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14890       } else boards[0][CASTLING][2] = NoRights;
14891       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14892         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14893         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14894       } else boards[0][CASTLING][5] = NoRights;
14895       if(gameInfo.variant == VariantSChess) {
14896         int i;
14897         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14898           boards[0][VIRGIN][i] = 0;
14899           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14900           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14901         }
14902       }
14903     }
14904     SendToProgram("force\n", &first);
14905     if (blackPlaysFirst) {
14906         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14907         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14908         currentMove = forwardMostMove = backwardMostMove = 1;
14909         CopyBoard(boards[1], boards[0]);
14910     } else {
14911         currentMove = forwardMostMove = backwardMostMove = 0;
14912     }
14913     SendBoard(&first, forwardMostMove);
14914     if (appData.debugMode) {
14915         fprintf(debugFP, "EditPosDone\n");
14916     }
14917     DisplayTitle("");
14918     DisplayMessage("", "");
14919     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14920     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14921     gameMode = EditGame;
14922     ModeHighlight();
14923     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14924     ClearHighlights(); /* [AS] */
14925 }
14926
14927 /* Pause for `ms' milliseconds */
14928 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14929 void
14930 TimeDelay (long ms)
14931 {
14932     TimeMark m1, m2;
14933
14934     GetTimeMark(&m1);
14935     do {
14936         GetTimeMark(&m2);
14937     } while (SubtractTimeMarks(&m2, &m1) < ms);
14938 }
14939
14940 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14941 void
14942 SendMultiLineToICS (char *buf)
14943 {
14944     char temp[MSG_SIZ+1], *p;
14945     int len;
14946
14947     len = strlen(buf);
14948     if (len > MSG_SIZ)
14949       len = MSG_SIZ;
14950
14951     strncpy(temp, buf, len);
14952     temp[len] = 0;
14953
14954     p = temp;
14955     while (*p) {
14956         if (*p == '\n' || *p == '\r')
14957           *p = ' ';
14958         ++p;
14959     }
14960
14961     strcat(temp, "\n");
14962     SendToICS(temp);
14963     SendToPlayer(temp, strlen(temp));
14964 }
14965
14966 void
14967 SetWhiteToPlayEvent ()
14968 {
14969     if (gameMode == EditPosition) {
14970         blackPlaysFirst = FALSE;
14971         DisplayBothClocks();    /* works because currentMove is 0 */
14972     } else if (gameMode == IcsExamining) {
14973         SendToICS(ics_prefix);
14974         SendToICS("tomove white\n");
14975     }
14976 }
14977
14978 void
14979 SetBlackToPlayEvent ()
14980 {
14981     if (gameMode == EditPosition) {
14982         blackPlaysFirst = TRUE;
14983         currentMove = 1;        /* kludge */
14984         DisplayBothClocks();
14985         currentMove = 0;
14986     } else if (gameMode == IcsExamining) {
14987         SendToICS(ics_prefix);
14988         SendToICS("tomove black\n");
14989     }
14990 }
14991
14992 void
14993 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14994 {
14995     char buf[MSG_SIZ];
14996     ChessSquare piece = boards[0][y][x];
14997     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14998     static int lastVariant;
14999
15000     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15001
15002     switch (selection) {
15003       case ClearBoard:
15004         CopyBoard(currentBoard, boards[0]);
15005         CopyBoard(menuBoard, initialPosition);
15006         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15007             SendToICS(ics_prefix);
15008             SendToICS("bsetup clear\n");
15009         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15010             SendToICS(ics_prefix);
15011             SendToICS("clearboard\n");
15012         } else {
15013             int nonEmpty = 0;
15014             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15015                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15016                 for (y = 0; y < BOARD_HEIGHT; y++) {
15017                     if (gameMode == IcsExamining) {
15018                         if (boards[currentMove][y][x] != EmptySquare) {
15019                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15020                                     AAA + x, ONE + y);
15021                             SendToICS(buf);
15022                         }
15023                     } else {
15024                         if(boards[0][y][x] != p) nonEmpty++;
15025                         boards[0][y][x] = p;
15026                     }
15027                 }
15028             }
15029             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15030                 int r;
15031                 for(r = 0; r < BOARD_HEIGHT; r++) {
15032                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15033                     ChessSquare p = menuBoard[r][x];
15034                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15035                   }
15036                 }
15037                 DisplayMessage("Clicking clock again restores position", "");
15038                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15039                 if(!nonEmpty) { // asked to clear an empty board
15040                     CopyBoard(boards[0], menuBoard);
15041                 } else
15042                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15043                     CopyBoard(boards[0], initialPosition);
15044                 } else
15045                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15046                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15047                     CopyBoard(boards[0], erasedBoard);
15048                 } else
15049                     CopyBoard(erasedBoard, currentBoard);
15050
15051             }
15052         }
15053         if (gameMode == EditPosition) {
15054             DrawPosition(FALSE, boards[0]);
15055         }
15056         break;
15057
15058       case WhitePlay:
15059         SetWhiteToPlayEvent();
15060         break;
15061
15062       case BlackPlay:
15063         SetBlackToPlayEvent();
15064         break;
15065
15066       case EmptySquare:
15067         if (gameMode == IcsExamining) {
15068             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15069             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15070             SendToICS(buf);
15071         } else {
15072             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15073                 if(x == BOARD_LEFT-2) {
15074                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15075                     boards[0][y][1] = 0;
15076                 } else
15077                 if(x == BOARD_RGHT+1) {
15078                     if(y >= gameInfo.holdingsSize) break;
15079                     boards[0][y][BOARD_WIDTH-2] = 0;
15080                 } else break;
15081             }
15082             boards[0][y][x] = EmptySquare;
15083             DrawPosition(FALSE, boards[0]);
15084         }
15085         break;
15086
15087       case PromotePiece:
15088         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15089            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15090             selection = (ChessSquare) (PROMOTED piece);
15091         } else if(piece == EmptySquare) selection = WhiteSilver;
15092         else selection = (ChessSquare)((int)piece - 1);
15093         goto defaultlabel;
15094
15095       case DemotePiece:
15096         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15097            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15098             selection = (ChessSquare) (DEMOTED piece);
15099         } else if(piece == EmptySquare) selection = BlackSilver;
15100         else selection = (ChessSquare)((int)piece + 1);
15101         goto defaultlabel;
15102
15103       case WhiteQueen:
15104       case BlackQueen:
15105         if(gameInfo.variant == VariantShatranj ||
15106            gameInfo.variant == VariantXiangqi  ||
15107            gameInfo.variant == VariantCourier  ||
15108            gameInfo.variant == VariantASEAN    ||
15109            gameInfo.variant == VariantMakruk     )
15110             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15111         goto defaultlabel;
15112
15113       case WhiteKing:
15114       case BlackKing:
15115         if(gameInfo.variant == VariantXiangqi)
15116             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15117         if(gameInfo.variant == VariantKnightmate)
15118             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15119       default:
15120         defaultlabel:
15121         if (gameMode == IcsExamining) {
15122             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15123             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15124                      PieceToChar(selection), AAA + x, ONE + y);
15125             SendToICS(buf);
15126         } else {
15127             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15128                 int n;
15129                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15130                     n = PieceToNumber(selection - BlackPawn);
15131                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15132                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15133                     boards[0][BOARD_HEIGHT-1-n][1]++;
15134                 } else
15135                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15136                     n = PieceToNumber(selection);
15137                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15138                     boards[0][n][BOARD_WIDTH-1] = selection;
15139                     boards[0][n][BOARD_WIDTH-2]++;
15140                 }
15141             } else
15142             boards[0][y][x] = selection;
15143             DrawPosition(TRUE, boards[0]);
15144             ClearHighlights();
15145             fromX = fromY = -1;
15146         }
15147         break;
15148     }
15149 }
15150
15151
15152 void
15153 DropMenuEvent (ChessSquare selection, int x, int y)
15154 {
15155     ChessMove moveType;
15156
15157     switch (gameMode) {
15158       case IcsPlayingWhite:
15159       case MachinePlaysBlack:
15160         if (!WhiteOnMove(currentMove)) {
15161             DisplayMoveError(_("It is Black's turn"));
15162             return;
15163         }
15164         moveType = WhiteDrop;
15165         break;
15166       case IcsPlayingBlack:
15167       case MachinePlaysWhite:
15168         if (WhiteOnMove(currentMove)) {
15169             DisplayMoveError(_("It is White's turn"));
15170             return;
15171         }
15172         moveType = BlackDrop;
15173         break;
15174       case EditGame:
15175         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15176         break;
15177       default:
15178         return;
15179     }
15180
15181     if (moveType == BlackDrop && selection < BlackPawn) {
15182       selection = (ChessSquare) ((int) selection
15183                                  + (int) BlackPawn - (int) WhitePawn);
15184     }
15185     if (boards[currentMove][y][x] != EmptySquare) {
15186         DisplayMoveError(_("That square is occupied"));
15187         return;
15188     }
15189
15190     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15191 }
15192
15193 void
15194 AcceptEvent ()
15195 {
15196     /* Accept a pending offer of any kind from opponent */
15197
15198     if (appData.icsActive) {
15199         SendToICS(ics_prefix);
15200         SendToICS("accept\n");
15201     } else if (cmailMsgLoaded) {
15202         if (currentMove == cmailOldMove &&
15203             commentList[cmailOldMove] != NULL &&
15204             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15205                    "Black offers a draw" : "White offers a draw")) {
15206             TruncateGame();
15207             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15208             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15209         } else {
15210             DisplayError(_("There is no pending offer on this move"), 0);
15211             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15212         }
15213     } else {
15214         /* Not used for offers from chess program */
15215     }
15216 }
15217
15218 void
15219 DeclineEvent ()
15220 {
15221     /* Decline a pending offer of any kind from opponent */
15222
15223     if (appData.icsActive) {
15224         SendToICS(ics_prefix);
15225         SendToICS("decline\n");
15226     } else if (cmailMsgLoaded) {
15227         if (currentMove == cmailOldMove &&
15228             commentList[cmailOldMove] != NULL &&
15229             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15230                    "Black offers a draw" : "White offers a draw")) {
15231 #ifdef NOTDEF
15232             AppendComment(cmailOldMove, "Draw declined", TRUE);
15233             DisplayComment(cmailOldMove - 1, "Draw declined");
15234 #endif /*NOTDEF*/
15235         } else {
15236             DisplayError(_("There is no pending offer on this move"), 0);
15237         }
15238     } else {
15239         /* Not used for offers from chess program */
15240     }
15241 }
15242
15243 void
15244 RematchEvent ()
15245 {
15246     /* Issue ICS rematch command */
15247     if (appData.icsActive) {
15248         SendToICS(ics_prefix);
15249         SendToICS("rematch\n");
15250     }
15251 }
15252
15253 void
15254 CallFlagEvent ()
15255 {
15256     /* Call your opponent's flag (claim a win on time) */
15257     if (appData.icsActive) {
15258         SendToICS(ics_prefix);
15259         SendToICS("flag\n");
15260     } else {
15261         switch (gameMode) {
15262           default:
15263             return;
15264           case MachinePlaysWhite:
15265             if (whiteFlag) {
15266                 if (blackFlag)
15267                   GameEnds(GameIsDrawn, "Both players ran out of time",
15268                            GE_PLAYER);
15269                 else
15270                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15271             } else {
15272                 DisplayError(_("Your opponent is not out of time"), 0);
15273             }
15274             break;
15275           case MachinePlaysBlack:
15276             if (blackFlag) {
15277                 if (whiteFlag)
15278                   GameEnds(GameIsDrawn, "Both players ran out of time",
15279                            GE_PLAYER);
15280                 else
15281                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15282             } else {
15283                 DisplayError(_("Your opponent is not out of time"), 0);
15284             }
15285             break;
15286         }
15287     }
15288 }
15289
15290 void
15291 ClockClick (int which)
15292 {       // [HGM] code moved to back-end from winboard.c
15293         if(which) { // black clock
15294           if (gameMode == EditPosition || gameMode == IcsExamining) {
15295             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15296             SetBlackToPlayEvent();
15297           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15298                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15299           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15300           } else if (shiftKey) {
15301             AdjustClock(which, -1);
15302           } else if (gameMode == IcsPlayingWhite ||
15303                      gameMode == MachinePlaysBlack) {
15304             CallFlagEvent();
15305           }
15306         } else { // white clock
15307           if (gameMode == EditPosition || gameMode == IcsExamining) {
15308             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15309             SetWhiteToPlayEvent();
15310           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15311                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15312           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15313           } else if (shiftKey) {
15314             AdjustClock(which, -1);
15315           } else if (gameMode == IcsPlayingBlack ||
15316                    gameMode == MachinePlaysWhite) {
15317             CallFlagEvent();
15318           }
15319         }
15320 }
15321
15322 void
15323 DrawEvent ()
15324 {
15325     /* Offer draw or accept pending draw offer from opponent */
15326
15327     if (appData.icsActive) {
15328         /* Note: tournament rules require draw offers to be
15329            made after you make your move but before you punch
15330            your clock.  Currently ICS doesn't let you do that;
15331            instead, you immediately punch your clock after making
15332            a move, but you can offer a draw at any time. */
15333
15334         SendToICS(ics_prefix);
15335         SendToICS("draw\n");
15336         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15337     } else if (cmailMsgLoaded) {
15338         if (currentMove == cmailOldMove &&
15339             commentList[cmailOldMove] != NULL &&
15340             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15341                    "Black offers a draw" : "White offers a draw")) {
15342             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15343             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15344         } else if (currentMove == cmailOldMove + 1) {
15345             char *offer = WhiteOnMove(cmailOldMove) ?
15346               "White offers a draw" : "Black offers a draw";
15347             AppendComment(currentMove, offer, TRUE);
15348             DisplayComment(currentMove - 1, offer);
15349             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15350         } else {
15351             DisplayError(_("You must make your move before offering a draw"), 0);
15352             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15353         }
15354     } else if (first.offeredDraw) {
15355         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15356     } else {
15357         if (first.sendDrawOffers) {
15358             SendToProgram("draw\n", &first);
15359             userOfferedDraw = TRUE;
15360         }
15361     }
15362 }
15363
15364 void
15365 AdjournEvent ()
15366 {
15367     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15368
15369     if (appData.icsActive) {
15370         SendToICS(ics_prefix);
15371         SendToICS("adjourn\n");
15372     } else {
15373         /* Currently GNU Chess doesn't offer or accept Adjourns */
15374     }
15375 }
15376
15377
15378 void
15379 AbortEvent ()
15380 {
15381     /* Offer Abort or accept pending Abort offer from opponent */
15382
15383     if (appData.icsActive) {
15384         SendToICS(ics_prefix);
15385         SendToICS("abort\n");
15386     } else {
15387         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15388     }
15389 }
15390
15391 void
15392 ResignEvent ()
15393 {
15394     /* Resign.  You can do this even if it's not your turn. */
15395
15396     if (appData.icsActive) {
15397         SendToICS(ics_prefix);
15398         SendToICS("resign\n");
15399     } else {
15400         switch (gameMode) {
15401           case MachinePlaysWhite:
15402             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15403             break;
15404           case MachinePlaysBlack:
15405             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15406             break;
15407           case EditGame:
15408             if (cmailMsgLoaded) {
15409                 TruncateGame();
15410                 if (WhiteOnMove(cmailOldMove)) {
15411                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15412                 } else {
15413                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15414                 }
15415                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15416             }
15417             break;
15418           default:
15419             break;
15420         }
15421     }
15422 }
15423
15424
15425 void
15426 StopObservingEvent ()
15427 {
15428     /* Stop observing current games */
15429     SendToICS(ics_prefix);
15430     SendToICS("unobserve\n");
15431 }
15432
15433 void
15434 StopExaminingEvent ()
15435 {
15436     /* Stop observing current game */
15437     SendToICS(ics_prefix);
15438     SendToICS("unexamine\n");
15439 }
15440
15441 void
15442 ForwardInner (int target)
15443 {
15444     int limit; int oldSeekGraphUp = seekGraphUp;
15445
15446     if (appData.debugMode)
15447         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15448                 target, currentMove, forwardMostMove);
15449
15450     if (gameMode == EditPosition)
15451       return;
15452
15453     seekGraphUp = FALSE;
15454     MarkTargetSquares(1);
15455
15456     if (gameMode == PlayFromGameFile && !pausing)
15457       PauseEvent();
15458
15459     if (gameMode == IcsExamining && pausing)
15460       limit = pauseExamForwardMostMove;
15461     else
15462       limit = forwardMostMove;
15463
15464     if (target > limit) target = limit;
15465
15466     if (target > 0 && moveList[target - 1][0]) {
15467         int fromX, fromY, toX, toY;
15468         toX = moveList[target - 1][2] - AAA;
15469         toY = moveList[target - 1][3] - ONE;
15470         if (moveList[target - 1][1] == '@') {
15471             if (appData.highlightLastMove) {
15472                 SetHighlights(-1, -1, toX, toY);
15473             }
15474         } else {
15475             fromX = moveList[target - 1][0] - AAA;
15476             fromY = moveList[target - 1][1] - ONE;
15477             if (target == currentMove + 1) {
15478                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15479             }
15480             if (appData.highlightLastMove) {
15481                 SetHighlights(fromX, fromY, toX, toY);
15482             }
15483         }
15484     }
15485     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15486         gameMode == Training || gameMode == PlayFromGameFile ||
15487         gameMode == AnalyzeFile) {
15488         while (currentMove < target) {
15489             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15490             SendMoveToProgram(currentMove++, &first);
15491         }
15492     } else {
15493         currentMove = target;
15494     }
15495
15496     if (gameMode == EditGame || gameMode == EndOfGame) {
15497         whiteTimeRemaining = timeRemaining[0][currentMove];
15498         blackTimeRemaining = timeRemaining[1][currentMove];
15499     }
15500     DisplayBothClocks();
15501     DisplayMove(currentMove - 1);
15502     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15503     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15504     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15505         DisplayComment(currentMove - 1, commentList[currentMove]);
15506     }
15507     ClearMap(); // [HGM] exclude: invalidate map
15508 }
15509
15510
15511 void
15512 ForwardEvent ()
15513 {
15514     if (gameMode == IcsExamining && !pausing) {
15515         SendToICS(ics_prefix);
15516         SendToICS("forward\n");
15517     } else {
15518         ForwardInner(currentMove + 1);
15519     }
15520 }
15521
15522 void
15523 ToEndEvent ()
15524 {
15525     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15526         /* to optimze, we temporarily turn off analysis mode while we feed
15527          * the remaining moves to the engine. Otherwise we get analysis output
15528          * after each move.
15529          */
15530         if (first.analysisSupport) {
15531           SendToProgram("exit\nforce\n", &first);
15532           first.analyzing = FALSE;
15533         }
15534     }
15535
15536     if (gameMode == IcsExamining && !pausing) {
15537         SendToICS(ics_prefix);
15538         SendToICS("forward 999999\n");
15539     } else {
15540         ForwardInner(forwardMostMove);
15541     }
15542
15543     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15544         /* we have fed all the moves, so reactivate analysis mode */
15545         SendToProgram("analyze\n", &first);
15546         first.analyzing = TRUE;
15547         /*first.maybeThinking = TRUE;*/
15548         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15549     }
15550 }
15551
15552 void
15553 BackwardInner (int target)
15554 {
15555     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15556
15557     if (appData.debugMode)
15558         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15559                 target, currentMove, forwardMostMove);
15560
15561     if (gameMode == EditPosition) return;
15562     seekGraphUp = FALSE;
15563     MarkTargetSquares(1);
15564     if (currentMove <= backwardMostMove) {
15565         ClearHighlights();
15566         DrawPosition(full_redraw, boards[currentMove]);
15567         return;
15568     }
15569     if (gameMode == PlayFromGameFile && !pausing)
15570       PauseEvent();
15571
15572     if (moveList[target][0]) {
15573         int fromX, fromY, toX, toY;
15574         toX = moveList[target][2] - AAA;
15575         toY = moveList[target][3] - ONE;
15576         if (moveList[target][1] == '@') {
15577             if (appData.highlightLastMove) {
15578                 SetHighlights(-1, -1, toX, toY);
15579             }
15580         } else {
15581             fromX = moveList[target][0] - AAA;
15582             fromY = moveList[target][1] - ONE;
15583             if (target == currentMove - 1) {
15584                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15585             }
15586             if (appData.highlightLastMove) {
15587                 SetHighlights(fromX, fromY, toX, toY);
15588             }
15589         }
15590     }
15591     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15592         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15593         while (currentMove > target) {
15594             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15595                 // null move cannot be undone. Reload program with move history before it.
15596                 int i;
15597                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15598                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15599                 }
15600                 SendBoard(&first, i);
15601               if(second.analyzing) SendBoard(&second, i);
15602                 for(currentMove=i; currentMove<target; currentMove++) {
15603                     SendMoveToProgram(currentMove, &first);
15604                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15605                 }
15606                 break;
15607             }
15608             SendToBoth("undo\n");
15609             currentMove--;
15610         }
15611     } else {
15612         currentMove = target;
15613     }
15614
15615     if (gameMode == EditGame || gameMode == EndOfGame) {
15616         whiteTimeRemaining = timeRemaining[0][currentMove];
15617         blackTimeRemaining = timeRemaining[1][currentMove];
15618     }
15619     DisplayBothClocks();
15620     DisplayMove(currentMove - 1);
15621     DrawPosition(full_redraw, boards[currentMove]);
15622     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15623     // [HGM] PV info: routine tests if comment empty
15624     DisplayComment(currentMove - 1, commentList[currentMove]);
15625     ClearMap(); // [HGM] exclude: invalidate map
15626 }
15627
15628 void
15629 BackwardEvent ()
15630 {
15631     if (gameMode == IcsExamining && !pausing) {
15632         SendToICS(ics_prefix);
15633         SendToICS("backward\n");
15634     } else {
15635         BackwardInner(currentMove - 1);
15636     }
15637 }
15638
15639 void
15640 ToStartEvent ()
15641 {
15642     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15643         /* to optimize, we temporarily turn off analysis mode while we undo
15644          * all the moves. Otherwise we get analysis output after each undo.
15645          */
15646         if (first.analysisSupport) {
15647           SendToProgram("exit\nforce\n", &first);
15648           first.analyzing = FALSE;
15649         }
15650     }
15651
15652     if (gameMode == IcsExamining && !pausing) {
15653         SendToICS(ics_prefix);
15654         SendToICS("backward 999999\n");
15655     } else {
15656         BackwardInner(backwardMostMove);
15657     }
15658
15659     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15660         /* we have fed all the moves, so reactivate analysis mode */
15661         SendToProgram("analyze\n", &first);
15662         first.analyzing = TRUE;
15663         /*first.maybeThinking = TRUE;*/
15664         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15665     }
15666 }
15667
15668 void
15669 ToNrEvent (int to)
15670 {
15671   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15672   if (to >= forwardMostMove) to = forwardMostMove;
15673   if (to <= backwardMostMove) to = backwardMostMove;
15674   if (to < currentMove) {
15675     BackwardInner(to);
15676   } else {
15677     ForwardInner(to);
15678   }
15679 }
15680
15681 void
15682 RevertEvent (Boolean annotate)
15683 {
15684     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15685         return;
15686     }
15687     if (gameMode != IcsExamining) {
15688         DisplayError(_("You are not examining a game"), 0);
15689         return;
15690     }
15691     if (pausing) {
15692         DisplayError(_("You can't revert while pausing"), 0);
15693         return;
15694     }
15695     SendToICS(ics_prefix);
15696     SendToICS("revert\n");
15697 }
15698
15699 void
15700 RetractMoveEvent ()
15701 {
15702     switch (gameMode) {
15703       case MachinePlaysWhite:
15704       case MachinePlaysBlack:
15705         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15706             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15707             return;
15708         }
15709         if (forwardMostMove < 2) return;
15710         currentMove = forwardMostMove = forwardMostMove - 2;
15711         whiteTimeRemaining = timeRemaining[0][currentMove];
15712         blackTimeRemaining = timeRemaining[1][currentMove];
15713         DisplayBothClocks();
15714         DisplayMove(currentMove - 1);
15715         ClearHighlights();/*!! could figure this out*/
15716         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15717         SendToProgram("remove\n", &first);
15718         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15719         break;
15720
15721       case BeginningOfGame:
15722       default:
15723         break;
15724
15725       case IcsPlayingWhite:
15726       case IcsPlayingBlack:
15727         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15728             SendToICS(ics_prefix);
15729             SendToICS("takeback 2\n");
15730         } else {
15731             SendToICS(ics_prefix);
15732             SendToICS("takeback 1\n");
15733         }
15734         break;
15735     }
15736 }
15737
15738 void
15739 MoveNowEvent ()
15740 {
15741     ChessProgramState *cps;
15742
15743     switch (gameMode) {
15744       case MachinePlaysWhite:
15745         if (!WhiteOnMove(forwardMostMove)) {
15746             DisplayError(_("It is your turn"), 0);
15747             return;
15748         }
15749         cps = &first;
15750         break;
15751       case MachinePlaysBlack:
15752         if (WhiteOnMove(forwardMostMove)) {
15753             DisplayError(_("It is your turn"), 0);
15754             return;
15755         }
15756         cps = &first;
15757         break;
15758       case TwoMachinesPlay:
15759         if (WhiteOnMove(forwardMostMove) ==
15760             (first.twoMachinesColor[0] == 'w')) {
15761             cps = &first;
15762         } else {
15763             cps = &second;
15764         }
15765         break;
15766       case BeginningOfGame:
15767       default:
15768         return;
15769     }
15770     SendToProgram("?\n", cps);
15771 }
15772
15773 void
15774 TruncateGameEvent ()
15775 {
15776     EditGameEvent();
15777     if (gameMode != EditGame) return;
15778     TruncateGame();
15779 }
15780
15781 void
15782 TruncateGame ()
15783 {
15784     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15785     if (forwardMostMove > currentMove) {
15786         if (gameInfo.resultDetails != NULL) {
15787             free(gameInfo.resultDetails);
15788             gameInfo.resultDetails = NULL;
15789             gameInfo.result = GameUnfinished;
15790         }
15791         forwardMostMove = currentMove;
15792         HistorySet(parseList, backwardMostMove, forwardMostMove,
15793                    currentMove-1);
15794     }
15795 }
15796
15797 void
15798 HintEvent ()
15799 {
15800     if (appData.noChessProgram) return;
15801     switch (gameMode) {
15802       case MachinePlaysWhite:
15803         if (WhiteOnMove(forwardMostMove)) {
15804             DisplayError(_("Wait until your turn."), 0);
15805             return;
15806         }
15807         break;
15808       case BeginningOfGame:
15809       case MachinePlaysBlack:
15810         if (!WhiteOnMove(forwardMostMove)) {
15811             DisplayError(_("Wait until your turn."), 0);
15812             return;
15813         }
15814         break;
15815       default:
15816         DisplayError(_("No hint available"), 0);
15817         return;
15818     }
15819     SendToProgram("hint\n", &first);
15820     hintRequested = TRUE;
15821 }
15822
15823 void
15824 CreateBookEvent ()
15825 {
15826     ListGame * lg = (ListGame *) gameList.head;
15827     FILE *f, *g;
15828     int nItem;
15829     static int secondTime = FALSE;
15830
15831     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15832         DisplayError(_("Game list not loaded or empty"), 0);
15833         return;
15834     }
15835
15836     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15837         fclose(g);
15838         secondTime++;
15839         DisplayNote(_("Book file exists! Try again for overwrite."));
15840         return;
15841     }
15842
15843     creatingBook = TRUE;
15844     secondTime = FALSE;
15845
15846     /* Get list size */
15847     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15848         LoadGame(f, nItem, "", TRUE);
15849         AddGameToBook(TRUE);
15850         lg = (ListGame *) lg->node.succ;
15851     }
15852
15853     creatingBook = FALSE;
15854     FlushBook();
15855 }
15856
15857 void
15858 BookEvent ()
15859 {
15860     if (appData.noChessProgram) return;
15861     switch (gameMode) {
15862       case MachinePlaysWhite:
15863         if (WhiteOnMove(forwardMostMove)) {
15864             DisplayError(_("Wait until your turn."), 0);
15865             return;
15866         }
15867         break;
15868       case BeginningOfGame:
15869       case MachinePlaysBlack:
15870         if (!WhiteOnMove(forwardMostMove)) {
15871             DisplayError(_("Wait until your turn."), 0);
15872             return;
15873         }
15874         break;
15875       case EditPosition:
15876         EditPositionDone(TRUE);
15877         break;
15878       case TwoMachinesPlay:
15879         return;
15880       default:
15881         break;
15882     }
15883     SendToProgram("bk\n", &first);
15884     bookOutput[0] = NULLCHAR;
15885     bookRequested = TRUE;
15886 }
15887
15888 void
15889 AboutGameEvent ()
15890 {
15891     char *tags = PGNTags(&gameInfo);
15892     TagsPopUp(tags, CmailMsg());
15893     free(tags);
15894 }
15895
15896 /* end button procedures */
15897
15898 void
15899 PrintPosition (FILE *fp, int move)
15900 {
15901     int i, j;
15902
15903     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15904         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15905             char c = PieceToChar(boards[move][i][j]);
15906             fputc(c == 'x' ? '.' : c, fp);
15907             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15908         }
15909     }
15910     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15911       fprintf(fp, "white to play\n");
15912     else
15913       fprintf(fp, "black to play\n");
15914 }
15915
15916 void
15917 PrintOpponents (FILE *fp)
15918 {
15919     if (gameInfo.white != NULL) {
15920         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15921     } else {
15922         fprintf(fp, "\n");
15923     }
15924 }
15925
15926 /* Find last component of program's own name, using some heuristics */
15927 void
15928 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15929 {
15930     char *p, *q, c;
15931     int local = (strcmp(host, "localhost") == 0);
15932     while (!local && (p = strchr(prog, ';')) != NULL) {
15933         p++;
15934         while (*p == ' ') p++;
15935         prog = p;
15936     }
15937     if (*prog == '"' || *prog == '\'') {
15938         q = strchr(prog + 1, *prog);
15939     } else {
15940         q = strchr(prog, ' ');
15941     }
15942     if (q == NULL) q = prog + strlen(prog);
15943     p = q;
15944     while (p >= prog && *p != '/' && *p != '\\') p--;
15945     p++;
15946     if(p == prog && *p == '"') p++;
15947     c = *q; *q = 0;
15948     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15949     memcpy(buf, p, q - p);
15950     buf[q - p] = NULLCHAR;
15951     if (!local) {
15952         strcat(buf, "@");
15953         strcat(buf, host);
15954     }
15955 }
15956
15957 char *
15958 TimeControlTagValue ()
15959 {
15960     char buf[MSG_SIZ];
15961     if (!appData.clockMode) {
15962       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15963     } else if (movesPerSession > 0) {
15964       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15965     } else if (timeIncrement == 0) {
15966       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15967     } else {
15968       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15969     }
15970     return StrSave(buf);
15971 }
15972
15973 void
15974 SetGameInfo ()
15975 {
15976     /* This routine is used only for certain modes */
15977     VariantClass v = gameInfo.variant;
15978     ChessMove r = GameUnfinished;
15979     char *p = NULL;
15980
15981     if(keepInfo) return;
15982
15983     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15984         r = gameInfo.result;
15985         p = gameInfo.resultDetails;
15986         gameInfo.resultDetails = NULL;
15987     }
15988     ClearGameInfo(&gameInfo);
15989     gameInfo.variant = v;
15990
15991     switch (gameMode) {
15992       case MachinePlaysWhite:
15993         gameInfo.event = StrSave( appData.pgnEventHeader );
15994         gameInfo.site = StrSave(HostName());
15995         gameInfo.date = PGNDate();
15996         gameInfo.round = StrSave("-");
15997         gameInfo.white = StrSave(first.tidy);
15998         gameInfo.black = StrSave(UserName());
15999         gameInfo.timeControl = TimeControlTagValue();
16000         break;
16001
16002       case MachinePlaysBlack:
16003         gameInfo.event = StrSave( appData.pgnEventHeader );
16004         gameInfo.site = StrSave(HostName());
16005         gameInfo.date = PGNDate();
16006         gameInfo.round = StrSave("-");
16007         gameInfo.white = StrSave(UserName());
16008         gameInfo.black = StrSave(first.tidy);
16009         gameInfo.timeControl = TimeControlTagValue();
16010         break;
16011
16012       case TwoMachinesPlay:
16013         gameInfo.event = StrSave( appData.pgnEventHeader );
16014         gameInfo.site = StrSave(HostName());
16015         gameInfo.date = PGNDate();
16016         if (roundNr > 0) {
16017             char buf[MSG_SIZ];
16018             snprintf(buf, MSG_SIZ, "%d", roundNr);
16019             gameInfo.round = StrSave(buf);
16020         } else {
16021             gameInfo.round = StrSave("-");
16022         }
16023         if (first.twoMachinesColor[0] == 'w') {
16024             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16025             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16026         } else {
16027             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16028             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16029         }
16030         gameInfo.timeControl = TimeControlTagValue();
16031         break;
16032
16033       case EditGame:
16034         gameInfo.event = StrSave("Edited game");
16035         gameInfo.site = StrSave(HostName());
16036         gameInfo.date = PGNDate();
16037         gameInfo.round = StrSave("-");
16038         gameInfo.white = StrSave("-");
16039         gameInfo.black = StrSave("-");
16040         gameInfo.result = r;
16041         gameInfo.resultDetails = p;
16042         break;
16043
16044       case EditPosition:
16045         gameInfo.event = StrSave("Edited position");
16046         gameInfo.site = StrSave(HostName());
16047         gameInfo.date = PGNDate();
16048         gameInfo.round = StrSave("-");
16049         gameInfo.white = StrSave("-");
16050         gameInfo.black = StrSave("-");
16051         break;
16052
16053       case IcsPlayingWhite:
16054       case IcsPlayingBlack:
16055       case IcsObserving:
16056       case IcsExamining:
16057         break;
16058
16059       case PlayFromGameFile:
16060         gameInfo.event = StrSave("Game from non-PGN file");
16061         gameInfo.site = StrSave(HostName());
16062         gameInfo.date = PGNDate();
16063         gameInfo.round = StrSave("-");
16064         gameInfo.white = StrSave("?");
16065         gameInfo.black = StrSave("?");
16066         break;
16067
16068       default:
16069         break;
16070     }
16071 }
16072
16073 void
16074 ReplaceComment (int index, char *text)
16075 {
16076     int len;
16077     char *p;
16078     float score;
16079
16080     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16081        pvInfoList[index-1].depth == len &&
16082        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16083        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16084     while (*text == '\n') text++;
16085     len = strlen(text);
16086     while (len > 0 && text[len - 1] == '\n') len--;
16087
16088     if (commentList[index] != NULL)
16089       free(commentList[index]);
16090
16091     if (len == 0) {
16092         commentList[index] = NULL;
16093         return;
16094     }
16095   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16096       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16097       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16098     commentList[index] = (char *) malloc(len + 2);
16099     strncpy(commentList[index], text, len);
16100     commentList[index][len] = '\n';
16101     commentList[index][len + 1] = NULLCHAR;
16102   } else {
16103     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16104     char *p;
16105     commentList[index] = (char *) malloc(len + 7);
16106     safeStrCpy(commentList[index], "{\n", 3);
16107     safeStrCpy(commentList[index]+2, text, len+1);
16108     commentList[index][len+2] = NULLCHAR;
16109     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16110     strcat(commentList[index], "\n}\n");
16111   }
16112 }
16113
16114 void
16115 CrushCRs (char *text)
16116 {
16117   char *p = text;
16118   char *q = text;
16119   char ch;
16120
16121   do {
16122     ch = *p++;
16123     if (ch == '\r') continue;
16124     *q++ = ch;
16125   } while (ch != '\0');
16126 }
16127
16128 void
16129 AppendComment (int index, char *text, Boolean addBraces)
16130 /* addBraces  tells if we should add {} */
16131 {
16132     int oldlen, len;
16133     char *old;
16134
16135 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16136     if(addBraces == 3) addBraces = 0; else // force appending literally
16137     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16138
16139     CrushCRs(text);
16140     while (*text == '\n') text++;
16141     len = strlen(text);
16142     while (len > 0 && text[len - 1] == '\n') len--;
16143     text[len] = NULLCHAR;
16144
16145     if (len == 0) return;
16146
16147     if (commentList[index] != NULL) {
16148       Boolean addClosingBrace = addBraces;
16149         old = commentList[index];
16150         oldlen = strlen(old);
16151         while(commentList[index][oldlen-1] ==  '\n')
16152           commentList[index][--oldlen] = NULLCHAR;
16153         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16154         safeStrCpy(commentList[index], old, oldlen + len + 6);
16155         free(old);
16156         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16157         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16158           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16159           while (*text == '\n') { text++; len--; }
16160           commentList[index][--oldlen] = NULLCHAR;
16161       }
16162         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16163         else          strcat(commentList[index], "\n");
16164         strcat(commentList[index], text);
16165         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16166         else          strcat(commentList[index], "\n");
16167     } else {
16168         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16169         if(addBraces)
16170           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16171         else commentList[index][0] = NULLCHAR;
16172         strcat(commentList[index], text);
16173         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16174         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16175     }
16176 }
16177
16178 static char *
16179 FindStr (char * text, char * sub_text)
16180 {
16181     char * result = strstr( text, sub_text );
16182
16183     if( result != NULL ) {
16184         result += strlen( sub_text );
16185     }
16186
16187     return result;
16188 }
16189
16190 /* [AS] Try to extract PV info from PGN comment */
16191 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16192 char *
16193 GetInfoFromComment (int index, char * text)
16194 {
16195     char * sep = text, *p;
16196
16197     if( text != NULL && index > 0 ) {
16198         int score = 0;
16199         int depth = 0;
16200         int time = -1, sec = 0, deci;
16201         char * s_eval = FindStr( text, "[%eval " );
16202         char * s_emt = FindStr( text, "[%emt " );
16203 #if 0
16204         if( s_eval != NULL || s_emt != NULL ) {
16205 #else
16206         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16207 #endif
16208             /* New style */
16209             char delim;
16210
16211             if( s_eval != NULL ) {
16212                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16213                     return text;
16214                 }
16215
16216                 if( delim != ']' ) {
16217                     return text;
16218                 }
16219             }
16220
16221             if( s_emt != NULL ) {
16222             }
16223                 return text;
16224         }
16225         else {
16226             /* We expect something like: [+|-]nnn.nn/dd */
16227             int score_lo = 0;
16228
16229             if(*text != '{') return text; // [HGM] braces: must be normal comment
16230
16231             sep = strchr( text, '/' );
16232             if( sep == NULL || sep < (text+4) ) {
16233                 return text;
16234             }
16235
16236             p = text;
16237             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16238             if(p[1] == '(') { // comment starts with PV
16239                p = strchr(p, ')'); // locate end of PV
16240                if(p == NULL || sep < p+5) return text;
16241                // at this point we have something like "{(.*) +0.23/6 ..."
16242                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16243                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16244                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16245             }
16246             time = -1; sec = -1; deci = -1;
16247             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16248                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16249                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16250                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16251                 return text;
16252             }
16253
16254             if( score_lo < 0 || score_lo >= 100 ) {
16255                 return text;
16256             }
16257
16258             if(sec >= 0) time = 600*time + 10*sec; else
16259             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16260
16261             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16262
16263             /* [HGM] PV time: now locate end of PV info */
16264             while( *++sep >= '0' && *sep <= '9'); // strip depth
16265             if(time >= 0)
16266             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16267             if(sec >= 0)
16268             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16269             if(deci >= 0)
16270             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16271             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16272         }
16273
16274         if( depth <= 0 ) {
16275             return text;
16276         }
16277
16278         if( time < 0 ) {
16279             time = -1;
16280         }
16281
16282         pvInfoList[index-1].depth = depth;
16283         pvInfoList[index-1].score = score;
16284         pvInfoList[index-1].time  = 10*time; // centi-sec
16285         if(*sep == '}') *sep = 0; else *--sep = '{';
16286         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16287     }
16288     return sep;
16289 }
16290
16291 void
16292 SendToProgram (char *message, ChessProgramState *cps)
16293 {
16294     int count, outCount, error;
16295     char buf[MSG_SIZ];
16296
16297     if (cps->pr == NoProc) return;
16298     Attention(cps);
16299
16300     if (appData.debugMode) {
16301         TimeMark now;
16302         GetTimeMark(&now);
16303         fprintf(debugFP, "%ld >%-6s: %s",
16304                 SubtractTimeMarks(&now, &programStartTime),
16305                 cps->which, message);
16306         if(serverFP)
16307             fprintf(serverFP, "%ld >%-6s: %s",
16308                 SubtractTimeMarks(&now, &programStartTime),
16309                 cps->which, message), fflush(serverFP);
16310     }
16311
16312     count = strlen(message);
16313     outCount = OutputToProcess(cps->pr, message, count, &error);
16314     if (outCount < count && !exiting
16315                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16316       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16317       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16318         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16319             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16320                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16321                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16322                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16323             } else {
16324                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16325                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16326                 gameInfo.result = res;
16327             }
16328             gameInfo.resultDetails = StrSave(buf);
16329         }
16330         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16331         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16332     }
16333 }
16334
16335 void
16336 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16337 {
16338     char *end_str;
16339     char buf[MSG_SIZ];
16340     ChessProgramState *cps = (ChessProgramState *)closure;
16341
16342     if (isr != cps->isr) return; /* Killed intentionally */
16343     if (count <= 0) {
16344         if (count == 0) {
16345             RemoveInputSource(cps->isr);
16346             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16347                     _(cps->which), cps->program);
16348             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16349             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16350                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16351                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16352                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16353                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16354                 } else {
16355                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16356                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16357                     gameInfo.result = res;
16358                 }
16359                 gameInfo.resultDetails = StrSave(buf);
16360             }
16361             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16362             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16363         } else {
16364             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16365                     _(cps->which), cps->program);
16366             RemoveInputSource(cps->isr);
16367
16368             /* [AS] Program is misbehaving badly... kill it */
16369             if( count == -2 ) {
16370                 DestroyChildProcess( cps->pr, 9 );
16371                 cps->pr = NoProc;
16372             }
16373
16374             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16375         }
16376         return;
16377     }
16378
16379     if ((end_str = strchr(message, '\r')) != NULL)
16380       *end_str = NULLCHAR;
16381     if ((end_str = strchr(message, '\n')) != NULL)
16382       *end_str = NULLCHAR;
16383
16384     if (appData.debugMode) {
16385         TimeMark now; int print = 1;
16386         char *quote = ""; char c; int i;
16387
16388         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16389                 char start = message[0];
16390                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16391                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16392                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16393                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16394                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16395                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16396                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16397                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16398                    sscanf(message, "hint: %c", &c)!=1 &&
16399                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16400                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16401                     print = (appData.engineComments >= 2);
16402                 }
16403                 message[0] = start; // restore original message
16404         }
16405         if(print) {
16406                 GetTimeMark(&now);
16407                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16408                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16409                         quote,
16410                         message);
16411                 if(serverFP)
16412                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16413                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16414                         quote,
16415                         message), fflush(serverFP);
16416         }
16417     }
16418
16419     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16420     if (appData.icsEngineAnalyze) {
16421         if (strstr(message, "whisper") != NULL ||
16422              strstr(message, "kibitz") != NULL ||
16423             strstr(message, "tellics") != NULL) return;
16424     }
16425
16426     HandleMachineMove(message, cps);
16427 }
16428
16429
16430 void
16431 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16432 {
16433     char buf[MSG_SIZ];
16434     int seconds;
16435
16436     if( timeControl_2 > 0 ) {
16437         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16438             tc = timeControl_2;
16439         }
16440     }
16441     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16442     inc /= cps->timeOdds;
16443     st  /= cps->timeOdds;
16444
16445     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16446
16447     if (st > 0) {
16448       /* Set exact time per move, normally using st command */
16449       if (cps->stKludge) {
16450         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16451         seconds = st % 60;
16452         if (seconds == 0) {
16453           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16454         } else {
16455           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16456         }
16457       } else {
16458         snprintf(buf, MSG_SIZ, "st %d\n", st);
16459       }
16460     } else {
16461       /* Set conventional or incremental time control, using level command */
16462       if (seconds == 0) {
16463         /* Note old gnuchess bug -- minutes:seconds used to not work.
16464            Fixed in later versions, but still avoid :seconds
16465            when seconds is 0. */
16466         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16467       } else {
16468         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16469                  seconds, inc/1000.);
16470       }
16471     }
16472     SendToProgram(buf, cps);
16473
16474     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16475     /* Orthogonally, limit search to given depth */
16476     if (sd > 0) {
16477       if (cps->sdKludge) {
16478         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16479       } else {
16480         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16481       }
16482       SendToProgram(buf, cps);
16483     }
16484
16485     if(cps->nps >= 0) { /* [HGM] nps */
16486         if(cps->supportsNPS == FALSE)
16487           cps->nps = -1; // don't use if engine explicitly says not supported!
16488         else {
16489           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16490           SendToProgram(buf, cps);
16491         }
16492     }
16493 }
16494
16495 ChessProgramState *
16496 WhitePlayer ()
16497 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16498 {
16499     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16500        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16501         return &second;
16502     return &first;
16503 }
16504
16505 void
16506 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16507 {
16508     char message[MSG_SIZ];
16509     long time, otime;
16510
16511     /* Note: this routine must be called when the clocks are stopped
16512        or when they have *just* been set or switched; otherwise
16513        it will be off by the time since the current tick started.
16514     */
16515     if (machineWhite) {
16516         time = whiteTimeRemaining / 10;
16517         otime = blackTimeRemaining / 10;
16518     } else {
16519         time = blackTimeRemaining / 10;
16520         otime = whiteTimeRemaining / 10;
16521     }
16522     /* [HGM] translate opponent's time by time-odds factor */
16523     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16524
16525     if (time <= 0) time = 1;
16526     if (otime <= 0) otime = 1;
16527
16528     snprintf(message, MSG_SIZ, "time %ld\n", time);
16529     SendToProgram(message, cps);
16530
16531     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16532     SendToProgram(message, cps);
16533 }
16534
16535 char *
16536 EngineDefinedVariant (ChessProgramState *cps, int n)
16537 {   // return name of n-th unknown variant that engine supports
16538     static char buf[MSG_SIZ];
16539     char *p, *s = cps->variants;
16540     if(!s) return NULL;
16541     do { // parse string from variants feature
16542       VariantClass v;
16543         p = strchr(s, ',');
16544         if(p) *p = NULLCHAR;
16545       v = StringToVariant(s);
16546       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16547         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16548             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16549         }
16550         if(p) *p++ = ',';
16551         if(n < 0) return buf;
16552     } while(s = p);
16553     return NULL;
16554 }
16555
16556 int
16557 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16558 {
16559   char buf[MSG_SIZ];
16560   int len = strlen(name);
16561   int val;
16562
16563   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16564     (*p) += len + 1;
16565     sscanf(*p, "%d", &val);
16566     *loc = (val != 0);
16567     while (**p && **p != ' ')
16568       (*p)++;
16569     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16570     SendToProgram(buf, cps);
16571     return TRUE;
16572   }
16573   return FALSE;
16574 }
16575
16576 int
16577 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16578 {
16579   char buf[MSG_SIZ];
16580   int len = strlen(name);
16581   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16582     (*p) += len + 1;
16583     sscanf(*p, "%d", loc);
16584     while (**p && **p != ' ') (*p)++;
16585     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16586     SendToProgram(buf, cps);
16587     return TRUE;
16588   }
16589   return FALSE;
16590 }
16591
16592 int
16593 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16594 {
16595   char buf[MSG_SIZ];
16596   int len = strlen(name);
16597   if (strncmp((*p), name, len) == 0
16598       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16599     (*p) += len + 2;
16600     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16601     sscanf(*p, "%[^\"]", *loc);
16602     while (**p && **p != '\"') (*p)++;
16603     if (**p == '\"') (*p)++;
16604     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16605     SendToProgram(buf, cps);
16606     return TRUE;
16607   }
16608   return FALSE;
16609 }
16610
16611 int
16612 ParseOption (Option *opt, ChessProgramState *cps)
16613 // [HGM] options: process the string that defines an engine option, and determine
16614 // name, type, default value, and allowed value range
16615 {
16616         char *p, *q, buf[MSG_SIZ];
16617         int n, min = (-1)<<31, max = 1<<31, def;
16618
16619         if(p = strstr(opt->name, " -spin ")) {
16620             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16621             if(max < min) max = min; // enforce consistency
16622             if(def < min) def = min;
16623             if(def > max) def = max;
16624             opt->value = def;
16625             opt->min = min;
16626             opt->max = max;
16627             opt->type = Spin;
16628         } else if((p = strstr(opt->name, " -slider "))) {
16629             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16630             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16631             if(max < min) max = min; // enforce consistency
16632             if(def < min) def = min;
16633             if(def > max) def = max;
16634             opt->value = def;
16635             opt->min = min;
16636             opt->max = max;
16637             opt->type = Spin; // Slider;
16638         } else if((p = strstr(opt->name, " -string "))) {
16639             opt->textValue = p+9;
16640             opt->type = TextBox;
16641         } else if((p = strstr(opt->name, " -file "))) {
16642             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16643             opt->textValue = p+7;
16644             opt->type = FileName; // FileName;
16645         } else if((p = strstr(opt->name, " -path "))) {
16646             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16647             opt->textValue = p+7;
16648             opt->type = PathName; // PathName;
16649         } else if(p = strstr(opt->name, " -check ")) {
16650             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16651             opt->value = (def != 0);
16652             opt->type = CheckBox;
16653         } else if(p = strstr(opt->name, " -combo ")) {
16654             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16655             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16656             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16657             opt->value = n = 0;
16658             while(q = StrStr(q, " /// ")) {
16659                 n++; *q = 0;    // count choices, and null-terminate each of them
16660                 q += 5;
16661                 if(*q == '*') { // remember default, which is marked with * prefix
16662                     q++;
16663                     opt->value = n;
16664                 }
16665                 cps->comboList[cps->comboCnt++] = q;
16666             }
16667             cps->comboList[cps->comboCnt++] = NULL;
16668             opt->max = n + 1;
16669             opt->type = ComboBox;
16670         } else if(p = strstr(opt->name, " -button")) {
16671             opt->type = Button;
16672         } else if(p = strstr(opt->name, " -save")) {
16673             opt->type = SaveButton;
16674         } else return FALSE;
16675         *p = 0; // terminate option name
16676         // now look if the command-line options define a setting for this engine option.
16677         if(cps->optionSettings && cps->optionSettings[0])
16678             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16679         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16680           snprintf(buf, MSG_SIZ, "option %s", p);
16681                 if(p = strstr(buf, ",")) *p = 0;
16682                 if(q = strchr(buf, '=')) switch(opt->type) {
16683                     case ComboBox:
16684                         for(n=0; n<opt->max; n++)
16685                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16686                         break;
16687                     case TextBox:
16688                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16689                         break;
16690                     case Spin:
16691                     case CheckBox:
16692                         opt->value = atoi(q+1);
16693                     default:
16694                         break;
16695                 }
16696                 strcat(buf, "\n");
16697                 SendToProgram(buf, cps);
16698         }
16699         return TRUE;
16700 }
16701
16702 void
16703 FeatureDone (ChessProgramState *cps, int val)
16704 {
16705   DelayedEventCallback cb = GetDelayedEvent();
16706   if ((cb == InitBackEnd3 && cps == &first) ||
16707       (cb == SettingsMenuIfReady && cps == &second) ||
16708       (cb == LoadEngine) ||
16709       (cb == TwoMachinesEventIfReady)) {
16710     CancelDelayedEvent();
16711     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16712   }
16713   cps->initDone = val;
16714   if(val) cps->reload = FALSE;
16715 }
16716
16717 /* Parse feature command from engine */
16718 void
16719 ParseFeatures (char *args, ChessProgramState *cps)
16720 {
16721   char *p = args;
16722   char *q = NULL;
16723   int val;
16724   char buf[MSG_SIZ];
16725
16726   for (;;) {
16727     while (*p == ' ') p++;
16728     if (*p == NULLCHAR) return;
16729
16730     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16731     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16732     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16733     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16734     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16735     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16736     if (BoolFeature(&p, "reuse", &val, cps)) {
16737       /* Engine can disable reuse, but can't enable it if user said no */
16738       if (!val) cps->reuse = FALSE;
16739       continue;
16740     }
16741     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16742     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16743       if (gameMode == TwoMachinesPlay) {
16744         DisplayTwoMachinesTitle();
16745       } else {
16746         DisplayTitle("");
16747       }
16748       continue;
16749     }
16750     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16751     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16752     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16753     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16754     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16755     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16756     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16757     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16758     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16759     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16760     if (IntFeature(&p, "done", &val, cps)) {
16761       FeatureDone(cps, val);
16762       continue;
16763     }
16764     /* Added by Tord: */
16765     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16766     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16767     /* End of additions by Tord */
16768
16769     /* [HGM] added features: */
16770     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16771     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16772     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16773     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16774     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16775     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16776     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16777     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16778         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16779         FREE(cps->option[cps->nrOptions].name);
16780         cps->option[cps->nrOptions].name = q; q = NULL;
16781         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16782           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16783             SendToProgram(buf, cps);
16784             continue;
16785         }
16786         if(cps->nrOptions >= MAX_OPTIONS) {
16787             cps->nrOptions--;
16788             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16789             DisplayError(buf, 0);
16790         }
16791         continue;
16792     }
16793     /* End of additions by HGM */
16794
16795     /* unknown feature: complain and skip */
16796     q = p;
16797     while (*q && *q != '=') q++;
16798     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16799     SendToProgram(buf, cps);
16800     p = q;
16801     if (*p == '=') {
16802       p++;
16803       if (*p == '\"') {
16804         p++;
16805         while (*p && *p != '\"') p++;
16806         if (*p == '\"') p++;
16807       } else {
16808         while (*p && *p != ' ') p++;
16809       }
16810     }
16811   }
16812
16813 }
16814
16815 void
16816 PeriodicUpdatesEvent (int newState)
16817 {
16818     if (newState == appData.periodicUpdates)
16819       return;
16820
16821     appData.periodicUpdates=newState;
16822
16823     /* Display type changes, so update it now */
16824 //    DisplayAnalysis();
16825
16826     /* Get the ball rolling again... */
16827     if (newState) {
16828         AnalysisPeriodicEvent(1);
16829         StartAnalysisClock();
16830     }
16831 }
16832
16833 void
16834 PonderNextMoveEvent (int newState)
16835 {
16836     if (newState == appData.ponderNextMove) return;
16837     if (gameMode == EditPosition) EditPositionDone(TRUE);
16838     if (newState) {
16839         SendToProgram("hard\n", &first);
16840         if (gameMode == TwoMachinesPlay) {
16841             SendToProgram("hard\n", &second);
16842         }
16843     } else {
16844         SendToProgram("easy\n", &first);
16845         thinkOutput[0] = NULLCHAR;
16846         if (gameMode == TwoMachinesPlay) {
16847             SendToProgram("easy\n", &second);
16848         }
16849     }
16850     appData.ponderNextMove = newState;
16851 }
16852
16853 void
16854 NewSettingEvent (int option, int *feature, char *command, int value)
16855 {
16856     char buf[MSG_SIZ];
16857
16858     if (gameMode == EditPosition) EditPositionDone(TRUE);
16859     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16860     if(feature == NULL || *feature) SendToProgram(buf, &first);
16861     if (gameMode == TwoMachinesPlay) {
16862         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16863     }
16864 }
16865
16866 void
16867 ShowThinkingEvent ()
16868 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16869 {
16870     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16871     int newState = appData.showThinking
16872         // [HGM] thinking: other features now need thinking output as well
16873         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16874
16875     if (oldState == newState) return;
16876     oldState = newState;
16877     if (gameMode == EditPosition) EditPositionDone(TRUE);
16878     if (oldState) {
16879         SendToProgram("post\n", &first);
16880         if (gameMode == TwoMachinesPlay) {
16881             SendToProgram("post\n", &second);
16882         }
16883     } else {
16884         SendToProgram("nopost\n", &first);
16885         thinkOutput[0] = NULLCHAR;
16886         if (gameMode == TwoMachinesPlay) {
16887             SendToProgram("nopost\n", &second);
16888         }
16889     }
16890 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16891 }
16892
16893 void
16894 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16895 {
16896   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16897   if (pr == NoProc) return;
16898   AskQuestion(title, question, replyPrefix, pr);
16899 }
16900
16901 void
16902 TypeInEvent (char firstChar)
16903 {
16904     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16905         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16906         gameMode == AnalyzeMode || gameMode == EditGame ||
16907         gameMode == EditPosition || gameMode == IcsExamining ||
16908         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16909         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16910                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16911                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16912         gameMode == Training) PopUpMoveDialog(firstChar);
16913 }
16914
16915 void
16916 TypeInDoneEvent (char *move)
16917 {
16918         Board board;
16919         int n, fromX, fromY, toX, toY;
16920         char promoChar;
16921         ChessMove moveType;
16922
16923         // [HGM] FENedit
16924         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16925                 EditPositionPasteFEN(move);
16926                 return;
16927         }
16928         // [HGM] movenum: allow move number to be typed in any mode
16929         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16930           ToNrEvent(2*n-1);
16931           return;
16932         }
16933         // undocumented kludge: allow command-line option to be typed in!
16934         // (potentially fatal, and does not implement the effect of the option.)
16935         // should only be used for options that are values on which future decisions will be made,
16936         // and definitely not on options that would be used during initialization.
16937         if(strstr(move, "!!! -") == move) {
16938             ParseArgsFromString(move+4);
16939             return;
16940         }
16941
16942       if (gameMode != EditGame && currentMove != forwardMostMove &&
16943         gameMode != Training) {
16944         DisplayMoveError(_("Displayed move is not current"));
16945       } else {
16946         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16947           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16948         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16949         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16950           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16951           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16952         } else {
16953           DisplayMoveError(_("Could not parse move"));
16954         }
16955       }
16956 }
16957
16958 void
16959 DisplayMove (int moveNumber)
16960 {
16961     char message[MSG_SIZ];
16962     char res[MSG_SIZ];
16963     char cpThinkOutput[MSG_SIZ];
16964
16965     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16966
16967     if (moveNumber == forwardMostMove - 1 ||
16968         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16969
16970         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16971
16972         if (strchr(cpThinkOutput, '\n')) {
16973             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16974         }
16975     } else {
16976         *cpThinkOutput = NULLCHAR;
16977     }
16978
16979     /* [AS] Hide thinking from human user */
16980     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16981         *cpThinkOutput = NULLCHAR;
16982         if( thinkOutput[0] != NULLCHAR ) {
16983             int i;
16984
16985             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16986                 cpThinkOutput[i] = '.';
16987             }
16988             cpThinkOutput[i] = NULLCHAR;
16989             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16990         }
16991     }
16992
16993     if (moveNumber == forwardMostMove - 1 &&
16994         gameInfo.resultDetails != NULL) {
16995         if (gameInfo.resultDetails[0] == NULLCHAR) {
16996           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16997         } else {
16998           snprintf(res, MSG_SIZ, " {%s} %s",
16999                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17000         }
17001     } else {
17002         res[0] = NULLCHAR;
17003     }
17004
17005     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17006         DisplayMessage(res, cpThinkOutput);
17007     } else {
17008       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17009                 WhiteOnMove(moveNumber) ? " " : ".. ",
17010                 parseList[moveNumber], res);
17011         DisplayMessage(message, cpThinkOutput);
17012     }
17013 }
17014
17015 void
17016 DisplayComment (int moveNumber, char *text)
17017 {
17018     char title[MSG_SIZ];
17019
17020     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17021       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17022     } else {
17023       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17024               WhiteOnMove(moveNumber) ? " " : ".. ",
17025               parseList[moveNumber]);
17026     }
17027     if (text != NULL && (appData.autoDisplayComment || commentUp))
17028         CommentPopUp(title, text);
17029 }
17030
17031 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17032  * might be busy thinking or pondering.  It can be omitted if your
17033  * gnuchess is configured to stop thinking immediately on any user
17034  * input.  However, that gnuchess feature depends on the FIONREAD
17035  * ioctl, which does not work properly on some flavors of Unix.
17036  */
17037 void
17038 Attention (ChessProgramState *cps)
17039 {
17040 #if ATTENTION
17041     if (!cps->useSigint) return;
17042     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17043     switch (gameMode) {
17044       case MachinePlaysWhite:
17045       case MachinePlaysBlack:
17046       case TwoMachinesPlay:
17047       case IcsPlayingWhite:
17048       case IcsPlayingBlack:
17049       case AnalyzeMode:
17050       case AnalyzeFile:
17051         /* Skip if we know it isn't thinking */
17052         if (!cps->maybeThinking) return;
17053         if (appData.debugMode)
17054           fprintf(debugFP, "Interrupting %s\n", cps->which);
17055         InterruptChildProcess(cps->pr);
17056         cps->maybeThinking = FALSE;
17057         break;
17058       default:
17059         break;
17060     }
17061 #endif /*ATTENTION*/
17062 }
17063
17064 int
17065 CheckFlags ()
17066 {
17067     if (whiteTimeRemaining <= 0) {
17068         if (!whiteFlag) {
17069             whiteFlag = TRUE;
17070             if (appData.icsActive) {
17071                 if (appData.autoCallFlag &&
17072                     gameMode == IcsPlayingBlack && !blackFlag) {
17073                   SendToICS(ics_prefix);
17074                   SendToICS("flag\n");
17075                 }
17076             } else {
17077                 if (blackFlag) {
17078                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17079                 } else {
17080                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17081                     if (appData.autoCallFlag) {
17082                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17083                         return TRUE;
17084                     }
17085                 }
17086             }
17087         }
17088     }
17089     if (blackTimeRemaining <= 0) {
17090         if (!blackFlag) {
17091             blackFlag = TRUE;
17092             if (appData.icsActive) {
17093                 if (appData.autoCallFlag &&
17094                     gameMode == IcsPlayingWhite && !whiteFlag) {
17095                   SendToICS(ics_prefix);
17096                   SendToICS("flag\n");
17097                 }
17098             } else {
17099                 if (whiteFlag) {
17100                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17101                 } else {
17102                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17103                     if (appData.autoCallFlag) {
17104                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17105                         return TRUE;
17106                     }
17107                 }
17108             }
17109         }
17110     }
17111     return FALSE;
17112 }
17113
17114 void
17115 CheckTimeControl ()
17116 {
17117     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17118         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17119
17120     /*
17121      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17122      */
17123     if ( !WhiteOnMove(forwardMostMove) ) {
17124         /* White made time control */
17125         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17126         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17127         /* [HGM] time odds: correct new time quota for time odds! */
17128                                             / WhitePlayer()->timeOdds;
17129         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17130     } else {
17131         lastBlack -= blackTimeRemaining;
17132         /* Black made time control */
17133         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17134                                             / WhitePlayer()->other->timeOdds;
17135         lastWhite = whiteTimeRemaining;
17136     }
17137 }
17138
17139 void
17140 DisplayBothClocks ()
17141 {
17142     int wom = gameMode == EditPosition ?
17143       !blackPlaysFirst : WhiteOnMove(currentMove);
17144     DisplayWhiteClock(whiteTimeRemaining, wom);
17145     DisplayBlackClock(blackTimeRemaining, !wom);
17146 }
17147
17148
17149 /* Timekeeping seems to be a portability nightmare.  I think everyone
17150    has ftime(), but I'm really not sure, so I'm including some ifdefs
17151    to use other calls if you don't.  Clocks will be less accurate if
17152    you have neither ftime nor gettimeofday.
17153 */
17154
17155 /* VS 2008 requires the #include outside of the function */
17156 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17157 #include <sys/timeb.h>
17158 #endif
17159
17160 /* Get the current time as a TimeMark */
17161 void
17162 GetTimeMark (TimeMark *tm)
17163 {
17164 #if HAVE_GETTIMEOFDAY
17165
17166     struct timeval timeVal;
17167     struct timezone timeZone;
17168
17169     gettimeofday(&timeVal, &timeZone);
17170     tm->sec = (long) timeVal.tv_sec;
17171     tm->ms = (int) (timeVal.tv_usec / 1000L);
17172
17173 #else /*!HAVE_GETTIMEOFDAY*/
17174 #if HAVE_FTIME
17175
17176 // include <sys/timeb.h> / moved to just above start of function
17177     struct timeb timeB;
17178
17179     ftime(&timeB);
17180     tm->sec = (long) timeB.time;
17181     tm->ms = (int) timeB.millitm;
17182
17183 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17184     tm->sec = (long) time(NULL);
17185     tm->ms = 0;
17186 #endif
17187 #endif
17188 }
17189
17190 /* Return the difference in milliseconds between two
17191    time marks.  We assume the difference will fit in a long!
17192 */
17193 long
17194 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17195 {
17196     return 1000L*(tm2->sec - tm1->sec) +
17197            (long) (tm2->ms - tm1->ms);
17198 }
17199
17200
17201 /*
17202  * Code to manage the game clocks.
17203  *
17204  * In tournament play, black starts the clock and then white makes a move.
17205  * We give the human user a slight advantage if he is playing white---the
17206  * clocks don't run until he makes his first move, so it takes zero time.
17207  * Also, we don't account for network lag, so we could get out of sync
17208  * with GNU Chess's clock -- but then, referees are always right.
17209  */
17210
17211 static TimeMark tickStartTM;
17212 static long intendedTickLength;
17213
17214 long
17215 NextTickLength (long timeRemaining)
17216 {
17217     long nominalTickLength, nextTickLength;
17218
17219     if (timeRemaining > 0L && timeRemaining <= 10000L)
17220       nominalTickLength = 100L;
17221     else
17222       nominalTickLength = 1000L;
17223     nextTickLength = timeRemaining % nominalTickLength;
17224     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17225
17226     return nextTickLength;
17227 }
17228
17229 /* Adjust clock one minute up or down */
17230 void
17231 AdjustClock (Boolean which, int dir)
17232 {
17233     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17234     if(which) blackTimeRemaining += 60000*dir;
17235     else      whiteTimeRemaining += 60000*dir;
17236     DisplayBothClocks();
17237     adjustedClock = TRUE;
17238 }
17239
17240 /* Stop clocks and reset to a fresh time control */
17241 void
17242 ResetClocks ()
17243 {
17244     (void) StopClockTimer();
17245     if (appData.icsActive) {
17246         whiteTimeRemaining = blackTimeRemaining = 0;
17247     } else if (searchTime) {
17248         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17249         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17250     } else { /* [HGM] correct new time quote for time odds */
17251         whiteTC = blackTC = fullTimeControlString;
17252         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17253         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17254     }
17255     if (whiteFlag || blackFlag) {
17256         DisplayTitle("");
17257         whiteFlag = blackFlag = FALSE;
17258     }
17259     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17260     DisplayBothClocks();
17261     adjustedClock = FALSE;
17262 }
17263
17264 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17265
17266 /* Decrement running clock by amount of time that has passed */
17267 void
17268 DecrementClocks ()
17269 {
17270     long timeRemaining;
17271     long lastTickLength, fudge;
17272     TimeMark now;
17273
17274     if (!appData.clockMode) return;
17275     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17276
17277     GetTimeMark(&now);
17278
17279     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17280
17281     /* Fudge if we woke up a little too soon */
17282     fudge = intendedTickLength - lastTickLength;
17283     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17284
17285     if (WhiteOnMove(forwardMostMove)) {
17286         if(whiteNPS >= 0) lastTickLength = 0;
17287         timeRemaining = whiteTimeRemaining -= lastTickLength;
17288         if(timeRemaining < 0 && !appData.icsActive) {
17289             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17290             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17291                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17292                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17293             }
17294         }
17295         DisplayWhiteClock(whiteTimeRemaining - fudge,
17296                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17297     } else {
17298         if(blackNPS >= 0) lastTickLength = 0;
17299         timeRemaining = blackTimeRemaining -= lastTickLength;
17300         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17301             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17302             if(suddenDeath) {
17303                 blackStartMove = forwardMostMove;
17304                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17305             }
17306         }
17307         DisplayBlackClock(blackTimeRemaining - fudge,
17308                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17309     }
17310     if (CheckFlags()) return;
17311
17312     if(twoBoards) { // count down secondary board's clocks as well
17313         activePartnerTime -= lastTickLength;
17314         partnerUp = 1;
17315         if(activePartner == 'W')
17316             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17317         else
17318             DisplayBlackClock(activePartnerTime, TRUE);
17319         partnerUp = 0;
17320     }
17321
17322     tickStartTM = now;
17323     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17324     StartClockTimer(intendedTickLength);
17325
17326     /* if the time remaining has fallen below the alarm threshold, sound the
17327      * alarm. if the alarm has sounded and (due to a takeback or time control
17328      * with increment) the time remaining has increased to a level above the
17329      * threshold, reset the alarm so it can sound again.
17330      */
17331
17332     if (appData.icsActive && appData.icsAlarm) {
17333
17334         /* make sure we are dealing with the user's clock */
17335         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17336                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17337            )) return;
17338
17339         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17340             alarmSounded = FALSE;
17341         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17342             PlayAlarmSound();
17343             alarmSounded = TRUE;
17344         }
17345     }
17346 }
17347
17348
17349 /* A player has just moved, so stop the previously running
17350    clock and (if in clock mode) start the other one.
17351    We redisplay both clocks in case we're in ICS mode, because
17352    ICS gives us an update to both clocks after every move.
17353    Note that this routine is called *after* forwardMostMove
17354    is updated, so the last fractional tick must be subtracted
17355    from the color that is *not* on move now.
17356 */
17357 void
17358 SwitchClocks (int newMoveNr)
17359 {
17360     long lastTickLength;
17361     TimeMark now;
17362     int flagged = FALSE;
17363
17364     GetTimeMark(&now);
17365
17366     if (StopClockTimer() && appData.clockMode) {
17367         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17368         if (!WhiteOnMove(forwardMostMove)) {
17369             if(blackNPS >= 0) lastTickLength = 0;
17370             blackTimeRemaining -= lastTickLength;
17371            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17372 //         if(pvInfoList[forwardMostMove].time == -1)
17373                  pvInfoList[forwardMostMove].time =               // use GUI time
17374                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17375         } else {
17376            if(whiteNPS >= 0) lastTickLength = 0;
17377            whiteTimeRemaining -= lastTickLength;
17378            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17379 //         if(pvInfoList[forwardMostMove].time == -1)
17380                  pvInfoList[forwardMostMove].time =
17381                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17382         }
17383         flagged = CheckFlags();
17384     }
17385     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17386     CheckTimeControl();
17387
17388     if (flagged || !appData.clockMode) return;
17389
17390     switch (gameMode) {
17391       case MachinePlaysBlack:
17392       case MachinePlaysWhite:
17393       case BeginningOfGame:
17394         if (pausing) return;
17395         break;
17396
17397       case EditGame:
17398       case PlayFromGameFile:
17399       case IcsExamining:
17400         return;
17401
17402       default:
17403         break;
17404     }
17405
17406     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17407         if(WhiteOnMove(forwardMostMove))
17408              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17409         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17410     }
17411
17412     tickStartTM = now;
17413     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17414       whiteTimeRemaining : blackTimeRemaining);
17415     StartClockTimer(intendedTickLength);
17416 }
17417
17418
17419 /* Stop both clocks */
17420 void
17421 StopClocks ()
17422 {
17423     long lastTickLength;
17424     TimeMark now;
17425
17426     if (!StopClockTimer()) return;
17427     if (!appData.clockMode) return;
17428
17429     GetTimeMark(&now);
17430
17431     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17432     if (WhiteOnMove(forwardMostMove)) {
17433         if(whiteNPS >= 0) lastTickLength = 0;
17434         whiteTimeRemaining -= lastTickLength;
17435         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17436     } else {
17437         if(blackNPS >= 0) lastTickLength = 0;
17438         blackTimeRemaining -= lastTickLength;
17439         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17440     }
17441     CheckFlags();
17442 }
17443
17444 /* Start clock of player on move.  Time may have been reset, so
17445    if clock is already running, stop and restart it. */
17446 void
17447 StartClocks ()
17448 {
17449     (void) StopClockTimer(); /* in case it was running already */
17450     DisplayBothClocks();
17451     if (CheckFlags()) return;
17452
17453     if (!appData.clockMode) return;
17454     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17455
17456     GetTimeMark(&tickStartTM);
17457     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17458       whiteTimeRemaining : blackTimeRemaining);
17459
17460    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17461     whiteNPS = blackNPS = -1;
17462     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17463        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17464         whiteNPS = first.nps;
17465     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17466        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17467         blackNPS = first.nps;
17468     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17469         whiteNPS = second.nps;
17470     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17471         blackNPS = second.nps;
17472     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17473
17474     StartClockTimer(intendedTickLength);
17475 }
17476
17477 char *
17478 TimeString (long ms)
17479 {
17480     long second, minute, hour, day;
17481     char *sign = "";
17482     static char buf[32];
17483
17484     if (ms > 0 && ms <= 9900) {
17485       /* convert milliseconds to tenths, rounding up */
17486       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17487
17488       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17489       return buf;
17490     }
17491
17492     /* convert milliseconds to seconds, rounding up */
17493     /* use floating point to avoid strangeness of integer division
17494        with negative dividends on many machines */
17495     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17496
17497     if (second < 0) {
17498         sign = "-";
17499         second = -second;
17500     }
17501
17502     day = second / (60 * 60 * 24);
17503     second = second % (60 * 60 * 24);
17504     hour = second / (60 * 60);
17505     second = second % (60 * 60);
17506     minute = second / 60;
17507     second = second % 60;
17508
17509     if (day > 0)
17510       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17511               sign, day, hour, minute, second);
17512     else if (hour > 0)
17513       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17514     else
17515       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17516
17517     return buf;
17518 }
17519
17520
17521 /*
17522  * This is necessary because some C libraries aren't ANSI C compliant yet.
17523  */
17524 char *
17525 StrStr (char *string, char *match)
17526 {
17527     int i, length;
17528
17529     length = strlen(match);
17530
17531     for (i = strlen(string) - length; i >= 0; i--, string++)
17532       if (!strncmp(match, string, length))
17533         return string;
17534
17535     return NULL;
17536 }
17537
17538 char *
17539 StrCaseStr (char *string, char *match)
17540 {
17541     int i, j, length;
17542
17543     length = strlen(match);
17544
17545     for (i = strlen(string) - length; i >= 0; i--, string++) {
17546         for (j = 0; j < length; j++) {
17547             if (ToLower(match[j]) != ToLower(string[j]))
17548               break;
17549         }
17550         if (j == length) return string;
17551     }
17552
17553     return NULL;
17554 }
17555
17556 #ifndef _amigados
17557 int
17558 StrCaseCmp (char *s1, char *s2)
17559 {
17560     char c1, c2;
17561
17562     for (;;) {
17563         c1 = ToLower(*s1++);
17564         c2 = ToLower(*s2++);
17565         if (c1 > c2) return 1;
17566         if (c1 < c2) return -1;
17567         if (c1 == NULLCHAR) return 0;
17568     }
17569 }
17570
17571
17572 int
17573 ToLower (int c)
17574 {
17575     return isupper(c) ? tolower(c) : c;
17576 }
17577
17578
17579 int
17580 ToUpper (int c)
17581 {
17582     return islower(c) ? toupper(c) : c;
17583 }
17584 #endif /* !_amigados    */
17585
17586 char *
17587 StrSave (char *s)
17588 {
17589   char *ret;
17590
17591   if ((ret = (char *) malloc(strlen(s) + 1)))
17592     {
17593       safeStrCpy(ret, s, strlen(s)+1);
17594     }
17595   return ret;
17596 }
17597
17598 char *
17599 StrSavePtr (char *s, char **savePtr)
17600 {
17601     if (*savePtr) {
17602         free(*savePtr);
17603     }
17604     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17605       safeStrCpy(*savePtr, s, strlen(s)+1);
17606     }
17607     return(*savePtr);
17608 }
17609
17610 char *
17611 PGNDate ()
17612 {
17613     time_t clock;
17614     struct tm *tm;
17615     char buf[MSG_SIZ];
17616
17617     clock = time((time_t *)NULL);
17618     tm = localtime(&clock);
17619     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17620             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17621     return StrSave(buf);
17622 }
17623
17624
17625 char *
17626 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17627 {
17628     int i, j, fromX, fromY, toX, toY;
17629     int whiteToPlay;
17630     char buf[MSG_SIZ];
17631     char *p, *q;
17632     int emptycount;
17633     ChessSquare piece;
17634
17635     whiteToPlay = (gameMode == EditPosition) ?
17636       !blackPlaysFirst : (move % 2 == 0);
17637     p = buf;
17638
17639     /* Piece placement data */
17640     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17641         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17642         emptycount = 0;
17643         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17644             if (boards[move][i][j] == EmptySquare) {
17645                 emptycount++;
17646             } else { ChessSquare piece = boards[move][i][j];
17647                 if (emptycount > 0) {
17648                     if(emptycount<10) /* [HGM] can be >= 10 */
17649                         *p++ = '0' + emptycount;
17650                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17651                     emptycount = 0;
17652                 }
17653                 if(PieceToChar(piece) == '+') {
17654                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17655                     *p++ = '+';
17656                     piece = (ChessSquare)(CHUDEMOTED piece);
17657                 }
17658                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17659                 if(p[-1] == '~') {
17660                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17661                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17662                     *p++ = '~';
17663                 }
17664             }
17665         }
17666         if (emptycount > 0) {
17667             if(emptycount<10) /* [HGM] can be >= 10 */
17668                 *p++ = '0' + emptycount;
17669             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17670             emptycount = 0;
17671         }
17672         *p++ = '/';
17673     }
17674     *(p - 1) = ' ';
17675
17676     /* [HGM] print Crazyhouse or Shogi holdings */
17677     if( gameInfo.holdingsWidth ) {
17678         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17679         q = p;
17680         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17681             piece = boards[move][i][BOARD_WIDTH-1];
17682             if( piece != EmptySquare )
17683               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17684                   *p++ = PieceToChar(piece);
17685         }
17686         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17687             piece = boards[move][BOARD_HEIGHT-i-1][0];
17688             if( piece != EmptySquare )
17689               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17690                   *p++ = PieceToChar(piece);
17691         }
17692
17693         if( q == p ) *p++ = '-';
17694         *p++ = ']';
17695         *p++ = ' ';
17696     }
17697
17698     /* Active color */
17699     *p++ = whiteToPlay ? 'w' : 'b';
17700     *p++ = ' ';
17701
17702   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17703     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17704   } else {
17705   if(nrCastlingRights) {
17706      q = p;
17707      if(appData.fischerCastling) {
17708        /* [HGM] write directly from rights */
17709            if(boards[move][CASTLING][2] != NoRights &&
17710               boards[move][CASTLING][0] != NoRights   )
17711                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17712            if(boards[move][CASTLING][2] != NoRights &&
17713               boards[move][CASTLING][1] != NoRights   )
17714                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17715            if(boards[move][CASTLING][5] != NoRights &&
17716               boards[move][CASTLING][3] != NoRights   )
17717                 *p++ = boards[move][CASTLING][3] + AAA;
17718            if(boards[move][CASTLING][5] != NoRights &&
17719               boards[move][CASTLING][4] != NoRights   )
17720                 *p++ = boards[move][CASTLING][4] + AAA;
17721      } else {
17722
17723         /* [HGM] write true castling rights */
17724         if( nrCastlingRights == 6 ) {
17725             int q, k=0;
17726             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17727                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17728             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17729                  boards[move][CASTLING][2] != NoRights  );
17730             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17731                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17732                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17733                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17734                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17735             }
17736             if(q) *p++ = 'Q';
17737             k = 0;
17738             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17739                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17740             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17741                  boards[move][CASTLING][5] != NoRights  );
17742             if(gameInfo.variant == VariantSChess) {
17743                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17744                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17745                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17746                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17747             }
17748             if(q) *p++ = 'q';
17749         }
17750      }
17751      if (q == p) *p++ = '-'; /* No castling rights */
17752      *p++ = ' ';
17753   }
17754
17755   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17756      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17757      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17758     /* En passant target square */
17759     if (move > backwardMostMove) {
17760         fromX = moveList[move - 1][0] - AAA;
17761         fromY = moveList[move - 1][1] - ONE;
17762         toX = moveList[move - 1][2] - AAA;
17763         toY = moveList[move - 1][3] - ONE;
17764         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17765             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17766             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17767             fromX == toX) {
17768             /* 2-square pawn move just happened */
17769             *p++ = toX + AAA;
17770             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17771         } else {
17772             *p++ = '-';
17773         }
17774     } else if(move == backwardMostMove) {
17775         // [HGM] perhaps we should always do it like this, and forget the above?
17776         if((signed char)boards[move][EP_STATUS] >= 0) {
17777             *p++ = boards[move][EP_STATUS] + AAA;
17778             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17779         } else {
17780             *p++ = '-';
17781         }
17782     } else {
17783         *p++ = '-';
17784     }
17785     *p++ = ' ';
17786   }
17787   }
17788
17789     if(moveCounts)
17790     {   int i = 0, j=move;
17791
17792         /* [HGM] find reversible plies */
17793         if (appData.debugMode) { int k;
17794             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17795             for(k=backwardMostMove; k<=forwardMostMove; k++)
17796                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17797
17798         }
17799
17800         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17801         if( j == backwardMostMove ) i += initialRulePlies;
17802         sprintf(p, "%d ", i);
17803         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17804
17805         /* Fullmove number */
17806         sprintf(p, "%d", (move / 2) + 1);
17807     } else *--p = NULLCHAR;
17808
17809     return StrSave(buf);
17810 }
17811
17812 Boolean
17813 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17814 {
17815     int i, j, k, w=0, subst=0, shuffle=0;
17816     char *p, c;
17817     int emptycount, virgin[BOARD_FILES];
17818     ChessSquare piece;
17819
17820     p = fen;
17821
17822     /* Piece placement data */
17823     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17824         j = 0;
17825         for (;;) {
17826             if (*p == '/' || *p == ' ' || *p == '[' ) {
17827                 if(j > w) w = j;
17828                 emptycount = gameInfo.boardWidth - j;
17829                 while (emptycount--)
17830                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17831                 if (*p == '/') p++;
17832                 else if(autoSize) { // we stumbled unexpectedly into end of board
17833                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17834                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17835                     }
17836                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17837                 }
17838                 break;
17839 #if(BOARD_FILES >= 10)*0
17840             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17841                 p++; emptycount=10;
17842                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17843                 while (emptycount--)
17844                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17845 #endif
17846             } else if (*p == '*') {
17847                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17848             } else if (isdigit(*p)) {
17849                 emptycount = *p++ - '0';
17850                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17851                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17852                 while (emptycount--)
17853                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17854             } else if (*p == '<') {
17855                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17856                 else if (i != 0 || !shuffle) return FALSE;
17857                 p++;
17858             } else if (shuffle && *p == '>') {
17859                 p++; // for now ignore closing shuffle range, and assume rank-end
17860             } else if (*p == '?') {
17861                 if (j >= gameInfo.boardWidth) return FALSE;
17862                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17863                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17864             } else if (*p == '+' || isalpha(*p)) {
17865                 if (j >= gameInfo.boardWidth) return FALSE;
17866                 if(*p=='+') {
17867                     piece = CharToPiece(*++p);
17868                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17869                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17870                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17871                 } else piece = CharToPiece(*p++);
17872
17873                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17874                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17875                     piece = (ChessSquare) (PROMOTED piece);
17876                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17877                     p++;
17878                 }
17879                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17880             } else {
17881                 return FALSE;
17882             }
17883         }
17884     }
17885     while (*p == '/' || *p == ' ') p++;
17886
17887     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17888
17889     /* [HGM] by default clear Crazyhouse holdings, if present */
17890     if(gameInfo.holdingsWidth) {
17891        for(i=0; i<BOARD_HEIGHT; i++) {
17892            board[i][0]             = EmptySquare; /* black holdings */
17893            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17894            board[i][1]             = (ChessSquare) 0; /* black counts */
17895            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17896        }
17897     }
17898
17899     /* [HGM] look for Crazyhouse holdings here */
17900     while(*p==' ') p++;
17901     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17902         int swap=0, wcnt=0, bcnt=0;
17903         if(*p == '[') p++;
17904         if(*p == '<') swap++, p++;
17905         if(*p == '-' ) p++; /* empty holdings */ else {
17906             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17907             /* if we would allow FEN reading to set board size, we would   */
17908             /* have to add holdings and shift the board read so far here   */
17909             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17910                 p++;
17911                 if((int) piece >= (int) BlackPawn ) {
17912                     i = (int)piece - (int)BlackPawn;
17913                     i = PieceToNumber((ChessSquare)i);
17914                     if( i >= gameInfo.holdingsSize ) return FALSE;
17915                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17916                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17917                     bcnt++;
17918                 } else {
17919                     i = (int)piece - (int)WhitePawn;
17920                     i = PieceToNumber((ChessSquare)i);
17921                     if( i >= gameInfo.holdingsSize ) return FALSE;
17922                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17923                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17924                     wcnt++;
17925                 }
17926             }
17927             if(subst) { // substitute back-rank question marks by holdings pieces
17928                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17929                     int k, m, n = bcnt + 1;
17930                     if(board[0][j] == ClearBoard) {
17931                         if(!wcnt) return FALSE;
17932                         n = rand() % wcnt;
17933                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17934                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17935                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17936                             break;
17937                         }
17938                     }
17939                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17940                         if(!bcnt) return FALSE;
17941                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17942                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17943                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17944                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17945                             break;
17946                         }
17947                     }
17948                 }
17949                 subst = 0;
17950             }
17951         }
17952         if(*p == ']') p++;
17953     }
17954
17955     if(subst) return FALSE; // substitution requested, but no holdings
17956
17957     while(*p == ' ') p++;
17958
17959     /* Active color */
17960     c = *p++;
17961     if(appData.colorNickNames) {
17962       if( c == appData.colorNickNames[0] ) c = 'w'; else
17963       if( c == appData.colorNickNames[1] ) c = 'b';
17964     }
17965     switch (c) {
17966       case 'w':
17967         *blackPlaysFirst = FALSE;
17968         break;
17969       case 'b':
17970         *blackPlaysFirst = TRUE;
17971         break;
17972       default:
17973         return FALSE;
17974     }
17975
17976     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17977     /* return the extra info in global variiables             */
17978
17979     /* set defaults in case FEN is incomplete */
17980     board[EP_STATUS] = EP_UNKNOWN;
17981     for(i=0; i<nrCastlingRights; i++ ) {
17982         board[CASTLING][i] =
17983             appData.fischerCastling ? NoRights : initialRights[i];
17984     }   /* assume possible unless obviously impossible */
17985     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17986     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17987     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17988                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17989     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17990     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17991     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17992                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17993     FENrulePlies = 0;
17994
17995     while(*p==' ') p++;
17996     if(nrCastlingRights) {
17997       int fischer = 0;
17998       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17999       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18000           /* castling indicator present, so default becomes no castlings */
18001           for(i=0; i<nrCastlingRights; i++ ) {
18002                  board[CASTLING][i] = NoRights;
18003           }
18004       }
18005       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18006              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18007              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18008              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18009         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18010
18011         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18012             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18013             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18014         }
18015         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18016             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18017         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18018                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18019         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18020                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18021         switch(c) {
18022           case'K':
18023               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18024               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18025               board[CASTLING][2] = whiteKingFile;
18026               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18027               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18028               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18029               break;
18030           case'Q':
18031               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18032               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18033               board[CASTLING][2] = whiteKingFile;
18034               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18035               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18036               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18037               break;
18038           case'k':
18039               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18040               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18041               board[CASTLING][5] = blackKingFile;
18042               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18043               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18044               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18045               break;
18046           case'q':
18047               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18048               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18049               board[CASTLING][5] = blackKingFile;
18050               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18051               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18052               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18053           case '-':
18054               break;
18055           default: /* FRC castlings */
18056               if(c >= 'a') { /* black rights */
18057                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18058                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18059                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18060                   if(i == BOARD_RGHT) break;
18061                   board[CASTLING][5] = i;
18062                   c -= AAA;
18063                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18064                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18065                   if(c > i)
18066                       board[CASTLING][3] = c;
18067                   else
18068                       board[CASTLING][4] = c;
18069               } else { /* white rights */
18070                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18071                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18072                     if(board[0][i] == WhiteKing) break;
18073                   if(i == BOARD_RGHT) break;
18074                   board[CASTLING][2] = i;
18075                   c -= AAA - 'a' + 'A';
18076                   if(board[0][c] >= WhiteKing) break;
18077                   if(c > i)
18078                       board[CASTLING][0] = c;
18079                   else
18080                       board[CASTLING][1] = c;
18081               }
18082         }
18083       }
18084       for(i=0; i<nrCastlingRights; i++)
18085         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18086       if(gameInfo.variant == VariantSChess)
18087         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18088       if(fischer && shuffle) appData.fischerCastling = TRUE;
18089     if (appData.debugMode) {
18090         fprintf(debugFP, "FEN castling rights:");
18091         for(i=0; i<nrCastlingRights; i++)
18092         fprintf(debugFP, " %d", board[CASTLING][i]);
18093         fprintf(debugFP, "\n");
18094     }
18095
18096       while(*p==' ') p++;
18097     }
18098
18099     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18100
18101     /* read e.p. field in games that know e.p. capture */
18102     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18103        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18104        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18105       if(*p=='-') {
18106         p++; board[EP_STATUS] = EP_NONE;
18107       } else {
18108          char c = *p++ - AAA;
18109
18110          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18111          if(*p >= '0' && *p <='9') p++;
18112          board[EP_STATUS] = c;
18113       }
18114     }
18115
18116
18117     if(sscanf(p, "%d", &i) == 1) {
18118         FENrulePlies = i; /* 50-move ply counter */
18119         /* (The move number is still ignored)    */
18120     }
18121
18122     return TRUE;
18123 }
18124
18125 void
18126 EditPositionPasteFEN (char *fen)
18127 {
18128   if (fen != NULL) {
18129     Board initial_position;
18130
18131     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18132       DisplayError(_("Bad FEN position in clipboard"), 0);
18133       return ;
18134     } else {
18135       int savedBlackPlaysFirst = blackPlaysFirst;
18136       EditPositionEvent();
18137       blackPlaysFirst = savedBlackPlaysFirst;
18138       CopyBoard(boards[0], initial_position);
18139       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18140       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18141       DisplayBothClocks();
18142       DrawPosition(FALSE, boards[currentMove]);
18143     }
18144   }
18145 }
18146
18147 static char cseq[12] = "\\   ";
18148
18149 Boolean
18150 set_cont_sequence (char *new_seq)
18151 {
18152     int len;
18153     Boolean ret;
18154
18155     // handle bad attempts to set the sequence
18156         if (!new_seq)
18157                 return 0; // acceptable error - no debug
18158
18159     len = strlen(new_seq);
18160     ret = (len > 0) && (len < sizeof(cseq));
18161     if (ret)
18162       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18163     else if (appData.debugMode)
18164       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18165     return ret;
18166 }
18167
18168 /*
18169     reformat a source message so words don't cross the width boundary.  internal
18170     newlines are not removed.  returns the wrapped size (no null character unless
18171     included in source message).  If dest is NULL, only calculate the size required
18172     for the dest buffer.  lp argument indicats line position upon entry, and it's
18173     passed back upon exit.
18174 */
18175 int
18176 wrap (char *dest, char *src, int count, int width, int *lp)
18177 {
18178     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18179
18180     cseq_len = strlen(cseq);
18181     old_line = line = *lp;
18182     ansi = len = clen = 0;
18183
18184     for (i=0; i < count; i++)
18185     {
18186         if (src[i] == '\033')
18187             ansi = 1;
18188
18189         // if we hit the width, back up
18190         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18191         {
18192             // store i & len in case the word is too long
18193             old_i = i, old_len = len;
18194
18195             // find the end of the last word
18196             while (i && src[i] != ' ' && src[i] != '\n')
18197             {
18198                 i--;
18199                 len--;
18200             }
18201
18202             // word too long?  restore i & len before splitting it
18203             if ((old_i-i+clen) >= width)
18204             {
18205                 i = old_i;
18206                 len = old_len;
18207             }
18208
18209             // extra space?
18210             if (i && src[i-1] == ' ')
18211                 len--;
18212
18213             if (src[i] != ' ' && src[i] != '\n')
18214             {
18215                 i--;
18216                 if (len)
18217                     len--;
18218             }
18219
18220             // now append the newline and continuation sequence
18221             if (dest)
18222                 dest[len] = '\n';
18223             len++;
18224             if (dest)
18225                 strncpy(dest+len, cseq, cseq_len);
18226             len += cseq_len;
18227             line = cseq_len;
18228             clen = cseq_len;
18229             continue;
18230         }
18231
18232         if (dest)
18233             dest[len] = src[i];
18234         len++;
18235         if (!ansi)
18236             line++;
18237         if (src[i] == '\n')
18238             line = 0;
18239         if (src[i] == 'm')
18240             ansi = 0;
18241     }
18242     if (dest && appData.debugMode)
18243     {
18244         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18245             count, width, line, len, *lp);
18246         show_bytes(debugFP, src, count);
18247         fprintf(debugFP, "\ndest: ");
18248         show_bytes(debugFP, dest, len);
18249         fprintf(debugFP, "\n");
18250     }
18251     *lp = dest ? line : old_line;
18252
18253     return len;
18254 }
18255
18256 // [HGM] vari: routines for shelving variations
18257 Boolean modeRestore = FALSE;
18258
18259 void
18260 PushInner (int firstMove, int lastMove)
18261 {
18262         int i, j, nrMoves = lastMove - firstMove;
18263
18264         // push current tail of game on stack
18265         savedResult[storedGames] = gameInfo.result;
18266         savedDetails[storedGames] = gameInfo.resultDetails;
18267         gameInfo.resultDetails = NULL;
18268         savedFirst[storedGames] = firstMove;
18269         savedLast [storedGames] = lastMove;
18270         savedFramePtr[storedGames] = framePtr;
18271         framePtr -= nrMoves; // reserve space for the boards
18272         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18273             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18274             for(j=0; j<MOVE_LEN; j++)
18275                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18276             for(j=0; j<2*MOVE_LEN; j++)
18277                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18278             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18279             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18280             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18281             pvInfoList[firstMove+i-1].depth = 0;
18282             commentList[framePtr+i] = commentList[firstMove+i];
18283             commentList[firstMove+i] = NULL;
18284         }
18285
18286         storedGames++;
18287         forwardMostMove = firstMove; // truncate game so we can start variation
18288 }
18289
18290 void
18291 PushTail (int firstMove, int lastMove)
18292 {
18293         if(appData.icsActive) { // only in local mode
18294                 forwardMostMove = currentMove; // mimic old ICS behavior
18295                 return;
18296         }
18297         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18298
18299         PushInner(firstMove, lastMove);
18300         if(storedGames == 1) GreyRevert(FALSE);
18301         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18302 }
18303
18304 void
18305 PopInner (Boolean annotate)
18306 {
18307         int i, j, nrMoves;
18308         char buf[8000], moveBuf[20];
18309
18310         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18311         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18312         nrMoves = savedLast[storedGames] - currentMove;
18313         if(annotate) {
18314                 int cnt = 10;
18315                 if(!WhiteOnMove(currentMove))
18316                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18317                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18318                 for(i=currentMove; i<forwardMostMove; i++) {
18319                         if(WhiteOnMove(i))
18320                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18321                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18322                         strcat(buf, moveBuf);
18323                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18324                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18325                 }
18326                 strcat(buf, ")");
18327         }
18328         for(i=1; i<=nrMoves; i++) { // copy last variation back
18329             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18330             for(j=0; j<MOVE_LEN; j++)
18331                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18332             for(j=0; j<2*MOVE_LEN; j++)
18333                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18334             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18335             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18336             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18337             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18338             commentList[currentMove+i] = commentList[framePtr+i];
18339             commentList[framePtr+i] = NULL;
18340         }
18341         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18342         framePtr = savedFramePtr[storedGames];
18343         gameInfo.result = savedResult[storedGames];
18344         if(gameInfo.resultDetails != NULL) {
18345             free(gameInfo.resultDetails);
18346       }
18347         gameInfo.resultDetails = savedDetails[storedGames];
18348         forwardMostMove = currentMove + nrMoves;
18349 }
18350
18351 Boolean
18352 PopTail (Boolean annotate)
18353 {
18354         if(appData.icsActive) return FALSE; // only in local mode
18355         if(!storedGames) return FALSE; // sanity
18356         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18357
18358         PopInner(annotate);
18359         if(currentMove < forwardMostMove) ForwardEvent(); else
18360         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18361
18362         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18363         return TRUE;
18364 }
18365
18366 void
18367 CleanupTail ()
18368 {       // remove all shelved variations
18369         int i;
18370         for(i=0; i<storedGames; i++) {
18371             if(savedDetails[i])
18372                 free(savedDetails[i]);
18373             savedDetails[i] = NULL;
18374         }
18375         for(i=framePtr; i<MAX_MOVES; i++) {
18376                 if(commentList[i]) free(commentList[i]);
18377                 commentList[i] = NULL;
18378         }
18379         framePtr = MAX_MOVES-1;
18380         storedGames = 0;
18381 }
18382
18383 void
18384 LoadVariation (int index, char *text)
18385 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18386         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18387         int level = 0, move;
18388
18389         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18390         // first find outermost bracketing variation
18391         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18392             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18393                 if(*p == '{') wait = '}'; else
18394                 if(*p == '[') wait = ']'; else
18395                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18396                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18397             }
18398             if(*p == wait) wait = NULLCHAR; // closing ]} found
18399             p++;
18400         }
18401         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18402         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18403         end[1] = NULLCHAR; // clip off comment beyond variation
18404         ToNrEvent(currentMove-1);
18405         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18406         // kludge: use ParsePV() to append variation to game
18407         move = currentMove;
18408         ParsePV(start, TRUE, TRUE);
18409         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18410         ClearPremoveHighlights();
18411         CommentPopDown();
18412         ToNrEvent(currentMove+1);
18413 }
18414
18415 void
18416 LoadTheme ()
18417 {
18418     char *p, *q, buf[MSG_SIZ];
18419     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18420         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18421         ParseArgsFromString(buf);
18422         ActivateTheme(TRUE); // also redo colors
18423         return;
18424     }
18425     p = nickName;
18426     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18427     {
18428         int len;
18429         q = appData.themeNames;
18430         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18431       if(appData.useBitmaps) {
18432         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18433                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18434                 appData.liteBackTextureMode,
18435                 appData.darkBackTextureMode );
18436       } else {
18437         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18438                 Col2Text(2),   // lightSquareColor
18439                 Col2Text(3) ); // darkSquareColor
18440       }
18441       if(appData.useBorder) {
18442         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18443                 appData.border);
18444       } else {
18445         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18446       }
18447       if(appData.useFont) {
18448         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18449                 appData.renderPiecesWithFont,
18450                 appData.fontToPieceTable,
18451                 Col2Text(9),    // appData.fontBackColorWhite
18452                 Col2Text(10) ); // appData.fontForeColorBlack
18453       } else {
18454         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18455                 appData.pieceDirectory);
18456         if(!appData.pieceDirectory[0])
18457           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18458                 Col2Text(0),   // whitePieceColor
18459                 Col2Text(1) ); // blackPieceColor
18460       }
18461       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18462                 Col2Text(4),   // highlightSquareColor
18463                 Col2Text(5) ); // premoveHighlightColor
18464         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18465         if(insert != q) insert[-1] = NULLCHAR;
18466         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18467         if(q)   free(q);
18468     }
18469     ActivateTheme(FALSE);
18470 }