Implement engine-defined pieces
[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 static Boolean pieceDefs;
299
300 /* States for ics_getting_history */
301 #define H_FALSE 0
302 #define H_REQUESTED 1
303 #define H_GOT_REQ_HEADER 2
304 #define H_GOT_UNREQ_HEADER 3
305 #define H_GETTING_MOVES 4
306 #define H_GOT_UNWANTED_HEADER 5
307
308 /* whosays values for GameEnds */
309 #define GE_ICS 0
310 #define GE_ENGINE 1
311 #define GE_PLAYER 2
312 #define GE_FILE 3
313 #define GE_XBOARD 4
314 #define GE_ENGINE1 5
315 #define GE_ENGINE2 6
316
317 /* Maximum number of games in a cmail message */
318 #define CMAIL_MAX_GAMES 20
319
320 /* Different types of move when calling RegisterMove */
321 #define CMAIL_MOVE   0
322 #define CMAIL_RESIGN 1
323 #define CMAIL_DRAW   2
324 #define CMAIL_ACCEPT 3
325
326 /* Different types of result to remember for each game */
327 #define CMAIL_NOT_RESULT 0
328 #define CMAIL_OLD_RESULT 1
329 #define CMAIL_NEW_RESULT 2
330
331 /* Telnet protocol constants */
332 #define TN_WILL 0373
333 #define TN_WONT 0374
334 #define TN_DO   0375
335 #define TN_DONT 0376
336 #define TN_IAC  0377
337 #define TN_ECHO 0001
338 #define TN_SGA  0003
339 #define TN_PORT 23
340
341 char*
342 safeStrCpy (char *dst, const char *src, size_t count)
343 { // [HGM] made safe
344   int i;
345   assert( dst != NULL );
346   assert( src != NULL );
347   assert( count > 0 );
348
349   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
350   if(  i == count && dst[count-1] != NULLCHAR)
351     {
352       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
353       if(appData.debugMode)
354         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
355     }
356
357   return dst;
358 }
359
360 /* Some compiler can't cast u64 to double
361  * This function do the job for us:
362
363  * We use the highest bit for cast, this only
364  * works if the highest bit is not
365  * in use (This should not happen)
366  *
367  * We used this for all compiler
368  */
369 double
370 u64ToDouble (u64 value)
371 {
372   double r;
373   u64 tmp = value & u64Const(0x7fffffffffffffff);
374   r = (double)(s64)tmp;
375   if (value & u64Const(0x8000000000000000))
376        r +=  9.2233720368547758080e18; /* 2^63 */
377  return r;
378 }
379
380 /* Fake up flags for now, as we aren't keeping track of castling
381    availability yet. [HGM] Change of logic: the flag now only
382    indicates the type of castlings allowed by the rule of the game.
383    The actual rights themselves are maintained in the array
384    castlingRights, as part of the game history, and are not probed
385    by this function.
386  */
387 int
388 PosFlags (index)
389 {
390   int flags = F_ALL_CASTLE_OK;
391   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
392   switch (gameInfo.variant) {
393   case VariantSuicide:
394     flags &= ~F_ALL_CASTLE_OK;
395   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
396     flags |= F_IGNORE_CHECK;
397   case VariantLosers:
398     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399     break;
400   case VariantAtomic:
401     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402     break;
403   case VariantKriegspiel:
404     flags |= F_KRIEGSPIEL_CAPTURE;
405     break;
406   case VariantCapaRandom:
407   case VariantFischeRandom:
408     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
409   case VariantNoCastle:
410   case VariantShatranj:
411   case VariantCourier:
412   case VariantMakruk:
413   case VariantASEAN:
414   case VariantGrand:
415     flags &= ~F_ALL_CASTLE_OK;
416     break;
417   case VariantChu:
418   case VariantChuChess:
419   case VariantLion:
420     flags |= F_NULL_MOVE;
421     break;
422   default:
423     break;
424   }
425   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
426   return flags;
427 }
428
429 FILE *gameFileFP, *debugFP, *serverFP;
430 char *currentDebugFile; // [HGM] debug split: to remember name
431
432 /*
433     [AS] Note: sometimes, the sscanf() function is used to parse the input
434     into a fixed-size buffer. Because of this, we must be prepared to
435     receive strings as long as the size of the input buffer, which is currently
436     set to 4K for Windows and 8K for the rest.
437     So, we must either allocate sufficiently large buffers here, or
438     reduce the size of the input buffer in the input reading part.
439 */
440
441 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
442 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
443 char thinkOutput1[MSG_SIZ*10];
444
445 ChessProgramState first, second, pairing;
446
447 /* premove variables */
448 int premoveToX = 0;
449 int premoveToY = 0;
450 int premoveFromX = 0;
451 int premoveFromY = 0;
452 int premovePromoChar = 0;
453 int gotPremove = 0;
454 Boolean alarmSounded;
455 /* end premove variables */
456
457 char *ics_prefix = "$";
458 enum ICS_TYPE ics_type = ICS_GENERIC;
459
460 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
461 int pauseExamForwardMostMove = 0;
462 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
463 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
464 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
465 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
466 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
467 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
468 int whiteFlag = FALSE, blackFlag = FALSE;
469 int userOfferedDraw = FALSE;
470 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
471 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
472 int cmailMoveType[CMAIL_MAX_GAMES];
473 long ics_clock_paused = 0;
474 ProcRef icsPR = NoProc, cmailPR = NoProc;
475 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
476 GameMode gameMode = BeginningOfGame;
477 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
478 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
479 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
480 int hiddenThinkOutputState = 0; /* [AS] */
481 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
482 int adjudicateLossPlies = 6;
483 char white_holding[64], black_holding[64];
484 TimeMark lastNodeCountTime;
485 long lastNodeCount=0;
486 int shiftKey, controlKey; // [HGM] set by mouse handler
487
488 int have_sent_ICS_logon = 0;
489 int movesPerSession;
490 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
491 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
492 Boolean adjustedClock;
493 long timeControl_2; /* [AS] Allow separate time controls */
494 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
495 long timeRemaining[2][MAX_MOVES];
496 int matchGame = 0, nextGame = 0, roundNr = 0;
497 Boolean waitingForGame = FALSE, startingEngine = FALSE;
498 TimeMark programStartTime, pauseStart;
499 char ics_handle[MSG_SIZ];
500 int have_set_title = 0;
501
502 /* animateTraining preserves the state of appData.animate
503  * when Training mode is activated. This allows the
504  * response to be animated when appData.animate == TRUE and
505  * appData.animateDragging == TRUE.
506  */
507 Boolean animateTraining;
508
509 GameInfo gameInfo;
510
511 AppData appData;
512
513 Board boards[MAX_MOVES];
514 /* [HGM] Following 7 needed for accurate legality tests: */
515 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
516 signed char  initialRights[BOARD_FILES];
517 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
518 int   initialRulePlies, FENrulePlies;
519 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
520 int loadFlag = 0;
521 Boolean shuffleOpenings;
522 int mute; // mute all sounds
523
524 // [HGM] vari: next 12 to save and restore variations
525 #define MAX_VARIATIONS 10
526 int framePtr = MAX_MOVES-1; // points to free stack entry
527 int storedGames = 0;
528 int savedFirst[MAX_VARIATIONS];
529 int savedLast[MAX_VARIATIONS];
530 int savedFramePtr[MAX_VARIATIONS];
531 char *savedDetails[MAX_VARIATIONS];
532 ChessMove savedResult[MAX_VARIATIONS];
533
534 void PushTail P((int firstMove, int lastMove));
535 Boolean PopTail P((Boolean annotate));
536 void PushInner P((int firstMove, int lastMove));
537 void PopInner P((Boolean annotate));
538 void CleanupTail P((void));
539
540 ChessSquare  FIDEArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544         BlackKing, BlackBishop, BlackKnight, BlackRook }
545 };
546
547 ChessSquare twoKingsArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
549         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
551         BlackKing, BlackKing, BlackKnight, BlackRook }
552 };
553
554 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
556         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
557     { BlackRook, BlackMan, BlackBishop, BlackQueen,
558         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
559 };
560
561 ChessSquare SpartanArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
565         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
566 };
567
568 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
569     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
570         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
571     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
572         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
573 };
574
575 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
577         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
579         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
584         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackMan, BlackFerz,
586         BlackKing, BlackMan, BlackKnight, BlackRook }
587 };
588
589 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
590     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
591         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackMan, BlackFerz,
593         BlackKing, BlackMan, BlackKnight, BlackRook }
594 };
595
596 ChessSquare  lionArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
598         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackLion, BlackBishop, BlackQueen,
600         BlackKing, BlackBishop, BlackKnight, BlackRook }
601 };
602
603
604 #if (BOARD_FILES>=10)
605 ChessSquare ShogiArray[2][BOARD_FILES] = {
606     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
607         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
608     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
609         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
610 };
611
612 ChessSquare XiangqiArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
614         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
616         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
617 };
618
619 ChessSquare CapablancaArray[2][BOARD_FILES] = {
620     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
621         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
622     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
623         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
624 };
625
626 ChessSquare GreatArray[2][BOARD_FILES] = {
627     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
628         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
629     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
630         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
631 };
632
633 ChessSquare JanusArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
635         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
636     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
637         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
638 };
639
640 ChessSquare GrandArray[2][BOARD_FILES] = {
641     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
642         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
643     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
644         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
645 };
646
647 ChessSquare ChuChessArray[2][BOARD_FILES] = {
648     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
649         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
650     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
651         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
652 };
653
654 #ifdef GOTHIC
655 ChessSquare GothicArray[2][BOARD_FILES] = {
656     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
657         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
658     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
659         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
660 };
661 #else // !GOTHIC
662 #define GothicArray CapablancaArray
663 #endif // !GOTHIC
664
665 #ifdef FALCON
666 ChessSquare FalconArray[2][BOARD_FILES] = {
667     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
668         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
669     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
670         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
671 };
672 #else // !FALCON
673 #define FalconArray CapablancaArray
674 #endif // !FALCON
675
676 #else // !(BOARD_FILES>=10)
677 #define XiangqiPosition FIDEArray
678 #define CapablancaArray FIDEArray
679 #define GothicArray FIDEArray
680 #define GreatArray FIDEArray
681 #endif // !(BOARD_FILES>=10)
682
683 #if (BOARD_FILES>=12)
684 ChessSquare CourierArray[2][BOARD_FILES] = {
685     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
686         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
687     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
688         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
689 };
690 ChessSquare ChuArray[6][BOARD_FILES] = {
691     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
692       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
693     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
694       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
695     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
696       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
697     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
698       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
699     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
700       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
701     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
702       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
703 };
704 #else // !(BOARD_FILES>=12)
705 #define CourierArray CapablancaArray
706 #define ChuArray CapablancaArray
707 #endif // !(BOARD_FILES>=12)
708
709
710 Board initialPosition;
711
712
713 /* Convert str to a rating. Checks for special cases of "----",
714
715    "++++", etc. Also strips ()'s */
716 int
717 string_to_rating (char *str)
718 {
719   while(*str && !isdigit(*str)) ++str;
720   if (!*str)
721     return 0;   /* One of the special "no rating" cases */
722   else
723     return atoi(str);
724 }
725
726 void
727 ClearProgramStats ()
728 {
729     /* Init programStats */
730     programStats.movelist[0] = 0;
731     programStats.depth = 0;
732     programStats.nr_moves = 0;
733     programStats.moves_left = 0;
734     programStats.nodes = 0;
735     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
736     programStats.score = 0;
737     programStats.got_only_move = 0;
738     programStats.got_fail = 0;
739     programStats.line_is_book = 0;
740 }
741
742 void
743 CommonEngineInit ()
744 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
745     if (appData.firstPlaysBlack) {
746         first.twoMachinesColor = "black\n";
747         second.twoMachinesColor = "white\n";
748     } else {
749         first.twoMachinesColor = "white\n";
750         second.twoMachinesColor = "black\n";
751     }
752
753     first.other = &second;
754     second.other = &first;
755
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = appData.timeOdds[0];
759             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
760         }
761         first.timeOdds  = appData.timeOdds[0]/norm;
762         second.timeOdds = appData.timeOdds[1]/norm;
763     }
764
765     if(programVersion) free(programVersion);
766     if (appData.noChessProgram) {
767         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
768         sprintf(programVersion, "%s", PACKAGE_STRING);
769     } else {
770       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
771       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
772       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
773     }
774 }
775
776 void
777 UnloadEngine (ChessProgramState *cps)
778 {
779         /* Kill off first chess program */
780         if (cps->isr != NULL)
781           RemoveInputSource(cps->isr);
782         cps->isr = NULL;
783
784         if (cps->pr != NoProc) {
785             ExitAnalyzeMode();
786             DoSleep( appData.delayBeforeQuit );
787             SendToProgram("quit\n", cps);
788             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
789         }
790         cps->pr = NoProc;
791         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
792 }
793
794 void
795 ClearOptions (ChessProgramState *cps)
796 {
797     int i;
798     cps->nrOptions = cps->comboCnt = 0;
799     for(i=0; i<MAX_OPTIONS; i++) {
800         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
801         cps->option[i].textValue = 0;
802     }
803 }
804
805 char *engineNames[] = {
806   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
807      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 N_("first"),
809   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("second")
812 };
813
814 void
815 InitEngine (ChessProgramState *cps, int n)
816 {   // [HGM] all engine initialiation put in a function that does one engine
817
818     ClearOptions(cps);
819
820     cps->which = engineNames[n];
821     cps->maybeThinking = FALSE;
822     cps->pr = NoProc;
823     cps->isr = NULL;
824     cps->sendTime = 2;
825     cps->sendDrawOffers = 1;
826
827     cps->program = appData.chessProgram[n];
828     cps->host = appData.host[n];
829     cps->dir = appData.directory[n];
830     cps->initString = appData.engInitString[n];
831     cps->computerString = appData.computerString[n];
832     cps->useSigint  = TRUE;
833     cps->useSigterm = TRUE;
834     cps->reuse = appData.reuse[n];
835     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
836     cps->useSetboard = FALSE;
837     cps->useSAN = FALSE;
838     cps->usePing = FALSE;
839     cps->lastPing = 0;
840     cps->lastPong = 0;
841     cps->usePlayother = FALSE;
842     cps->useColors = TRUE;
843     cps->useUsermove = FALSE;
844     cps->sendICS = FALSE;
845     cps->sendName = appData.icsActive;
846     cps->sdKludge = FALSE;
847     cps->stKludge = FALSE;
848     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
849     TidyProgramName(cps->program, cps->host, cps->tidy);
850     cps->matchWins = 0;
851     ASSIGN(cps->variants, appData.variant);
852     cps->analysisSupport = 2; /* detect */
853     cps->analyzing = FALSE;
854     cps->initDone = FALSE;
855     cps->reload = FALSE;
856     cps->pseudo = appData.pseudo[n];
857
858     /* New features added by Tord: */
859     cps->useFEN960 = FALSE;
860     cps->useOOCastle = TRUE;
861     /* End of new features added by Tord. */
862     cps->fenOverride  = appData.fenOverride[n];
863
864     /* [HGM] time odds: set factor for each machine */
865     cps->timeOdds  = appData.timeOdds[n];
866
867     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
868     cps->accumulateTC = appData.accumulateTC[n];
869     cps->maxNrOfSessions = 1;
870
871     /* [HGM] debug */
872     cps->debug = FALSE;
873
874     cps->drawDepth = appData.drawDepth[n];
875     cps->supportsNPS = UNKNOWN;
876     cps->memSize = FALSE;
877     cps->maxCores = FALSE;
878     ASSIGN(cps->egtFormats, "");
879
880     /* [HGM] options */
881     cps->optionSettings  = appData.engOptions[n];
882
883     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
884     cps->isUCI = appData.isUCI[n]; /* [AS] */
885     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
886     cps->highlight = 0;
887
888     if (appData.protocolVersion[n] > PROTOVER
889         || appData.protocolVersion[n] < 1)
890       {
891         char buf[MSG_SIZ];
892         int len;
893
894         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
895                        appData.protocolVersion[n]);
896         if( (len >= MSG_SIZ) && appData.debugMode )
897           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
898
899         DisplayFatalError(buf, 0, 2);
900       }
901     else
902       {
903         cps->protocolVersion = appData.protocolVersion[n];
904       }
905
906     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
907     ParseFeatures(appData.featureDefaults, cps);
908 }
909
910 ChessProgramState *savCps;
911
912 GameMode oldMode;
913
914 void
915 LoadEngine ()
916 {
917     int i;
918     if(WaitForEngine(savCps, LoadEngine)) return;
919     CommonEngineInit(); // recalculate time odds
920     if(gameInfo.variant != StringToVariant(appData.variant)) {
921         // we changed variant when loading the engine; this forces us to reset
922         Reset(TRUE, savCps != &first);
923         oldMode = BeginningOfGame; // to prevent restoring old mode
924     }
925     InitChessProgram(savCps, FALSE);
926     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
927     DisplayMessage("", "");
928     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
929     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
930     ThawUI();
931     SetGNUMode();
932     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
933 }
934
935 void
936 ReplaceEngine (ChessProgramState *cps, int n)
937 {
938     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
939     keepInfo = 1;
940     if(oldMode != BeginningOfGame) EditGameEvent();
941     keepInfo = 0;
942     UnloadEngine(cps);
943     appData.noChessProgram = FALSE;
944     appData.clockMode = TRUE;
945     InitEngine(cps, n);
946     UpdateLogos(TRUE);
947     if(n) return; // only startup first engine immediately; second can wait
948     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
949     LoadEngine();
950 }
951
952 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
953 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
954
955 static char resetOptions[] =
956         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
957         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
958         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
959         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
960
961 void
962 FloatToFront(char **list, char *engineLine)
963 {
964     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
965     int i=0;
966     if(appData.recentEngines <= 0) return;
967     TidyProgramName(engineLine, "localhost", tidy+1);
968     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
969     strncpy(buf+1, *list, MSG_SIZ-50);
970     if(p = strstr(buf, tidy)) { // tidy name appears in list
971         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
972         while(*p++ = *++q); // squeeze out
973     }
974     strcat(tidy, buf+1); // put list behind tidy name
975     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
976     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
977     ASSIGN(*list, tidy+1);
978 }
979
980 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
981
982 void
983 Load (ChessProgramState *cps, int i)
984 {
985     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
986     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
987         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
988         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
989         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
990         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
991         appData.firstProtocolVersion = PROTOVER;
992         ParseArgsFromString(buf);
993         SwapEngines(i);
994         ReplaceEngine(cps, i);
995         FloatToFront(&appData.recentEngineList, engineLine);
996         return;
997     }
998     p = engineName;
999     while(q = strchr(p, SLASH)) p = q+1;
1000     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1001     if(engineDir[0] != NULLCHAR) {
1002         ASSIGN(appData.directory[i], engineDir); p = engineName;
1003     } else if(p != engineName) { // derive directory from engine path, when not given
1004         p[-1] = 0;
1005         ASSIGN(appData.directory[i], engineName);
1006         p[-1] = SLASH;
1007         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1008     } else { ASSIGN(appData.directory[i], "."); }
1009     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1010     if(params[0]) {
1011         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1012         snprintf(command, MSG_SIZ, "%s %s", p, params);
1013         p = command;
1014     }
1015     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1016     ASSIGN(appData.chessProgram[i], p);
1017     appData.isUCI[i] = isUCI;
1018     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1019     appData.hasOwnBookUCI[i] = hasBook;
1020     if(!nickName[0]) useNick = FALSE;
1021     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1022     if(addToList) {
1023         int len;
1024         char quote;
1025         q = firstChessProgramNames;
1026         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1027         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1028         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1029                         quote, p, quote, appData.directory[i],
1030                         useNick ? " -fn \"" : "",
1031                         useNick ? nickName : "",
1032                         useNick ? "\"" : "",
1033                         v1 ? " -firstProtocolVersion 1" : "",
1034                         hasBook ? "" : " -fNoOwnBookUCI",
1035                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1036                         storeVariant ? " -variant " : "",
1037                         storeVariant ? VariantName(gameInfo.variant) : "");
1038         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1039         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1040         if(insert != q) insert[-1] = NULLCHAR;
1041         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1042         if(q)   free(q);
1043         FloatToFront(&appData.recentEngineList, buf);
1044     }
1045     ReplaceEngine(cps, i);
1046 }
1047
1048 void
1049 InitTimeControls ()
1050 {
1051     int matched, min, sec;
1052     /*
1053      * Parse timeControl resource
1054      */
1055     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1056                           appData.movesPerSession)) {
1057         char buf[MSG_SIZ];
1058         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1059         DisplayFatalError(buf, 0, 2);
1060     }
1061
1062     /*
1063      * Parse searchTime resource
1064      */
1065     if (*appData.searchTime != NULLCHAR) {
1066         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1067         if (matched == 1) {
1068             searchTime = min * 60;
1069         } else if (matched == 2) {
1070             searchTime = min * 60 + sec;
1071         } else {
1072             char buf[MSG_SIZ];
1073             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1074             DisplayFatalError(buf, 0, 2);
1075         }
1076     }
1077 }
1078
1079 void
1080 InitBackEnd1 ()
1081 {
1082
1083     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1084     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1085
1086     GetTimeMark(&programStartTime);
1087     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1088     appData.seedBase = random() + (random()<<15);
1089     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1090
1091     ClearProgramStats();
1092     programStats.ok_to_send = 1;
1093     programStats.seen_stat = 0;
1094
1095     /*
1096      * Initialize game list
1097      */
1098     ListNew(&gameList);
1099
1100
1101     /*
1102      * Internet chess server status
1103      */
1104     if (appData.icsActive) {
1105         appData.matchMode = FALSE;
1106         appData.matchGames = 0;
1107 #if ZIPPY
1108         appData.noChessProgram = !appData.zippyPlay;
1109 #else
1110         appData.zippyPlay = FALSE;
1111         appData.zippyTalk = FALSE;
1112         appData.noChessProgram = TRUE;
1113 #endif
1114         if (*appData.icsHelper != NULLCHAR) {
1115             appData.useTelnet = TRUE;
1116             appData.telnetProgram = appData.icsHelper;
1117         }
1118     } else {
1119         appData.zippyTalk = appData.zippyPlay = FALSE;
1120     }
1121
1122     /* [AS] Initialize pv info list [HGM] and game state */
1123     {
1124         int i, j;
1125
1126         for( i=0; i<=framePtr; i++ ) {
1127             pvInfoList[i].depth = -1;
1128             boards[i][EP_STATUS] = EP_NONE;
1129             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1130         }
1131     }
1132
1133     InitTimeControls();
1134
1135     /* [AS] Adjudication threshold */
1136     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1137
1138     InitEngine(&first, 0);
1139     InitEngine(&second, 1);
1140     CommonEngineInit();
1141
1142     pairing.which = "pairing"; // pairing engine
1143     pairing.pr = NoProc;
1144     pairing.isr = NULL;
1145     pairing.program = appData.pairingEngine;
1146     pairing.host = "localhost";
1147     pairing.dir = ".";
1148
1149     if (appData.icsActive) {
1150         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1151     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1152         appData.clockMode = FALSE;
1153         first.sendTime = second.sendTime = 0;
1154     }
1155
1156 #if ZIPPY
1157     /* Override some settings from environment variables, for backward
1158        compatibility.  Unfortunately it's not feasible to have the env
1159        vars just set defaults, at least in xboard.  Ugh.
1160     */
1161     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1162       ZippyInit();
1163     }
1164 #endif
1165
1166     if (!appData.icsActive) {
1167       char buf[MSG_SIZ];
1168       int len;
1169
1170       /* Check for variants that are supported only in ICS mode,
1171          or not at all.  Some that are accepted here nevertheless
1172          have bugs; see comments below.
1173       */
1174       VariantClass variant = StringToVariant(appData.variant);
1175       switch (variant) {
1176       case VariantBughouse:     /* need four players and two boards */
1177       case VariantKriegspiel:   /* need to hide pieces and move details */
1178         /* case VariantFischeRandom: (Fabien: moved below) */
1179         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1180         if( (len >= MSG_SIZ) && appData.debugMode )
1181           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1182
1183         DisplayFatalError(buf, 0, 2);
1184         return;
1185
1186       case VariantUnknown:
1187       case VariantLoadable:
1188       case Variant29:
1189       case Variant30:
1190       case Variant31:
1191       case Variant32:
1192       case Variant33:
1193       case Variant34:
1194       case Variant35:
1195       case Variant36:
1196       default:
1197         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1198         if( (len >= MSG_SIZ) && appData.debugMode )
1199           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1200
1201         DisplayFatalError(buf, 0, 2);
1202         return;
1203
1204       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1205       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1206       case VariantGothic:     /* [HGM] should work */
1207       case VariantCapablanca: /* [HGM] should work */
1208       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1209       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1210       case VariantChu:        /* [HGM] experimental */
1211       case VariantKnightmate: /* [HGM] should work */
1212       case VariantCylinder:   /* [HGM] untested */
1213       case VariantFalcon:     /* [HGM] untested */
1214       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1215                                  offboard interposition not understood */
1216       case VariantNormal:     /* definitely works! */
1217       case VariantWildCastle: /* pieces not automatically shuffled */
1218       case VariantNoCastle:   /* pieces not automatically shuffled */
1219       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1220       case VariantLosers:     /* should work except for win condition,
1221                                  and doesn't know captures are mandatory */
1222       case VariantSuicide:    /* should work except for win condition,
1223                                  and doesn't know captures are mandatory */
1224       case VariantGiveaway:   /* should work except for win condition,
1225                                  and doesn't know captures are mandatory */
1226       case VariantTwoKings:   /* should work */
1227       case VariantAtomic:     /* should work except for win condition */
1228       case Variant3Check:     /* should work except for win condition */
1229       case VariantShatranj:   /* should work except for all win conditions */
1230       case VariantMakruk:     /* should work except for draw countdown */
1231       case VariantASEAN :     /* should work except for draw countdown */
1232       case VariantBerolina:   /* might work if TestLegality is off */
1233       case VariantCapaRandom: /* should work */
1234       case VariantJanus:      /* should work */
1235       case VariantSuper:      /* experimental */
1236       case VariantGreat:      /* experimental, requires legality testing to be off */
1237       case VariantSChess:     /* S-Chess, should work */
1238       case VariantGrand:      /* should work */
1239       case VariantSpartan:    /* should work */
1240       case VariantLion:       /* should work */
1241       case VariantChuChess:   /* should work */
1242         break;
1243       }
1244     }
1245
1246 }
1247
1248 int
1249 NextIntegerFromString (char ** str, long * value)
1250 {
1251     int result = -1;
1252     char * s = *str;
1253
1254     while( *s == ' ' || *s == '\t' ) {
1255         s++;
1256     }
1257
1258     *value = 0;
1259
1260     if( *s >= '0' && *s <= '9' ) {
1261         while( *s >= '0' && *s <= '9' ) {
1262             *value = *value * 10 + (*s - '0');
1263             s++;
1264         }
1265
1266         result = 0;
1267     }
1268
1269     *str = s;
1270
1271     return result;
1272 }
1273
1274 int
1275 NextTimeControlFromString (char ** str, long * value)
1276 {
1277     long temp;
1278     int result = NextIntegerFromString( str, &temp );
1279
1280     if( result == 0 ) {
1281         *value = temp * 60; /* Minutes */
1282         if( **str == ':' ) {
1283             (*str)++;
1284             result = NextIntegerFromString( str, &temp );
1285             *value += temp; /* Seconds */
1286         }
1287     }
1288
1289     return result;
1290 }
1291
1292 int
1293 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1294 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1295     int result = -1, type = 0; long temp, temp2;
1296
1297     if(**str != ':') return -1; // old params remain in force!
1298     (*str)++;
1299     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1300     if( NextIntegerFromString( str, &temp ) ) return -1;
1301     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1302
1303     if(**str != '/') {
1304         /* time only: incremental or sudden-death time control */
1305         if(**str == '+') { /* increment follows; read it */
1306             (*str)++;
1307             if(**str == '!') type = *(*str)++; // Bronstein TC
1308             if(result = NextIntegerFromString( str, &temp2)) return -1;
1309             *inc = temp2 * 1000;
1310             if(**str == '.') { // read fraction of increment
1311                 char *start = ++(*str);
1312                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1313                 temp2 *= 1000;
1314                 while(start++ < *str) temp2 /= 10;
1315                 *inc += temp2;
1316             }
1317         } else *inc = 0;
1318         *moves = 0; *tc = temp * 1000; *incType = type;
1319         return 0;
1320     }
1321
1322     (*str)++; /* classical time control */
1323     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1324
1325     if(result == 0) {
1326         *moves = temp;
1327         *tc    = temp2 * 1000;
1328         *inc   = 0;
1329         *incType = type;
1330     }
1331     return result;
1332 }
1333
1334 int
1335 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1336 {   /* [HGM] get time to add from the multi-session time-control string */
1337     int incType, moves=1; /* kludge to force reading of first session */
1338     long time, increment;
1339     char *s = tcString;
1340
1341     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1342     do {
1343         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1344         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1345         if(movenr == -1) return time;    /* last move before new session     */
1346         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1347         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1348         if(!moves) return increment;     /* current session is incremental   */
1349         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1350     } while(movenr >= -1);               /* try again for next session       */
1351
1352     return 0; // no new time quota on this move
1353 }
1354
1355 int
1356 ParseTimeControl (char *tc, float ti, int mps)
1357 {
1358   long tc1;
1359   long tc2;
1360   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1361   int min, sec=0;
1362
1363   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1364   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1365       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1366   if(ti > 0) {
1367
1368     if(mps)
1369       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1370     else
1371       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1372   } else {
1373     if(mps)
1374       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1375     else
1376       snprintf(buf, MSG_SIZ, ":%s", mytc);
1377   }
1378   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1379
1380   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1381     return FALSE;
1382   }
1383
1384   if( *tc == '/' ) {
1385     /* Parse second time control */
1386     tc++;
1387
1388     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1389       return FALSE;
1390     }
1391
1392     if( tc2 == 0 ) {
1393       return FALSE;
1394     }
1395
1396     timeControl_2 = tc2 * 1000;
1397   }
1398   else {
1399     timeControl_2 = 0;
1400   }
1401
1402   if( tc1 == 0 ) {
1403     return FALSE;
1404   }
1405
1406   timeControl = tc1 * 1000;
1407
1408   if (ti >= 0) {
1409     timeIncrement = ti * 1000;  /* convert to ms */
1410     movesPerSession = 0;
1411   } else {
1412     timeIncrement = 0;
1413     movesPerSession = mps;
1414   }
1415   return TRUE;
1416 }
1417
1418 void
1419 InitBackEnd2 ()
1420 {
1421     if (appData.debugMode) {
1422 #    ifdef __GIT_VERSION
1423       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1424 #    else
1425       fprintf(debugFP, "Version: %s\n", programVersion);
1426 #    endif
1427     }
1428     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1429
1430     set_cont_sequence(appData.wrapContSeq);
1431     if (appData.matchGames > 0) {
1432         appData.matchMode = TRUE;
1433     } else if (appData.matchMode) {
1434         appData.matchGames = 1;
1435     }
1436     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1437         appData.matchGames = appData.sameColorGames;
1438     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1439         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1440         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1441     }
1442     Reset(TRUE, FALSE);
1443     if (appData.noChessProgram || first.protocolVersion == 1) {
1444       InitBackEnd3();
1445     } else {
1446       /* kludge: allow timeout for initial "feature" commands */
1447       FreezeUI();
1448       DisplayMessage("", _("Starting chess program"));
1449       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1450     }
1451 }
1452
1453 int
1454 CalculateIndex (int index, int gameNr)
1455 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1456     int res;
1457     if(index > 0) return index; // fixed nmber
1458     if(index == 0) return 1;
1459     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1460     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1461     return res;
1462 }
1463
1464 int
1465 LoadGameOrPosition (int gameNr)
1466 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1467     if (*appData.loadGameFile != NULLCHAR) {
1468         if (!LoadGameFromFile(appData.loadGameFile,
1469                 CalculateIndex(appData.loadGameIndex, gameNr),
1470                               appData.loadGameFile, FALSE)) {
1471             DisplayFatalError(_("Bad game file"), 0, 1);
1472             return 0;
1473         }
1474     } else if (*appData.loadPositionFile != NULLCHAR) {
1475         if (!LoadPositionFromFile(appData.loadPositionFile,
1476                 CalculateIndex(appData.loadPositionIndex, gameNr),
1477                                   appData.loadPositionFile)) {
1478             DisplayFatalError(_("Bad position file"), 0, 1);
1479             return 0;
1480         }
1481     }
1482     return 1;
1483 }
1484
1485 void
1486 ReserveGame (int gameNr, char resChar)
1487 {
1488     FILE *tf = fopen(appData.tourneyFile, "r+");
1489     char *p, *q, c, buf[MSG_SIZ];
1490     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1491     safeStrCpy(buf, lastMsg, MSG_SIZ);
1492     DisplayMessage(_("Pick new game"), "");
1493     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1494     ParseArgsFromFile(tf);
1495     p = q = appData.results;
1496     if(appData.debugMode) {
1497       char *r = appData.participants;
1498       fprintf(debugFP, "results = '%s'\n", p);
1499       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1500       fprintf(debugFP, "\n");
1501     }
1502     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1503     nextGame = q - p;
1504     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1505     safeStrCpy(q, p, strlen(p) + 2);
1506     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1507     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1508     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1509         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1510         q[nextGame] = '*';
1511     }
1512     fseek(tf, -(strlen(p)+4), SEEK_END);
1513     c = fgetc(tf);
1514     if(c != '"') // depending on DOS or Unix line endings we can be one off
1515          fseek(tf, -(strlen(p)+2), SEEK_END);
1516     else fseek(tf, -(strlen(p)+3), SEEK_END);
1517     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1518     DisplayMessage(buf, "");
1519     free(p); appData.results = q;
1520     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1521        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1522       int round = appData.defaultMatchGames * appData.tourneyType;
1523       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1524          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1525         UnloadEngine(&first);  // next game belongs to other pairing;
1526         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1527     }
1528     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1529 }
1530
1531 void
1532 MatchEvent (int mode)
1533 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1534         int dummy;
1535         if(matchMode) { // already in match mode: switch it off
1536             abortMatch = TRUE;
1537             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1538             return;
1539         }
1540 //      if(gameMode != BeginningOfGame) {
1541 //          DisplayError(_("You can only start a match from the initial position."), 0);
1542 //          return;
1543 //      }
1544         abortMatch = FALSE;
1545         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1546         /* Set up machine vs. machine match */
1547         nextGame = 0;
1548         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1549         if(appData.tourneyFile[0]) {
1550             ReserveGame(-1, 0);
1551             if(nextGame > appData.matchGames) {
1552                 char buf[MSG_SIZ];
1553                 if(strchr(appData.results, '*') == NULL) {
1554                     FILE *f;
1555                     appData.tourneyCycles++;
1556                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1557                         fclose(f);
1558                         NextTourneyGame(-1, &dummy);
1559                         ReserveGame(-1, 0);
1560                         if(nextGame <= appData.matchGames) {
1561                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1562                             matchMode = mode;
1563                             ScheduleDelayedEvent(NextMatchGame, 10000);
1564                             return;
1565                         }
1566                     }
1567                 }
1568                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1569                 DisplayError(buf, 0);
1570                 appData.tourneyFile[0] = 0;
1571                 return;
1572             }
1573         } else
1574         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1575             DisplayFatalError(_("Can't have a match with no chess programs"),
1576                               0, 2);
1577             return;
1578         }
1579         matchMode = mode;
1580         matchGame = roundNr = 1;
1581         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1582         NextMatchGame();
1583 }
1584
1585 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1586
1587 void
1588 InitBackEnd3 P((void))
1589 {
1590     GameMode initialMode;
1591     char buf[MSG_SIZ];
1592     int err, len;
1593
1594     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1595        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1596         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1597        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1598        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1599         char c, *q = first.variants, *p = strchr(q, ',');
1600         if(p) *p = NULLCHAR;
1601         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1602             int w, h, s;
1603             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1604                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1605             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1606             Reset(TRUE, FALSE);         // and re-initialize
1607         }
1608         if(p) *p = ',';
1609     }
1610
1611     InitChessProgram(&first, startedFromSetupPosition);
1612
1613     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1614         free(programVersion);
1615         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1616         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1617         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1618     }
1619
1620     if (appData.icsActive) {
1621 #ifdef WIN32
1622         /* [DM] Make a console window if needed [HGM] merged ifs */
1623         ConsoleCreate();
1624 #endif
1625         err = establish();
1626         if (err != 0)
1627           {
1628             if (*appData.icsCommPort != NULLCHAR)
1629               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1630                              appData.icsCommPort);
1631             else
1632               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1633                         appData.icsHost, appData.icsPort);
1634
1635             if( (len >= MSG_SIZ) && appData.debugMode )
1636               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1637
1638             DisplayFatalError(buf, err, 1);
1639             return;
1640         }
1641         SetICSMode();
1642         telnetISR =
1643           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1644         fromUserISR =
1645           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1646         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1647             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1648     } else if (appData.noChessProgram) {
1649         SetNCPMode();
1650     } else {
1651         SetGNUMode();
1652     }
1653
1654     if (*appData.cmailGameName != NULLCHAR) {
1655         SetCmailMode();
1656         OpenLoopback(&cmailPR);
1657         cmailISR =
1658           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1659     }
1660
1661     ThawUI();
1662     DisplayMessage("", "");
1663     if (StrCaseCmp(appData.initialMode, "") == 0) {
1664       initialMode = BeginningOfGame;
1665       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1666         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1667         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1668         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1669         ModeHighlight();
1670       }
1671     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1672       initialMode = TwoMachinesPlay;
1673     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1674       initialMode = AnalyzeFile;
1675     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1676       initialMode = AnalyzeMode;
1677     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1678       initialMode = MachinePlaysWhite;
1679     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1680       initialMode = MachinePlaysBlack;
1681     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1682       initialMode = EditGame;
1683     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1684       initialMode = EditPosition;
1685     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1686       initialMode = Training;
1687     } else {
1688       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1689       if( (len >= MSG_SIZ) && appData.debugMode )
1690         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1691
1692       DisplayFatalError(buf, 0, 2);
1693       return;
1694     }
1695
1696     if (appData.matchMode) {
1697         if(appData.tourneyFile[0]) { // start tourney from command line
1698             FILE *f;
1699             if(f = fopen(appData.tourneyFile, "r")) {
1700                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1701                 fclose(f);
1702                 appData.clockMode = TRUE;
1703                 SetGNUMode();
1704             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1705         }
1706         MatchEvent(TRUE);
1707     } else if (*appData.cmailGameName != NULLCHAR) {
1708         /* Set up cmail mode */
1709         ReloadCmailMsgEvent(TRUE);
1710     } else {
1711         /* Set up other modes */
1712         if (initialMode == AnalyzeFile) {
1713           if (*appData.loadGameFile == NULLCHAR) {
1714             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1715             return;
1716           }
1717         }
1718         if (*appData.loadGameFile != NULLCHAR) {
1719             (void) LoadGameFromFile(appData.loadGameFile,
1720                                     appData.loadGameIndex,
1721                                     appData.loadGameFile, TRUE);
1722         } else if (*appData.loadPositionFile != NULLCHAR) {
1723             (void) LoadPositionFromFile(appData.loadPositionFile,
1724                                         appData.loadPositionIndex,
1725                                         appData.loadPositionFile);
1726             /* [HGM] try to make self-starting even after FEN load */
1727             /* to allow automatic setup of fairy variants with wtm */
1728             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1729                 gameMode = BeginningOfGame;
1730                 setboardSpoiledMachineBlack = 1;
1731             }
1732             /* [HGM] loadPos: make that every new game uses the setup */
1733             /* from file as long as we do not switch variant          */
1734             if(!blackPlaysFirst) {
1735                 startedFromPositionFile = TRUE;
1736                 CopyBoard(filePosition, boards[0]);
1737             }
1738         }
1739         if (initialMode == AnalyzeMode) {
1740           if (appData.noChessProgram) {
1741             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1742             return;
1743           }
1744           if (appData.icsActive) {
1745             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1746             return;
1747           }
1748           AnalyzeModeEvent();
1749         } else if (initialMode == AnalyzeFile) {
1750           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1751           ShowThinkingEvent();
1752           AnalyzeFileEvent();
1753           AnalysisPeriodicEvent(1);
1754         } else if (initialMode == MachinePlaysWhite) {
1755           if (appData.noChessProgram) {
1756             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1757                               0, 2);
1758             return;
1759           }
1760           if (appData.icsActive) {
1761             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1762                               0, 2);
1763             return;
1764           }
1765           MachineWhiteEvent();
1766         } else if (initialMode == MachinePlaysBlack) {
1767           if (appData.noChessProgram) {
1768             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1769                               0, 2);
1770             return;
1771           }
1772           if (appData.icsActive) {
1773             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1774                               0, 2);
1775             return;
1776           }
1777           MachineBlackEvent();
1778         } else if (initialMode == TwoMachinesPlay) {
1779           if (appData.noChessProgram) {
1780             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1781                               0, 2);
1782             return;
1783           }
1784           if (appData.icsActive) {
1785             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1786                               0, 2);
1787             return;
1788           }
1789           TwoMachinesEvent();
1790         } else if (initialMode == EditGame) {
1791           EditGameEvent();
1792         } else if (initialMode == EditPosition) {
1793           EditPositionEvent();
1794         } else if (initialMode == Training) {
1795           if (*appData.loadGameFile == NULLCHAR) {
1796             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1797             return;
1798           }
1799           TrainingEvent();
1800         }
1801     }
1802 }
1803
1804 void
1805 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1806 {
1807     DisplayBook(current+1);
1808
1809     MoveHistorySet( movelist, first, last, current, pvInfoList );
1810
1811     EvalGraphSet( first, last, current, pvInfoList );
1812
1813     MakeEngineOutputTitle();
1814 }
1815
1816 /*
1817  * Establish will establish a contact to a remote host.port.
1818  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1819  *  used to talk to the host.
1820  * Returns 0 if okay, error code if not.
1821  */
1822 int
1823 establish ()
1824 {
1825     char buf[MSG_SIZ];
1826
1827     if (*appData.icsCommPort != NULLCHAR) {
1828         /* Talk to the host through a serial comm port */
1829         return OpenCommPort(appData.icsCommPort, &icsPR);
1830
1831     } else if (*appData.gateway != NULLCHAR) {
1832         if (*appData.remoteShell == NULLCHAR) {
1833             /* Use the rcmd protocol to run telnet program on a gateway host */
1834             snprintf(buf, sizeof(buf), "%s %s %s",
1835                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1836             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1837
1838         } else {
1839             /* Use the rsh program to run telnet program on a gateway host */
1840             if (*appData.remoteUser == NULLCHAR) {
1841                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1842                         appData.gateway, appData.telnetProgram,
1843                         appData.icsHost, appData.icsPort);
1844             } else {
1845                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1846                         appData.remoteShell, appData.gateway,
1847                         appData.remoteUser, appData.telnetProgram,
1848                         appData.icsHost, appData.icsPort);
1849             }
1850             return StartChildProcess(buf, "", &icsPR);
1851
1852         }
1853     } else if (appData.useTelnet) {
1854         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1855
1856     } else {
1857         /* TCP socket interface differs somewhat between
1858            Unix and NT; handle details in the front end.
1859            */
1860         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1861     }
1862 }
1863
1864 void
1865 EscapeExpand (char *p, char *q)
1866 {       // [HGM] initstring: routine to shape up string arguments
1867         while(*p++ = *q++) if(p[-1] == '\\')
1868             switch(*q++) {
1869                 case 'n': p[-1] = '\n'; break;
1870                 case 'r': p[-1] = '\r'; break;
1871                 case 't': p[-1] = '\t'; break;
1872                 case '\\': p[-1] = '\\'; break;
1873                 case 0: *p = 0; return;
1874                 default: p[-1] = q[-1]; break;
1875             }
1876 }
1877
1878 void
1879 show_bytes (FILE *fp, char *buf, int count)
1880 {
1881     while (count--) {
1882         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1883             fprintf(fp, "\\%03o", *buf & 0xff);
1884         } else {
1885             putc(*buf, fp);
1886         }
1887         buf++;
1888     }
1889     fflush(fp);
1890 }
1891
1892 /* Returns an errno value */
1893 int
1894 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1895 {
1896     char buf[8192], *p, *q, *buflim;
1897     int left, newcount, outcount;
1898
1899     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1900         *appData.gateway != NULLCHAR) {
1901         if (appData.debugMode) {
1902             fprintf(debugFP, ">ICS: ");
1903             show_bytes(debugFP, message, count);
1904             fprintf(debugFP, "\n");
1905         }
1906         return OutputToProcess(pr, message, count, outError);
1907     }
1908
1909     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1910     p = message;
1911     q = buf;
1912     left = count;
1913     newcount = 0;
1914     while (left) {
1915         if (q >= buflim) {
1916             if (appData.debugMode) {
1917                 fprintf(debugFP, ">ICS: ");
1918                 show_bytes(debugFP, buf, newcount);
1919                 fprintf(debugFP, "\n");
1920             }
1921             outcount = OutputToProcess(pr, buf, newcount, outError);
1922             if (outcount < newcount) return -1; /* to be sure */
1923             q = buf;
1924             newcount = 0;
1925         }
1926         if (*p == '\n') {
1927             *q++ = '\r';
1928             newcount++;
1929         } else if (((unsigned char) *p) == TN_IAC) {
1930             *q++ = (char) TN_IAC;
1931             newcount ++;
1932         }
1933         *q++ = *p++;
1934         newcount++;
1935         left--;
1936     }
1937     if (appData.debugMode) {
1938         fprintf(debugFP, ">ICS: ");
1939         show_bytes(debugFP, buf, newcount);
1940         fprintf(debugFP, "\n");
1941     }
1942     outcount = OutputToProcess(pr, buf, newcount, outError);
1943     if (outcount < newcount) return -1; /* to be sure */
1944     return count;
1945 }
1946
1947 void
1948 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1949 {
1950     int outError, outCount;
1951     static int gotEof = 0;
1952     static FILE *ini;
1953
1954     /* Pass data read from player on to ICS */
1955     if (count > 0) {
1956         gotEof = 0;
1957         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1958         if (outCount < count) {
1959             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1960         }
1961         if(have_sent_ICS_logon == 2) {
1962           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1963             fprintf(ini, "%s", message);
1964             have_sent_ICS_logon = 3;
1965           } else
1966             have_sent_ICS_logon = 1;
1967         } else if(have_sent_ICS_logon == 3) {
1968             fprintf(ini, "%s", message);
1969             fclose(ini);
1970           have_sent_ICS_logon = 1;
1971         }
1972     } else if (count < 0) {
1973         RemoveInputSource(isr);
1974         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1975     } else if (gotEof++ > 0) {
1976         RemoveInputSource(isr);
1977         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1978     }
1979 }
1980
1981 void
1982 KeepAlive ()
1983 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1984     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1985     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1986     SendToICS("date\n");
1987     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1988 }
1989
1990 /* added routine for printf style output to ics */
1991 void
1992 ics_printf (char *format, ...)
1993 {
1994     char buffer[MSG_SIZ];
1995     va_list args;
1996
1997     va_start(args, format);
1998     vsnprintf(buffer, sizeof(buffer), format, args);
1999     buffer[sizeof(buffer)-1] = '\0';
2000     SendToICS(buffer);
2001     va_end(args);
2002 }
2003
2004 void
2005 SendToICS (char *s)
2006 {
2007     int count, outCount, outError;
2008
2009     if (icsPR == NoProc) return;
2010
2011     count = strlen(s);
2012     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2013     if (outCount < count) {
2014         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2015     }
2016 }
2017
2018 /* This is used for sending logon scripts to the ICS. Sending
2019    without a delay causes problems when using timestamp on ICC
2020    (at least on my machine). */
2021 void
2022 SendToICSDelayed (char *s, long msdelay)
2023 {
2024     int count, outCount, outError;
2025
2026     if (icsPR == NoProc) return;
2027
2028     count = strlen(s);
2029     if (appData.debugMode) {
2030         fprintf(debugFP, ">ICS: ");
2031         show_bytes(debugFP, s, count);
2032         fprintf(debugFP, "\n");
2033     }
2034     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2035                                       msdelay);
2036     if (outCount < count) {
2037         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2038     }
2039 }
2040
2041
2042 /* Remove all highlighting escape sequences in s
2043    Also deletes any suffix starting with '('
2044    */
2045 char *
2046 StripHighlightAndTitle (char *s)
2047 {
2048     static char retbuf[MSG_SIZ];
2049     char *p = retbuf;
2050
2051     while (*s != NULLCHAR) {
2052         while (*s == '\033') {
2053             while (*s != NULLCHAR && !isalpha(*s)) s++;
2054             if (*s != NULLCHAR) s++;
2055         }
2056         while (*s != NULLCHAR && *s != '\033') {
2057             if (*s == '(' || *s == '[') {
2058                 *p = NULLCHAR;
2059                 return retbuf;
2060             }
2061             *p++ = *s++;
2062         }
2063     }
2064     *p = NULLCHAR;
2065     return retbuf;
2066 }
2067
2068 /* Remove all highlighting escape sequences in s */
2069 char *
2070 StripHighlight (char *s)
2071 {
2072     static char retbuf[MSG_SIZ];
2073     char *p = retbuf;
2074
2075     while (*s != NULLCHAR) {
2076         while (*s == '\033') {
2077             while (*s != NULLCHAR && !isalpha(*s)) s++;
2078             if (*s != NULLCHAR) s++;
2079         }
2080         while (*s != NULLCHAR && *s != '\033') {
2081             *p++ = *s++;
2082         }
2083     }
2084     *p = NULLCHAR;
2085     return retbuf;
2086 }
2087
2088 char engineVariant[MSG_SIZ];
2089 char *variantNames[] = VARIANT_NAMES;
2090 char *
2091 VariantName (VariantClass v)
2092 {
2093     if(v == VariantUnknown || *engineVariant) return engineVariant;
2094     return variantNames[v];
2095 }
2096
2097
2098 /* Identify a variant from the strings the chess servers use or the
2099    PGN Variant tag names we use. */
2100 VariantClass
2101 StringToVariant (char *e)
2102 {
2103     char *p;
2104     int wnum = -1;
2105     VariantClass v = VariantNormal;
2106     int i, found = FALSE;
2107     char buf[MSG_SIZ];
2108     int len;
2109
2110     if (!e) return v;
2111
2112     /* [HGM] skip over optional board-size prefixes */
2113     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2114         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2115         while( *e++ != '_');
2116     }
2117
2118     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2119         v = VariantNormal;
2120         found = TRUE;
2121     } else
2122     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2123       if (p = StrCaseStr(e, variantNames[i])) {
2124         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2125         v = (VariantClass) i;
2126         found = TRUE;
2127         break;
2128       }
2129     }
2130
2131     if (!found) {
2132       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2133           || StrCaseStr(e, "wild/fr")
2134           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2135         v = VariantFischeRandom;
2136       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2137                  (i = 1, p = StrCaseStr(e, "w"))) {
2138         p += i;
2139         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2140         if (isdigit(*p)) {
2141           wnum = atoi(p);
2142         } else {
2143           wnum = -1;
2144         }
2145         switch (wnum) {
2146         case 0: /* FICS only, actually */
2147         case 1:
2148           /* Castling legal even if K starts on d-file */
2149           v = VariantWildCastle;
2150           break;
2151         case 2:
2152         case 3:
2153         case 4:
2154           /* Castling illegal even if K & R happen to start in
2155              normal positions. */
2156           v = VariantNoCastle;
2157           break;
2158         case 5:
2159         case 7:
2160         case 8:
2161         case 10:
2162         case 11:
2163         case 12:
2164         case 13:
2165         case 14:
2166         case 15:
2167         case 18:
2168         case 19:
2169           /* Castling legal iff K & R start in normal positions */
2170           v = VariantNormal;
2171           break;
2172         case 6:
2173         case 20:
2174         case 21:
2175           /* Special wilds for position setup; unclear what to do here */
2176           v = VariantLoadable;
2177           break;
2178         case 9:
2179           /* Bizarre ICC game */
2180           v = VariantTwoKings;
2181           break;
2182         case 16:
2183           v = VariantKriegspiel;
2184           break;
2185         case 17:
2186           v = VariantLosers;
2187           break;
2188         case 22:
2189           v = VariantFischeRandom;
2190           break;
2191         case 23:
2192           v = VariantCrazyhouse;
2193           break;
2194         case 24:
2195           v = VariantBughouse;
2196           break;
2197         case 25:
2198           v = Variant3Check;
2199           break;
2200         case 26:
2201           /* Not quite the same as FICS suicide! */
2202           v = VariantGiveaway;
2203           break;
2204         case 27:
2205           v = VariantAtomic;
2206           break;
2207         case 28:
2208           v = VariantShatranj;
2209           break;
2210
2211         /* Temporary names for future ICC types.  The name *will* change in
2212            the next xboard/WinBoard release after ICC defines it. */
2213         case 29:
2214           v = Variant29;
2215           break;
2216         case 30:
2217           v = Variant30;
2218           break;
2219         case 31:
2220           v = Variant31;
2221           break;
2222         case 32:
2223           v = Variant32;
2224           break;
2225         case 33:
2226           v = Variant33;
2227           break;
2228         case 34:
2229           v = Variant34;
2230           break;
2231         case 35:
2232           v = Variant35;
2233           break;
2234         case 36:
2235           v = Variant36;
2236           break;
2237         case 37:
2238           v = VariantShogi;
2239           break;
2240         case 38:
2241           v = VariantXiangqi;
2242           break;
2243         case 39:
2244           v = VariantCourier;
2245           break;
2246         case 40:
2247           v = VariantGothic;
2248           break;
2249         case 41:
2250           v = VariantCapablanca;
2251           break;
2252         case 42:
2253           v = VariantKnightmate;
2254           break;
2255         case 43:
2256           v = VariantFairy;
2257           break;
2258         case 44:
2259           v = VariantCylinder;
2260           break;
2261         case 45:
2262           v = VariantFalcon;
2263           break;
2264         case 46:
2265           v = VariantCapaRandom;
2266           break;
2267         case 47:
2268           v = VariantBerolina;
2269           break;
2270         case 48:
2271           v = VariantJanus;
2272           break;
2273         case 49:
2274           v = VariantSuper;
2275           break;
2276         case 50:
2277           v = VariantGreat;
2278           break;
2279         case -1:
2280           /* Found "wild" or "w" in the string but no number;
2281              must assume it's normal chess. */
2282           v = VariantNormal;
2283           break;
2284         default:
2285           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2286           if( (len >= MSG_SIZ) && appData.debugMode )
2287             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2288
2289           DisplayError(buf, 0);
2290           v = VariantUnknown;
2291           break;
2292         }
2293       }
2294     }
2295     if (appData.debugMode) {
2296       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2297               e, wnum, VariantName(v));
2298     }
2299     return v;
2300 }
2301
2302 static int leftover_start = 0, leftover_len = 0;
2303 char star_match[STAR_MATCH_N][MSG_SIZ];
2304
2305 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2306    advance *index beyond it, and set leftover_start to the new value of
2307    *index; else return FALSE.  If pattern contains the character '*', it
2308    matches any sequence of characters not containing '\r', '\n', or the
2309    character following the '*' (if any), and the matched sequence(s) are
2310    copied into star_match.
2311    */
2312 int
2313 looking_at ( char *buf, int *index, char *pattern)
2314 {
2315     char *bufp = &buf[*index], *patternp = pattern;
2316     int star_count = 0;
2317     char *matchp = star_match[0];
2318
2319     for (;;) {
2320         if (*patternp == NULLCHAR) {
2321             *index = leftover_start = bufp - buf;
2322             *matchp = NULLCHAR;
2323             return TRUE;
2324         }
2325         if (*bufp == NULLCHAR) return FALSE;
2326         if (*patternp == '*') {
2327             if (*bufp == *(patternp + 1)) {
2328                 *matchp = NULLCHAR;
2329                 matchp = star_match[++star_count];
2330                 patternp += 2;
2331                 bufp++;
2332                 continue;
2333             } else if (*bufp == '\n' || *bufp == '\r') {
2334                 patternp++;
2335                 if (*patternp == NULLCHAR)
2336                   continue;
2337                 else
2338                   return FALSE;
2339             } else {
2340                 *matchp++ = *bufp++;
2341                 continue;
2342             }
2343         }
2344         if (*patternp != *bufp) return FALSE;
2345         patternp++;
2346         bufp++;
2347     }
2348 }
2349
2350 void
2351 SendToPlayer (char *data, int length)
2352 {
2353     int error, outCount;
2354     outCount = OutputToProcess(NoProc, data, length, &error);
2355     if (outCount < length) {
2356         DisplayFatalError(_("Error writing to display"), error, 1);
2357     }
2358 }
2359
2360 void
2361 PackHolding (char packed[], char *holding)
2362 {
2363     char *p = holding;
2364     char *q = packed;
2365     int runlength = 0;
2366     int curr = 9999;
2367     do {
2368         if (*p == curr) {
2369             runlength++;
2370         } else {
2371             switch (runlength) {
2372               case 0:
2373                 break;
2374               case 1:
2375                 *q++ = curr;
2376                 break;
2377               case 2:
2378                 *q++ = curr;
2379                 *q++ = curr;
2380                 break;
2381               default:
2382                 sprintf(q, "%d", runlength);
2383                 while (*q) q++;
2384                 *q++ = curr;
2385                 break;
2386             }
2387             runlength = 1;
2388             curr = *p;
2389         }
2390     } while (*p++);
2391     *q = NULLCHAR;
2392 }
2393
2394 /* Telnet protocol requests from the front end */
2395 void
2396 TelnetRequest (unsigned char ddww, unsigned char option)
2397 {
2398     unsigned char msg[3];
2399     int outCount, outError;
2400
2401     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2402
2403     if (appData.debugMode) {
2404         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2405         switch (ddww) {
2406           case TN_DO:
2407             ddwwStr = "DO";
2408             break;
2409           case TN_DONT:
2410             ddwwStr = "DONT";
2411             break;
2412           case TN_WILL:
2413             ddwwStr = "WILL";
2414             break;
2415           case TN_WONT:
2416             ddwwStr = "WONT";
2417             break;
2418           default:
2419             ddwwStr = buf1;
2420             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2421             break;
2422         }
2423         switch (option) {
2424           case TN_ECHO:
2425             optionStr = "ECHO";
2426             break;
2427           default:
2428             optionStr = buf2;
2429             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2430             break;
2431         }
2432         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2433     }
2434     msg[0] = TN_IAC;
2435     msg[1] = ddww;
2436     msg[2] = option;
2437     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2438     if (outCount < 3) {
2439         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2440     }
2441 }
2442
2443 void
2444 DoEcho ()
2445 {
2446     if (!appData.icsActive) return;
2447     TelnetRequest(TN_DO, TN_ECHO);
2448 }
2449
2450 void
2451 DontEcho ()
2452 {
2453     if (!appData.icsActive) return;
2454     TelnetRequest(TN_DONT, TN_ECHO);
2455 }
2456
2457 void
2458 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2459 {
2460     /* put the holdings sent to us by the server on the board holdings area */
2461     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2462     char p;
2463     ChessSquare piece;
2464
2465     if(gameInfo.holdingsWidth < 2)  return;
2466     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2467         return; // prevent overwriting by pre-board holdings
2468
2469     if( (int)lowestPiece >= BlackPawn ) {
2470         holdingsColumn = 0;
2471         countsColumn = 1;
2472         holdingsStartRow = BOARD_HEIGHT-1;
2473         direction = -1;
2474     } else {
2475         holdingsColumn = BOARD_WIDTH-1;
2476         countsColumn = BOARD_WIDTH-2;
2477         holdingsStartRow = 0;
2478         direction = 1;
2479     }
2480
2481     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2482         board[i][holdingsColumn] = EmptySquare;
2483         board[i][countsColumn]   = (ChessSquare) 0;
2484     }
2485     while( (p=*holdings++) != NULLCHAR ) {
2486         piece = CharToPiece( ToUpper(p) );
2487         if(piece == EmptySquare) continue;
2488         /*j = (int) piece - (int) WhitePawn;*/
2489         j = PieceToNumber(piece);
2490         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2491         if(j < 0) continue;               /* should not happen */
2492         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2493         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2494         board[holdingsStartRow+j*direction][countsColumn]++;
2495     }
2496 }
2497
2498
2499 void
2500 VariantSwitch (Board board, VariantClass newVariant)
2501 {
2502    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2503    static Board oldBoard;
2504
2505    startedFromPositionFile = FALSE;
2506    if(gameInfo.variant == newVariant) return;
2507
2508    /* [HGM] This routine is called each time an assignment is made to
2509     * gameInfo.variant during a game, to make sure the board sizes
2510     * are set to match the new variant. If that means adding or deleting
2511     * holdings, we shift the playing board accordingly
2512     * This kludge is needed because in ICS observe mode, we get boards
2513     * of an ongoing game without knowing the variant, and learn about the
2514     * latter only later. This can be because of the move list we requested,
2515     * in which case the game history is refilled from the beginning anyway,
2516     * but also when receiving holdings of a crazyhouse game. In the latter
2517     * case we want to add those holdings to the already received position.
2518     */
2519
2520
2521    if (appData.debugMode) {
2522      fprintf(debugFP, "Switch board from %s to %s\n",
2523              VariantName(gameInfo.variant), VariantName(newVariant));
2524      setbuf(debugFP, NULL);
2525    }
2526    shuffleOpenings = 0;       /* [HGM] shuffle */
2527    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2528    switch(newVariant)
2529      {
2530      case VariantShogi:
2531        newWidth = 9;  newHeight = 9;
2532        gameInfo.holdingsSize = 7;
2533      case VariantBughouse:
2534      case VariantCrazyhouse:
2535        newHoldingsWidth = 2; break;
2536      case VariantGreat:
2537        newWidth = 10;
2538      case VariantSuper:
2539        newHoldingsWidth = 2;
2540        gameInfo.holdingsSize = 8;
2541        break;
2542      case VariantGothic:
2543      case VariantCapablanca:
2544      case VariantCapaRandom:
2545        newWidth = 10;
2546      default:
2547        newHoldingsWidth = gameInfo.holdingsSize = 0;
2548      };
2549
2550    if(newWidth  != gameInfo.boardWidth  ||
2551       newHeight != gameInfo.boardHeight ||
2552       newHoldingsWidth != gameInfo.holdingsWidth ) {
2553
2554      /* shift position to new playing area, if needed */
2555      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2556        for(i=0; i<BOARD_HEIGHT; i++)
2557          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2558            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2559              board[i][j];
2560        for(i=0; i<newHeight; i++) {
2561          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2562          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2563        }
2564      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2565        for(i=0; i<BOARD_HEIGHT; i++)
2566          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2567            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2568              board[i][j];
2569      }
2570      board[HOLDINGS_SET] = 0;
2571      gameInfo.boardWidth  = newWidth;
2572      gameInfo.boardHeight = newHeight;
2573      gameInfo.holdingsWidth = newHoldingsWidth;
2574      gameInfo.variant = newVariant;
2575      InitDrawingSizes(-2, 0);
2576    } else gameInfo.variant = newVariant;
2577    CopyBoard(oldBoard, board);   // remember correctly formatted board
2578      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2579    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2580 }
2581
2582 static int loggedOn = FALSE;
2583
2584 /*-- Game start info cache: --*/
2585 int gs_gamenum;
2586 char gs_kind[MSG_SIZ];
2587 static char player1Name[128] = "";
2588 static char player2Name[128] = "";
2589 static char cont_seq[] = "\n\\   ";
2590 static int player1Rating = -1;
2591 static int player2Rating = -1;
2592 /*----------------------------*/
2593
2594 ColorClass curColor = ColorNormal;
2595 int suppressKibitz = 0;
2596
2597 // [HGM] seekgraph
2598 Boolean soughtPending = FALSE;
2599 Boolean seekGraphUp;
2600 #define MAX_SEEK_ADS 200
2601 #define SQUARE 0x80
2602 char *seekAdList[MAX_SEEK_ADS];
2603 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2604 float tcList[MAX_SEEK_ADS];
2605 char colorList[MAX_SEEK_ADS];
2606 int nrOfSeekAds = 0;
2607 int minRating = 1010, maxRating = 2800;
2608 int hMargin = 10, vMargin = 20, h, w;
2609 extern int squareSize, lineGap;
2610
2611 void
2612 PlotSeekAd (int i)
2613 {
2614         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2615         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2616         if(r < minRating+100 && r >=0 ) r = minRating+100;
2617         if(r > maxRating) r = maxRating;
2618         if(tc < 1.f) tc = 1.f;
2619         if(tc > 95.f) tc = 95.f;
2620         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2621         y = ((double)r - minRating)/(maxRating - minRating)
2622             * (h-vMargin-squareSize/8-1) + vMargin;
2623         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2624         if(strstr(seekAdList[i], " u ")) color = 1;
2625         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2626            !strstr(seekAdList[i], "bullet") &&
2627            !strstr(seekAdList[i], "blitz") &&
2628            !strstr(seekAdList[i], "standard") ) color = 2;
2629         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2630         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2631 }
2632
2633 void
2634 PlotSingleSeekAd (int i)
2635 {
2636         PlotSeekAd(i);
2637 }
2638
2639 void
2640 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2641 {
2642         char buf[MSG_SIZ], *ext = "";
2643         VariantClass v = StringToVariant(type);
2644         if(strstr(type, "wild")) {
2645             ext = type + 4; // append wild number
2646             if(v == VariantFischeRandom) type = "chess960"; else
2647             if(v == VariantLoadable) type = "setup"; else
2648             type = VariantName(v);
2649         }
2650         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2651         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2652             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2653             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2654             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2655             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2656             seekNrList[nrOfSeekAds] = nr;
2657             zList[nrOfSeekAds] = 0;
2658             seekAdList[nrOfSeekAds++] = StrSave(buf);
2659             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2660         }
2661 }
2662
2663 void
2664 EraseSeekDot (int i)
2665 {
2666     int x = xList[i], y = yList[i], d=squareSize/4, k;
2667     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2668     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2669     // now replot every dot that overlapped
2670     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2671         int xx = xList[k], yy = yList[k];
2672         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2673             DrawSeekDot(xx, yy, colorList[k]);
2674     }
2675 }
2676
2677 void
2678 RemoveSeekAd (int nr)
2679 {
2680         int i;
2681         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2682             EraseSeekDot(i);
2683             if(seekAdList[i]) free(seekAdList[i]);
2684             seekAdList[i] = seekAdList[--nrOfSeekAds];
2685             seekNrList[i] = seekNrList[nrOfSeekAds];
2686             ratingList[i] = ratingList[nrOfSeekAds];
2687             colorList[i]  = colorList[nrOfSeekAds];
2688             tcList[i] = tcList[nrOfSeekAds];
2689             xList[i]  = xList[nrOfSeekAds];
2690             yList[i]  = yList[nrOfSeekAds];
2691             zList[i]  = zList[nrOfSeekAds];
2692             seekAdList[nrOfSeekAds] = NULL;
2693             break;
2694         }
2695 }
2696
2697 Boolean
2698 MatchSoughtLine (char *line)
2699 {
2700     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2701     int nr, base, inc, u=0; char dummy;
2702
2703     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2704        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2705        (u=1) &&
2706        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2707         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2708         // match: compact and save the line
2709         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2710         return TRUE;
2711     }
2712     return FALSE;
2713 }
2714
2715 int
2716 DrawSeekGraph ()
2717 {
2718     int i;
2719     if(!seekGraphUp) return FALSE;
2720     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2721     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2722
2723     DrawSeekBackground(0, 0, w, h);
2724     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2725     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2726     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2727         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2728         yy = h-1-yy;
2729         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2730         if(i%500 == 0) {
2731             char buf[MSG_SIZ];
2732             snprintf(buf, MSG_SIZ, "%d", i);
2733             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2734         }
2735     }
2736     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2737     for(i=1; i<100; i+=(i<10?1:5)) {
2738         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2739         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2740         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2741             char buf[MSG_SIZ];
2742             snprintf(buf, MSG_SIZ, "%d", i);
2743             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2744         }
2745     }
2746     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2747     return TRUE;
2748 }
2749
2750 int
2751 SeekGraphClick (ClickType click, int x, int y, int moving)
2752 {
2753     static int lastDown = 0, displayed = 0, lastSecond;
2754     if(y < 0) return FALSE;
2755     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2756         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2757         if(!seekGraphUp) return FALSE;
2758         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2759         DrawPosition(TRUE, NULL);
2760         return TRUE;
2761     }
2762     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2763         if(click == Release || moving) return FALSE;
2764         nrOfSeekAds = 0;
2765         soughtPending = TRUE;
2766         SendToICS(ics_prefix);
2767         SendToICS("sought\n"); // should this be "sought all"?
2768     } else { // issue challenge based on clicked ad
2769         int dist = 10000; int i, closest = 0, second = 0;
2770         for(i=0; i<nrOfSeekAds; i++) {
2771             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2772             if(d < dist) { dist = d; closest = i; }
2773             second += (d - zList[i] < 120); // count in-range ads
2774             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2775         }
2776         if(dist < 120) {
2777             char buf[MSG_SIZ];
2778             second = (second > 1);
2779             if(displayed != closest || second != lastSecond) {
2780                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2781                 lastSecond = second; displayed = closest;
2782             }
2783             if(click == Press) {
2784                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2785                 lastDown = closest;
2786                 return TRUE;
2787             } // on press 'hit', only show info
2788             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2789             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2790             SendToICS(ics_prefix);
2791             SendToICS(buf);
2792             return TRUE; // let incoming board of started game pop down the graph
2793         } else if(click == Release) { // release 'miss' is ignored
2794             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2795             if(moving == 2) { // right up-click
2796                 nrOfSeekAds = 0; // refresh graph
2797                 soughtPending = TRUE;
2798                 SendToICS(ics_prefix);
2799                 SendToICS("sought\n"); // should this be "sought all"?
2800             }
2801             return TRUE;
2802         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2803         // press miss or release hit 'pop down' seek graph
2804         seekGraphUp = FALSE;
2805         DrawPosition(TRUE, NULL);
2806     }
2807     return TRUE;
2808 }
2809
2810 void
2811 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2812 {
2813 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2814 #define STARTED_NONE 0
2815 #define STARTED_MOVES 1
2816 #define STARTED_BOARD 2
2817 #define STARTED_OBSERVE 3
2818 #define STARTED_HOLDINGS 4
2819 #define STARTED_CHATTER 5
2820 #define STARTED_COMMENT 6
2821 #define STARTED_MOVES_NOHIDE 7
2822
2823     static int started = STARTED_NONE;
2824     static char parse[20000];
2825     static int parse_pos = 0;
2826     static char buf[BUF_SIZE + 1];
2827     static int firstTime = TRUE, intfSet = FALSE;
2828     static ColorClass prevColor = ColorNormal;
2829     static int savingComment = FALSE;
2830     static int cmatch = 0; // continuation sequence match
2831     char *bp;
2832     char str[MSG_SIZ];
2833     int i, oldi;
2834     int buf_len;
2835     int next_out;
2836     int tkind;
2837     int backup;    /* [DM] For zippy color lines */
2838     char *p;
2839     char talker[MSG_SIZ]; // [HGM] chat
2840     int channel, collective=0;
2841
2842     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2843
2844     if (appData.debugMode) {
2845       if (!error) {
2846         fprintf(debugFP, "<ICS: ");
2847         show_bytes(debugFP, data, count);
2848         fprintf(debugFP, "\n");
2849       }
2850     }
2851
2852     if (appData.debugMode) { int f = forwardMostMove;
2853         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2854                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2855                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2856     }
2857     if (count > 0) {
2858         /* If last read ended with a partial line that we couldn't parse,
2859            prepend it to the new read and try again. */
2860         if (leftover_len > 0) {
2861             for (i=0; i<leftover_len; i++)
2862               buf[i] = buf[leftover_start + i];
2863         }
2864
2865     /* copy new characters into the buffer */
2866     bp = buf + leftover_len;
2867     buf_len=leftover_len;
2868     for (i=0; i<count; i++)
2869     {
2870         // ignore these
2871         if (data[i] == '\r')
2872             continue;
2873
2874         // join lines split by ICS?
2875         if (!appData.noJoin)
2876         {
2877             /*
2878                 Joining just consists of finding matches against the
2879                 continuation sequence, and discarding that sequence
2880                 if found instead of copying it.  So, until a match
2881                 fails, there's nothing to do since it might be the
2882                 complete sequence, and thus, something we don't want
2883                 copied.
2884             */
2885             if (data[i] == cont_seq[cmatch])
2886             {
2887                 cmatch++;
2888                 if (cmatch == strlen(cont_seq))
2889                 {
2890                     cmatch = 0; // complete match.  just reset the counter
2891
2892                     /*
2893                         it's possible for the ICS to not include the space
2894                         at the end of the last word, making our [correct]
2895                         join operation fuse two separate words.  the server
2896                         does this when the space occurs at the width setting.
2897                     */
2898                     if (!buf_len || buf[buf_len-1] != ' ')
2899                     {
2900                         *bp++ = ' ';
2901                         buf_len++;
2902                     }
2903                 }
2904                 continue;
2905             }
2906             else if (cmatch)
2907             {
2908                 /*
2909                     match failed, so we have to copy what matched before
2910                     falling through and copying this character.  In reality,
2911                     this will only ever be just the newline character, but
2912                     it doesn't hurt to be precise.
2913                 */
2914                 strncpy(bp, cont_seq, cmatch);
2915                 bp += cmatch;
2916                 buf_len += cmatch;
2917                 cmatch = 0;
2918             }
2919         }
2920
2921         // copy this char
2922         *bp++ = data[i];
2923         buf_len++;
2924     }
2925
2926         buf[buf_len] = NULLCHAR;
2927 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2928         next_out = 0;
2929         leftover_start = 0;
2930
2931         i = 0;
2932         while (i < buf_len) {
2933             /* Deal with part of the TELNET option negotiation
2934                protocol.  We refuse to do anything beyond the
2935                defaults, except that we allow the WILL ECHO option,
2936                which ICS uses to turn off password echoing when we are
2937                directly connected to it.  We reject this option
2938                if localLineEditing mode is on (always on in xboard)
2939                and we are talking to port 23, which might be a real
2940                telnet server that will try to keep WILL ECHO on permanently.
2941              */
2942             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2943                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2944                 unsigned char option;
2945                 oldi = i;
2946                 switch ((unsigned char) buf[++i]) {
2947                   case TN_WILL:
2948                     if (appData.debugMode)
2949                       fprintf(debugFP, "\n<WILL ");
2950                     switch (option = (unsigned char) buf[++i]) {
2951                       case TN_ECHO:
2952                         if (appData.debugMode)
2953                           fprintf(debugFP, "ECHO ");
2954                         /* Reply only if this is a change, according
2955                            to the protocol rules. */
2956                         if (remoteEchoOption) break;
2957                         if (appData.localLineEditing &&
2958                             atoi(appData.icsPort) == TN_PORT) {
2959                             TelnetRequest(TN_DONT, TN_ECHO);
2960                         } else {
2961                             EchoOff();
2962                             TelnetRequest(TN_DO, TN_ECHO);
2963                             remoteEchoOption = TRUE;
2964                         }
2965                         break;
2966                       default:
2967                         if (appData.debugMode)
2968                           fprintf(debugFP, "%d ", option);
2969                         /* Whatever this is, we don't want it. */
2970                         TelnetRequest(TN_DONT, option);
2971                         break;
2972                     }
2973                     break;
2974                   case TN_WONT:
2975                     if (appData.debugMode)
2976                       fprintf(debugFP, "\n<WONT ");
2977                     switch (option = (unsigned char) buf[++i]) {
2978                       case TN_ECHO:
2979                         if (appData.debugMode)
2980                           fprintf(debugFP, "ECHO ");
2981                         /* Reply only if this is a change, according
2982                            to the protocol rules. */
2983                         if (!remoteEchoOption) break;
2984                         EchoOn();
2985                         TelnetRequest(TN_DONT, TN_ECHO);
2986                         remoteEchoOption = FALSE;
2987                         break;
2988                       default:
2989                         if (appData.debugMode)
2990                           fprintf(debugFP, "%d ", (unsigned char) option);
2991                         /* Whatever this is, it must already be turned
2992                            off, because we never agree to turn on
2993                            anything non-default, so according to the
2994                            protocol rules, we don't reply. */
2995                         break;
2996                     }
2997                     break;
2998                   case TN_DO:
2999                     if (appData.debugMode)
3000                       fprintf(debugFP, "\n<DO ");
3001                     switch (option = (unsigned char) buf[++i]) {
3002                       default:
3003                         /* Whatever this is, we refuse to do it. */
3004                         if (appData.debugMode)
3005                           fprintf(debugFP, "%d ", option);
3006                         TelnetRequest(TN_WONT, option);
3007                         break;
3008                     }
3009                     break;
3010                   case TN_DONT:
3011                     if (appData.debugMode)
3012                       fprintf(debugFP, "\n<DONT ");
3013                     switch (option = (unsigned char) buf[++i]) {
3014                       default:
3015                         if (appData.debugMode)
3016                           fprintf(debugFP, "%d ", option);
3017                         /* Whatever this is, we are already not doing
3018                            it, because we never agree to do anything
3019                            non-default, so according to the protocol
3020                            rules, we don't reply. */
3021                         break;
3022                     }
3023                     break;
3024                   case TN_IAC:
3025                     if (appData.debugMode)
3026                       fprintf(debugFP, "\n<IAC ");
3027                     /* Doubled IAC; pass it through */
3028                     i--;
3029                     break;
3030                   default:
3031                     if (appData.debugMode)
3032                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3033                     /* Drop all other telnet commands on the floor */
3034                     break;
3035                 }
3036                 if (oldi > next_out)
3037                   SendToPlayer(&buf[next_out], oldi - next_out);
3038                 if (++i > next_out)
3039                   next_out = i;
3040                 continue;
3041             }
3042
3043             /* OK, this at least will *usually* work */
3044             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3045                 loggedOn = TRUE;
3046             }
3047
3048             if (loggedOn && !intfSet) {
3049                 if (ics_type == ICS_ICC) {
3050                   snprintf(str, MSG_SIZ,
3051                           "/set-quietly interface %s\n/set-quietly style 12\n",
3052                           programVersion);
3053                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3054                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3055                 } else if (ics_type == ICS_CHESSNET) {
3056                   snprintf(str, MSG_SIZ, "/style 12\n");
3057                 } else {
3058                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3059                   strcat(str, programVersion);
3060                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3061                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3063 #ifdef WIN32
3064                   strcat(str, "$iset nohighlight 1\n");
3065 #endif
3066                   strcat(str, "$iset lock 1\n$style 12\n");
3067                 }
3068                 SendToICS(str);
3069                 NotifyFrontendLogin();
3070                 intfSet = TRUE;
3071             }
3072
3073             if (started == STARTED_COMMENT) {
3074                 /* Accumulate characters in comment */
3075                 parse[parse_pos++] = buf[i];
3076                 if (buf[i] == '\n') {
3077                     parse[parse_pos] = NULLCHAR;
3078                     if(chattingPartner>=0) {
3079                         char mess[MSG_SIZ];
3080                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3081                         OutputChatMessage(chattingPartner, mess);
3082                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3083                             int p;
3084                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3085                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3086                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3087                                 OutputChatMessage(p, mess);
3088                                 break;
3089                             }
3090                         }
3091                         chattingPartner = -1;
3092                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3093                         collective = 0;
3094                     } else
3095                     if(!suppressKibitz) // [HGM] kibitz
3096                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3097                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3098                         int nrDigit = 0, nrAlph = 0, j;
3099                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3100                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3101                         parse[parse_pos] = NULLCHAR;
3102                         // try to be smart: if it does not look like search info, it should go to
3103                         // ICS interaction window after all, not to engine-output window.
3104                         for(j=0; j<parse_pos; j++) { // count letters and digits
3105                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3106                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3107                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3108                         }
3109                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3110                             int depth=0; float score;
3111                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3112                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3113                                 pvInfoList[forwardMostMove-1].depth = depth;
3114                                 pvInfoList[forwardMostMove-1].score = 100*score;
3115                             }
3116                             OutputKibitz(suppressKibitz, parse);
3117                         } else {
3118                             char tmp[MSG_SIZ];
3119                             if(gameMode == IcsObserving) // restore original ICS messages
3120                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3121                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3122                             else
3123                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3124                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3125                             SendToPlayer(tmp, strlen(tmp));
3126                         }
3127                         next_out = i+1; // [HGM] suppress printing in ICS window
3128                     }
3129                     started = STARTED_NONE;
3130                 } else {
3131                     /* Don't match patterns against characters in comment */
3132                     i++;
3133                     continue;
3134                 }
3135             }
3136             if (started == STARTED_CHATTER) {
3137                 if (buf[i] != '\n') {
3138                     /* Don't match patterns against characters in chatter */
3139                     i++;
3140                     continue;
3141                 }
3142                 started = STARTED_NONE;
3143                 if(suppressKibitz) next_out = i+1;
3144             }
3145
3146             /* Kludge to deal with rcmd protocol */
3147             if (firstTime && looking_at(buf, &i, "\001*")) {
3148                 DisplayFatalError(&buf[1], 0, 1);
3149                 continue;
3150             } else {
3151                 firstTime = FALSE;
3152             }
3153
3154             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3155                 ics_type = ICS_ICC;
3156                 ics_prefix = "/";
3157                 if (appData.debugMode)
3158                   fprintf(debugFP, "ics_type %d\n", ics_type);
3159                 continue;
3160             }
3161             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3162                 ics_type = ICS_FICS;
3163                 ics_prefix = "$";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3169                 ics_type = ICS_CHESSNET;
3170                 ics_prefix = "/";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175
3176             if (!loggedOn &&
3177                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3178                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3179                  looking_at(buf, &i, "will be \"*\""))) {
3180               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3181               continue;
3182             }
3183
3184             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3185               char buf[MSG_SIZ];
3186               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3187               DisplayIcsInteractionTitle(buf);
3188               have_set_title = TRUE;
3189             }
3190
3191             /* skip finger notes */
3192             if (started == STARTED_NONE &&
3193                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3194                  (buf[i] == '1' && buf[i+1] == '0')) &&
3195                 buf[i+2] == ':' && buf[i+3] == ' ') {
3196               started = STARTED_CHATTER;
3197               i += 3;
3198               continue;
3199             }
3200
3201             oldi = i;
3202             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3203             if(appData.seekGraph) {
3204                 if(soughtPending && MatchSoughtLine(buf+i)) {
3205                     i = strstr(buf+i, "rated") - buf;
3206                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3207                     next_out = leftover_start = i;
3208                     started = STARTED_CHATTER;
3209                     suppressKibitz = TRUE;
3210                     continue;
3211                 }
3212                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3213                         && looking_at(buf, &i, "* ads displayed")) {
3214                     soughtPending = FALSE;
3215                     seekGraphUp = TRUE;
3216                     DrawSeekGraph();
3217                     continue;
3218                 }
3219                 if(appData.autoRefresh) {
3220                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3221                         int s = (ics_type == ICS_ICC); // ICC format differs
3222                         if(seekGraphUp)
3223                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3224                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3225                         looking_at(buf, &i, "*% "); // eat prompt
3226                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3227                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3228                         next_out = i; // suppress
3229                         continue;
3230                     }
3231                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3232                         char *p = star_match[0];
3233                         while(*p) {
3234                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3235                             while(*p && *p++ != ' '); // next
3236                         }
3237                         looking_at(buf, &i, "*% "); // eat prompt
3238                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3239                         next_out = i;
3240                         continue;
3241                     }
3242                 }
3243             }
3244
3245             /* skip formula vars */
3246             if (started == STARTED_NONE &&
3247                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3248               started = STARTED_CHATTER;
3249               i += 3;
3250               continue;
3251             }
3252
3253             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3254             if (appData.autoKibitz && started == STARTED_NONE &&
3255                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3256                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3257                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3258                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3259                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3260                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3261                         suppressKibitz = TRUE;
3262                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3263                         next_out = i;
3264                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3265                                 && (gameMode == IcsPlayingWhite)) ||
3266                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3267                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3268                             started = STARTED_CHATTER; // own kibitz we simply discard
3269                         else {
3270                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3271                             parse_pos = 0; parse[0] = NULLCHAR;
3272                             savingComment = TRUE;
3273                             suppressKibitz = gameMode != IcsObserving ? 2 :
3274                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3275                         }
3276                         continue;
3277                 } else
3278                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3279                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3280                          && atoi(star_match[0])) {
3281                     // suppress the acknowledgements of our own autoKibitz
3282                     char *p;
3283                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3284                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3285                     SendToPlayer(star_match[0], strlen(star_match[0]));
3286                     if(looking_at(buf, &i, "*% ")) // eat prompt
3287                         suppressKibitz = FALSE;
3288                     next_out = i;
3289                     continue;
3290                 }
3291             } // [HGM] kibitz: end of patch
3292
3293             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3294
3295             // [HGM] chat: intercept tells by users for which we have an open chat window
3296             channel = -1;
3297             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3298                                            looking_at(buf, &i, "* whispers:") ||
3299                                            looking_at(buf, &i, "* kibitzes:") ||
3300                                            looking_at(buf, &i, "* shouts:") ||
3301                                            looking_at(buf, &i, "* c-shouts:") ||
3302                                            looking_at(buf, &i, "--> * ") ||
3303                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3304                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3305                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3306                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3307                 int p;
3308                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3309                 chattingPartner = -1; collective = 0;
3310
3311                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3312                 for(p=0; p<MAX_CHAT; p++) {
3313                     collective = 1;
3314                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3315                     talker[0] = '['; strcat(talker, "] ");
3316                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3317                     chattingPartner = p; break;
3318                     }
3319                 } else
3320                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3321                 for(p=0; p<MAX_CHAT; p++) {
3322                     collective = 1;
3323                     if(!strcmp("kibitzes", chatPartner[p])) {
3324                         talker[0] = '['; strcat(talker, "] ");
3325                         chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3329                 for(p=0; p<MAX_CHAT; p++) {
3330                     collective = 1;
3331                     if(!strcmp("whispers", chatPartner[p])) {
3332                         talker[0] = '['; strcat(talker, "] ");
3333                         chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3337                   if(buf[i-8] == '-' && buf[i-3] == 't')
3338                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3339                     collective = 1;
3340                     if(!strcmp("c-shouts", chatPartner[p])) {
3341                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3342                         chattingPartner = p; break;
3343                     }
3344                   }
3345                   if(chattingPartner < 0)
3346                   for(p=0; p<MAX_CHAT; p++) {
3347                     collective = 1;
3348                     if(!strcmp("shouts", chatPartner[p])) {
3349                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3350                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3351                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3352                         chattingPartner = p; break;
3353                     }
3354                   }
3355                 }
3356                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3357                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3358                     talker[0] = 0;
3359                     Colorize(ColorTell, FALSE);
3360                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3361                     collective |= 2;
3362                     chattingPartner = p; break;
3363                 }
3364                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3365                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3366                     started = STARTED_COMMENT;
3367                     parse_pos = 0; parse[0] = NULLCHAR;
3368                     savingComment = 3 + chattingPartner; // counts as TRUE
3369                     if(collective == 3) i = oldi; else {
3370                         suppressKibitz = TRUE;
3371                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3372                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3373                         continue;
3374                     }
3375                 }
3376             } // [HGM] chat: end of patch
3377
3378           backup = i;
3379             if (appData.zippyTalk || appData.zippyPlay) {
3380                 /* [DM] Backup address for color zippy lines */
3381 #if ZIPPY
3382                if (loggedOn == TRUE)
3383                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3384                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3385 #endif
3386             } // [DM] 'else { ' deleted
3387                 if (
3388                     /* Regular tells and says */
3389                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3390                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3391                     looking_at(buf, &i, "* says: ") ||
3392                     /* Don't color "message" or "messages" output */
3393                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3394                     looking_at(buf, &i, "*. * at *:*: ") ||
3395                     looking_at(buf, &i, "--* (*:*): ") ||
3396                     /* Message notifications (same color as tells) */
3397                     looking_at(buf, &i, "* has left a message ") ||
3398                     looking_at(buf, &i, "* just sent you a message:\n") ||
3399                     /* Whispers and kibitzes */
3400                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3401                     looking_at(buf, &i, "* kibitzes: ") ||
3402                     /* Channel tells */
3403                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3404
3405                   if (tkind == 1 && strchr(star_match[0], ':')) {
3406                       /* Avoid "tells you:" spoofs in channels */
3407                      tkind = 3;
3408                   }
3409                   if (star_match[0][0] == NULLCHAR ||
3410                       strchr(star_match[0], ' ') ||
3411                       (tkind == 3 && strchr(star_match[1], ' '))) {
3412                     /* Reject bogus matches */
3413                     i = oldi;
3414                   } else {
3415                     if (appData.colorize) {
3416                       if (oldi > next_out) {
3417                         SendToPlayer(&buf[next_out], oldi - next_out);
3418                         next_out = oldi;
3419                       }
3420                       switch (tkind) {
3421                       case 1:
3422                         Colorize(ColorTell, FALSE);
3423                         curColor = ColorTell;
3424                         break;
3425                       case 2:
3426                         Colorize(ColorKibitz, FALSE);
3427                         curColor = ColorKibitz;
3428                         break;
3429                       case 3:
3430                         p = strrchr(star_match[1], '(');
3431                         if (p == NULL) {
3432                           p = star_match[1];
3433                         } else {
3434                           p++;
3435                         }
3436                         if (atoi(p) == 1) {
3437                           Colorize(ColorChannel1, FALSE);
3438                           curColor = ColorChannel1;
3439                         } else {
3440                           Colorize(ColorChannel, FALSE);
3441                           curColor = ColorChannel;
3442                         }
3443                         break;
3444                       case 5:
3445                         curColor = ColorNormal;
3446                         break;
3447                       }
3448                     }
3449                     if (started == STARTED_NONE && appData.autoComment &&
3450                         (gameMode == IcsObserving ||
3451                          gameMode == IcsPlayingWhite ||
3452                          gameMode == IcsPlayingBlack)) {
3453                       parse_pos = i - oldi;
3454                       memcpy(parse, &buf[oldi], parse_pos);
3455                       parse[parse_pos] = NULLCHAR;
3456                       started = STARTED_COMMENT;
3457                       savingComment = TRUE;
3458                     } else if(collective != 3) {
3459                       started = STARTED_CHATTER;
3460                       savingComment = FALSE;
3461                     }
3462                     loggedOn = TRUE;
3463                     continue;
3464                   }
3465                 }
3466
3467                 if (looking_at(buf, &i, "* s-shouts: ") ||
3468                     looking_at(buf, &i, "* c-shouts: ")) {
3469                     if (appData.colorize) {
3470                         if (oldi > next_out) {
3471                             SendToPlayer(&buf[next_out], oldi - next_out);
3472                             next_out = oldi;
3473                         }
3474                         Colorize(ColorSShout, FALSE);
3475                         curColor = ColorSShout;
3476                     }
3477                     loggedOn = TRUE;
3478                     started = STARTED_CHATTER;
3479                     continue;
3480                 }
3481
3482                 if (looking_at(buf, &i, "--->")) {
3483                     loggedOn = TRUE;
3484                     continue;
3485                 }
3486
3487                 if (looking_at(buf, &i, "* shouts: ") ||
3488                     looking_at(buf, &i, "--> ")) {
3489                     if (appData.colorize) {
3490                         if (oldi > next_out) {
3491                             SendToPlayer(&buf[next_out], oldi - next_out);
3492                             next_out = oldi;
3493                         }
3494                         Colorize(ColorShout, FALSE);
3495                         curColor = ColorShout;
3496                     }
3497                     loggedOn = TRUE;
3498                     started = STARTED_CHATTER;
3499                     continue;
3500                 }
3501
3502                 if (looking_at( buf, &i, "Challenge:")) {
3503                     if (appData.colorize) {
3504                         if (oldi > next_out) {
3505                             SendToPlayer(&buf[next_out], oldi - next_out);
3506                             next_out = oldi;
3507                         }
3508                         Colorize(ColorChallenge, FALSE);
3509                         curColor = ColorChallenge;
3510                     }
3511                     loggedOn = TRUE;
3512                     continue;
3513                 }
3514
3515                 if (looking_at(buf, &i, "* offers you") ||
3516                     looking_at(buf, &i, "* offers to be") ||
3517                     looking_at(buf, &i, "* would like to") ||
3518                     looking_at(buf, &i, "* requests to") ||
3519                     looking_at(buf, &i, "Your opponent offers") ||
3520                     looking_at(buf, &i, "Your opponent requests")) {
3521
3522                     if (appData.colorize) {
3523                         if (oldi > next_out) {
3524                             SendToPlayer(&buf[next_out], oldi - next_out);
3525                             next_out = oldi;
3526                         }
3527                         Colorize(ColorRequest, FALSE);
3528                         curColor = ColorRequest;
3529                     }
3530                     continue;
3531                 }
3532
3533                 if (looking_at(buf, &i, "* (*) seeking")) {
3534                     if (appData.colorize) {
3535                         if (oldi > next_out) {
3536                             SendToPlayer(&buf[next_out], oldi - next_out);
3537                             next_out = oldi;
3538                         }
3539                         Colorize(ColorSeek, FALSE);
3540                         curColor = ColorSeek;
3541                     }
3542                     continue;
3543             }
3544
3545           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3546
3547             if (looking_at(buf, &i, "\\   ")) {
3548                 if (prevColor != ColorNormal) {
3549                     if (oldi > next_out) {
3550                         SendToPlayer(&buf[next_out], oldi - next_out);
3551                         next_out = oldi;
3552                     }
3553                     Colorize(prevColor, TRUE);
3554                     curColor = prevColor;
3555                 }
3556                 if (savingComment) {
3557                     parse_pos = i - oldi;
3558                     memcpy(parse, &buf[oldi], parse_pos);
3559                     parse[parse_pos] = NULLCHAR;
3560                     started = STARTED_COMMENT;
3561                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3562                         chattingPartner = savingComment - 3; // kludge to remember the box
3563                 } else {
3564                     started = STARTED_CHATTER;
3565                 }
3566                 continue;
3567             }
3568
3569             if (looking_at(buf, &i, "Black Strength :") ||
3570                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3571                 looking_at(buf, &i, "<10>") ||
3572                 looking_at(buf, &i, "#@#")) {
3573                 /* Wrong board style */
3574                 loggedOn = TRUE;
3575                 SendToICS(ics_prefix);
3576                 SendToICS("set style 12\n");
3577                 SendToICS(ics_prefix);
3578                 SendToICS("refresh\n");
3579                 continue;
3580             }
3581
3582             if (looking_at(buf, &i, "login:")) {
3583               if (!have_sent_ICS_logon) {
3584                 if(ICSInitScript())
3585                   have_sent_ICS_logon = 1;
3586                 else // no init script was found
3587                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3588               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3589                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3590               }
3591                 continue;
3592             }
3593
3594             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3595                 (looking_at(buf, &i, "\n<12> ") ||
3596                  looking_at(buf, &i, "<12> "))) {
3597                 loggedOn = TRUE;
3598                 if (oldi > next_out) {
3599                     SendToPlayer(&buf[next_out], oldi - next_out);
3600                 }
3601                 next_out = i;
3602                 started = STARTED_BOARD;
3603                 parse_pos = 0;
3604                 continue;
3605             }
3606
3607             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3608                 looking_at(buf, &i, "<b1> ")) {
3609                 if (oldi > next_out) {
3610                     SendToPlayer(&buf[next_out], oldi - next_out);
3611                 }
3612                 next_out = i;
3613                 started = STARTED_HOLDINGS;
3614                 parse_pos = 0;
3615                 continue;
3616             }
3617
3618             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3619                 loggedOn = TRUE;
3620                 /* Header for a move list -- first line */
3621
3622                 switch (ics_getting_history) {
3623                   case H_FALSE:
3624                     switch (gameMode) {
3625                       case IcsIdle:
3626                       case BeginningOfGame:
3627                         /* User typed "moves" or "oldmoves" while we
3628                            were idle.  Pretend we asked for these
3629                            moves and soak them up so user can step
3630                            through them and/or save them.
3631                            */
3632                         Reset(FALSE, TRUE);
3633                         gameMode = IcsObserving;
3634                         ModeHighlight();
3635                         ics_gamenum = -1;
3636                         ics_getting_history = H_GOT_UNREQ_HEADER;
3637                         break;
3638                       case EditGame: /*?*/
3639                       case EditPosition: /*?*/
3640                         /* Should above feature work in these modes too? */
3641                         /* For now it doesn't */
3642                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3643                         break;
3644                       default:
3645                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3646                         break;
3647                     }
3648                     break;
3649                   case H_REQUESTED:
3650                     /* Is this the right one? */
3651                     if (gameInfo.white && gameInfo.black &&
3652                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3653                         strcmp(gameInfo.black, star_match[2]) == 0) {
3654                         /* All is well */
3655                         ics_getting_history = H_GOT_REQ_HEADER;
3656                     }
3657                     break;
3658                   case H_GOT_REQ_HEADER:
3659                   case H_GOT_UNREQ_HEADER:
3660                   case H_GOT_UNWANTED_HEADER:
3661                   case H_GETTING_MOVES:
3662                     /* Should not happen */
3663                     DisplayError(_("Error gathering move list: two headers"), 0);
3664                     ics_getting_history = H_FALSE;
3665                     break;
3666                 }
3667
3668                 /* Save player ratings into gameInfo if needed */
3669                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3670                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3671                     (gameInfo.whiteRating == -1 ||
3672                      gameInfo.blackRating == -1)) {
3673
3674                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3675                     gameInfo.blackRating = string_to_rating(star_match[3]);
3676                     if (appData.debugMode)
3677                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3678                               gameInfo.whiteRating, gameInfo.blackRating);
3679                 }
3680                 continue;
3681             }
3682
3683             if (looking_at(buf, &i,
3684               "* * match, initial time: * minute*, increment: * second")) {
3685                 /* Header for a move list -- second line */
3686                 /* Initial board will follow if this is a wild game */
3687                 if (gameInfo.event != NULL) free(gameInfo.event);
3688                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3689                 gameInfo.event = StrSave(str);
3690                 /* [HGM] we switched variant. Translate boards if needed. */
3691                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3692                 continue;
3693             }
3694
3695             if (looking_at(buf, &i, "Move  ")) {
3696                 /* Beginning of a move list */
3697                 switch (ics_getting_history) {
3698                   case H_FALSE:
3699                     /* Normally should not happen */
3700                     /* Maybe user hit reset while we were parsing */
3701                     break;
3702                   case H_REQUESTED:
3703                     /* Happens if we are ignoring a move list that is not
3704                      * the one we just requested.  Common if the user
3705                      * tries to observe two games without turning off
3706                      * getMoveList */
3707                     break;
3708                   case H_GETTING_MOVES:
3709                     /* Should not happen */
3710                     DisplayError(_("Error gathering move list: nested"), 0);
3711                     ics_getting_history = H_FALSE;
3712                     break;
3713                   case H_GOT_REQ_HEADER:
3714                     ics_getting_history = H_GETTING_MOVES;
3715                     started = STARTED_MOVES;
3716                     parse_pos = 0;
3717                     if (oldi > next_out) {
3718                         SendToPlayer(&buf[next_out], oldi - next_out);
3719                     }
3720                     break;
3721                   case H_GOT_UNREQ_HEADER:
3722                     ics_getting_history = H_GETTING_MOVES;
3723                     started = STARTED_MOVES_NOHIDE;
3724                     parse_pos = 0;
3725                     break;
3726                   case H_GOT_UNWANTED_HEADER:
3727                     ics_getting_history = H_FALSE;
3728                     break;
3729                 }
3730                 continue;
3731             }
3732
3733             if (looking_at(buf, &i, "% ") ||
3734                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3735                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3736                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3737                     soughtPending = FALSE;
3738                     seekGraphUp = TRUE;
3739                     DrawSeekGraph();
3740                 }
3741                 if(suppressKibitz) next_out = i;
3742                 savingComment = FALSE;
3743                 suppressKibitz = 0;
3744                 switch (started) {
3745                   case STARTED_MOVES:
3746                   case STARTED_MOVES_NOHIDE:
3747                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3748                     parse[parse_pos + i - oldi] = NULLCHAR;
3749                     ParseGameHistory(parse);
3750 #if ZIPPY
3751                     if (appData.zippyPlay && first.initDone) {
3752                         FeedMovesToProgram(&first, forwardMostMove);
3753                         if (gameMode == IcsPlayingWhite) {
3754                             if (WhiteOnMove(forwardMostMove)) {
3755                                 if (first.sendTime) {
3756                                   if (first.useColors) {
3757                                     SendToProgram("black\n", &first);
3758                                   }
3759                                   SendTimeRemaining(&first, TRUE);
3760                                 }
3761                                 if (first.useColors) {
3762                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3763                                 }
3764                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3765                                 first.maybeThinking = TRUE;
3766                             } else {
3767                                 if (first.usePlayother) {
3768                                   if (first.sendTime) {
3769                                     SendTimeRemaining(&first, TRUE);
3770                                   }
3771                                   SendToProgram("playother\n", &first);
3772                                   firstMove = FALSE;
3773                                 } else {
3774                                   firstMove = TRUE;
3775                                 }
3776                             }
3777                         } else if (gameMode == IcsPlayingBlack) {
3778                             if (!WhiteOnMove(forwardMostMove)) {
3779                                 if (first.sendTime) {
3780                                   if (first.useColors) {
3781                                     SendToProgram("white\n", &first);
3782                                   }
3783                                   SendTimeRemaining(&first, FALSE);
3784                                 }
3785                                 if (first.useColors) {
3786                                   SendToProgram("black\n", &first);
3787                                 }
3788                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3789                                 first.maybeThinking = TRUE;
3790                             } else {
3791                                 if (first.usePlayother) {
3792                                   if (first.sendTime) {
3793                                     SendTimeRemaining(&first, FALSE);
3794                                   }
3795                                   SendToProgram("playother\n", &first);
3796                                   firstMove = FALSE;
3797                                 } else {
3798                                   firstMove = TRUE;
3799                                 }
3800                             }
3801                         }
3802                     }
3803 #endif
3804                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3805                         /* Moves came from oldmoves or moves command
3806                            while we weren't doing anything else.
3807                            */
3808                         currentMove = forwardMostMove;
3809                         ClearHighlights();/*!!could figure this out*/
3810                         flipView = appData.flipView;
3811                         DrawPosition(TRUE, boards[currentMove]);
3812                         DisplayBothClocks();
3813                         snprintf(str, MSG_SIZ, "%s %s %s",
3814                                 gameInfo.white, _("vs."),  gameInfo.black);
3815                         DisplayTitle(str);
3816                         gameMode = IcsIdle;
3817                     } else {
3818                         /* Moves were history of an active game */
3819                         if (gameInfo.resultDetails != NULL) {
3820                             free(gameInfo.resultDetails);
3821                             gameInfo.resultDetails = NULL;
3822                         }
3823                     }
3824                     HistorySet(parseList, backwardMostMove,
3825                                forwardMostMove, currentMove-1);
3826                     DisplayMove(currentMove - 1);
3827                     if (started == STARTED_MOVES) next_out = i;
3828                     started = STARTED_NONE;
3829                     ics_getting_history = H_FALSE;
3830                     break;
3831
3832                   case STARTED_OBSERVE:
3833                     started = STARTED_NONE;
3834                     SendToICS(ics_prefix);
3835                     SendToICS("refresh\n");
3836                     break;
3837
3838                   default:
3839                     break;
3840                 }
3841                 if(bookHit) { // [HGM] book: simulate book reply
3842                     static char bookMove[MSG_SIZ]; // a bit generous?
3843
3844                     programStats.nodes = programStats.depth = programStats.time =
3845                     programStats.score = programStats.got_only_move = 0;
3846                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3847
3848                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3849                     strcat(bookMove, bookHit);
3850                     HandleMachineMove(bookMove, &first);
3851                 }
3852                 continue;
3853             }
3854
3855             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3856                  started == STARTED_HOLDINGS ||
3857                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3858                 /* Accumulate characters in move list or board */
3859                 parse[parse_pos++] = buf[i];
3860             }
3861
3862             /* Start of game messages.  Mostly we detect start of game
3863                when the first board image arrives.  On some versions
3864                of the ICS, though, we need to do a "refresh" after starting
3865                to observe in order to get the current board right away. */
3866             if (looking_at(buf, &i, "Adding game * to observation list")) {
3867                 started = STARTED_OBSERVE;
3868                 continue;
3869             }
3870
3871             /* Handle auto-observe */
3872             if (appData.autoObserve &&
3873                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3874                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3875                 char *player;
3876                 /* Choose the player that was highlighted, if any. */
3877                 if (star_match[0][0] == '\033' ||
3878                     star_match[1][0] != '\033') {
3879                     player = star_match[0];
3880                 } else {
3881                     player = star_match[2];
3882                 }
3883                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3884                         ics_prefix, StripHighlightAndTitle(player));
3885                 SendToICS(str);
3886
3887                 /* Save ratings from notify string */
3888                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3889                 player1Rating = string_to_rating(star_match[1]);
3890                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3891                 player2Rating = string_to_rating(star_match[3]);
3892
3893                 if (appData.debugMode)
3894                   fprintf(debugFP,
3895                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3896                           player1Name, player1Rating,
3897                           player2Name, player2Rating);
3898
3899                 continue;
3900             }
3901
3902             /* Deal with automatic examine mode after a game,
3903                and with IcsObserving -> IcsExamining transition */
3904             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3905                 looking_at(buf, &i, "has made you an examiner of game *")) {
3906
3907                 int gamenum = atoi(star_match[0]);
3908                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3909                     gamenum == ics_gamenum) {
3910                     /* We were already playing or observing this game;
3911                        no need to refetch history */
3912                     gameMode = IcsExamining;
3913                     if (pausing) {
3914                         pauseExamForwardMostMove = forwardMostMove;
3915                     } else if (currentMove < forwardMostMove) {
3916                         ForwardInner(forwardMostMove);
3917                     }
3918                 } else {
3919                     /* I don't think this case really can happen */
3920                     SendToICS(ics_prefix);
3921                     SendToICS("refresh\n");
3922                 }
3923                 continue;
3924             }
3925
3926             /* Error messages */
3927 //          if (ics_user_moved) {
3928             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3929                 if (looking_at(buf, &i, "Illegal move") ||
3930                     looking_at(buf, &i, "Not a legal move") ||
3931                     looking_at(buf, &i, "Your king is in check") ||
3932                     looking_at(buf, &i, "It isn't your turn") ||
3933                     looking_at(buf, &i, "It is not your move")) {
3934                     /* Illegal move */
3935                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3936                         currentMove = forwardMostMove-1;
3937                         DisplayMove(currentMove - 1); /* before DMError */
3938                         DrawPosition(FALSE, boards[currentMove]);
3939                         SwitchClocks(forwardMostMove-1); // [HGM] race
3940                         DisplayBothClocks();
3941                     }
3942                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3943                     ics_user_moved = 0;
3944                     continue;
3945                 }
3946             }
3947
3948             if (looking_at(buf, &i, "still have time") ||
3949                 looking_at(buf, &i, "not out of time") ||
3950                 looking_at(buf, &i, "either player is out of time") ||
3951                 looking_at(buf, &i, "has timeseal; checking")) {
3952                 /* We must have called his flag a little too soon */
3953                 whiteFlag = blackFlag = FALSE;
3954                 continue;
3955             }
3956
3957             if (looking_at(buf, &i, "added * seconds to") ||
3958                 looking_at(buf, &i, "seconds were added to")) {
3959                 /* Update the clocks */
3960                 SendToICS(ics_prefix);
3961                 SendToICS("refresh\n");
3962                 continue;
3963             }
3964
3965             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3966                 ics_clock_paused = TRUE;
3967                 StopClocks();
3968                 continue;
3969             }
3970
3971             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3972                 ics_clock_paused = FALSE;
3973                 StartClocks();
3974                 continue;
3975             }
3976
3977             /* Grab player ratings from the Creating: message.
3978                Note we have to check for the special case when
3979                the ICS inserts things like [white] or [black]. */
3980             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3981                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3982                 /* star_matches:
3983                    0    player 1 name (not necessarily white)
3984                    1    player 1 rating
3985                    2    empty, white, or black (IGNORED)
3986                    3    player 2 name (not necessarily black)
3987                    4    player 2 rating
3988
3989                    The names/ratings are sorted out when the game
3990                    actually starts (below).
3991                 */
3992                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3993                 player1Rating = string_to_rating(star_match[1]);
3994                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3995                 player2Rating = string_to_rating(star_match[4]);
3996
3997                 if (appData.debugMode)
3998                   fprintf(debugFP,
3999                           "Ratings from 'Creating:' %s %d, %s %d\n",
4000                           player1Name, player1Rating,
4001                           player2Name, player2Rating);
4002
4003                 continue;
4004             }
4005
4006             /* Improved generic start/end-of-game messages */
4007             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4008                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4009                 /* If tkind == 0: */
4010                 /* star_match[0] is the game number */
4011                 /*           [1] is the white player's name */
4012                 /*           [2] is the black player's name */
4013                 /* For end-of-game: */
4014                 /*           [3] is the reason for the game end */
4015                 /*           [4] is a PGN end game-token, preceded by " " */
4016                 /* For start-of-game: */
4017                 /*           [3] begins with "Creating" or "Continuing" */
4018                 /*           [4] is " *" or empty (don't care). */
4019                 int gamenum = atoi(star_match[0]);
4020                 char *whitename, *blackname, *why, *endtoken;
4021                 ChessMove endtype = EndOfFile;
4022
4023                 if (tkind == 0) {
4024                   whitename = star_match[1];
4025                   blackname = star_match[2];
4026                   why = star_match[3];
4027                   endtoken = star_match[4];
4028                 } else {
4029                   whitename = star_match[1];
4030                   blackname = star_match[3];
4031                   why = star_match[5];
4032                   endtoken = star_match[6];
4033                 }
4034
4035                 /* Game start messages */
4036                 if (strncmp(why, "Creating ", 9) == 0 ||
4037                     strncmp(why, "Continuing ", 11) == 0) {
4038                     gs_gamenum = gamenum;
4039                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4040                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4041                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4042 #if ZIPPY
4043                     if (appData.zippyPlay) {
4044                         ZippyGameStart(whitename, blackname);
4045                     }
4046 #endif /*ZIPPY*/
4047                     partnerBoardValid = FALSE; // [HGM] bughouse
4048                     continue;
4049                 }
4050
4051                 /* Game end messages */
4052                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4053                     ics_gamenum != gamenum) {
4054                     continue;
4055                 }
4056                 while (endtoken[0] == ' ') endtoken++;
4057                 switch (endtoken[0]) {
4058                   case '*':
4059                   default:
4060                     endtype = GameUnfinished;
4061                     break;
4062                   case '0':
4063                     endtype = BlackWins;
4064                     break;
4065                   case '1':
4066                     if (endtoken[1] == '/')
4067                       endtype = GameIsDrawn;
4068                     else
4069                       endtype = WhiteWins;
4070                     break;
4071                 }
4072                 GameEnds(endtype, why, GE_ICS);
4073 #if ZIPPY
4074                 if (appData.zippyPlay && first.initDone) {
4075                     ZippyGameEnd(endtype, why);
4076                     if (first.pr == NoProc) {
4077                       /* Start the next process early so that we'll
4078                          be ready for the next challenge */
4079                       StartChessProgram(&first);
4080                     }
4081                     /* Send "new" early, in case this command takes
4082                        a long time to finish, so that we'll be ready
4083                        for the next challenge. */
4084                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4085                     Reset(TRUE, TRUE);
4086                 }
4087 #endif /*ZIPPY*/
4088                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4089                 continue;
4090             }
4091
4092             if (looking_at(buf, &i, "Removing game * from observation") ||
4093                 looking_at(buf, &i, "no longer observing game *") ||
4094                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4095                 if (gameMode == IcsObserving &&
4096                     atoi(star_match[0]) == ics_gamenum)
4097                   {
4098                       /* icsEngineAnalyze */
4099                       if (appData.icsEngineAnalyze) {
4100                             ExitAnalyzeMode();
4101                             ModeHighlight();
4102                       }
4103                       StopClocks();
4104                       gameMode = IcsIdle;
4105                       ics_gamenum = -1;
4106                       ics_user_moved = FALSE;
4107                   }
4108                 continue;
4109             }
4110
4111             if (looking_at(buf, &i, "no longer examining game *")) {
4112                 if (gameMode == IcsExamining &&
4113                     atoi(star_match[0]) == ics_gamenum)
4114                   {
4115                       gameMode = IcsIdle;
4116                       ics_gamenum = -1;
4117                       ics_user_moved = FALSE;
4118                   }
4119                 continue;
4120             }
4121
4122             /* Advance leftover_start past any newlines we find,
4123                so only partial lines can get reparsed */
4124             if (looking_at(buf, &i, "\n")) {
4125                 prevColor = curColor;
4126                 if (curColor != ColorNormal) {
4127                     if (oldi > next_out) {
4128                         SendToPlayer(&buf[next_out], oldi - next_out);
4129                         next_out = oldi;
4130                     }
4131                     Colorize(ColorNormal, FALSE);
4132                     curColor = ColorNormal;
4133                 }
4134                 if (started == STARTED_BOARD) {
4135                     started = STARTED_NONE;
4136                     parse[parse_pos] = NULLCHAR;
4137                     ParseBoard12(parse);
4138                     ics_user_moved = 0;
4139
4140                     /* Send premove here */
4141                     if (appData.premove) {
4142                       char str[MSG_SIZ];
4143                       if (currentMove == 0 &&
4144                           gameMode == IcsPlayingWhite &&
4145                           appData.premoveWhite) {
4146                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4147                         if (appData.debugMode)
4148                           fprintf(debugFP, "Sending premove:\n");
4149                         SendToICS(str);
4150                       } else if (currentMove == 1 &&
4151                                  gameMode == IcsPlayingBlack &&
4152                                  appData.premoveBlack) {
4153                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                         SendToICS(str);
4157                       } else if (gotPremove) {
4158                         gotPremove = 0;
4159                         ClearPremoveHighlights();
4160                         if (appData.debugMode)
4161                           fprintf(debugFP, "Sending premove:\n");
4162                           UserMoveEvent(premoveFromX, premoveFromY,
4163                                         premoveToX, premoveToY,
4164                                         premovePromoChar);
4165                       }
4166                     }
4167
4168                     /* Usually suppress following prompt */
4169                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4170                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4171                         if (looking_at(buf, &i, "*% ")) {
4172                             savingComment = FALSE;
4173                             suppressKibitz = 0;
4174                         }
4175                     }
4176                     next_out = i;
4177                 } else if (started == STARTED_HOLDINGS) {
4178                     int gamenum;
4179                     char new_piece[MSG_SIZ];
4180                     started = STARTED_NONE;
4181                     parse[parse_pos] = NULLCHAR;
4182                     if (appData.debugMode)
4183                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4184                                                         parse, currentMove);
4185                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4186                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4187                         if (gameInfo.variant == VariantNormal) {
4188                           /* [HGM] We seem to switch variant during a game!
4189                            * Presumably no holdings were displayed, so we have
4190                            * to move the position two files to the right to
4191                            * create room for them!
4192                            */
4193                           VariantClass newVariant;
4194                           switch(gameInfo.boardWidth) { // base guess on board width
4195                                 case 9:  newVariant = VariantShogi; break;
4196                                 case 10: newVariant = VariantGreat; break;
4197                                 default: newVariant = VariantCrazyhouse; break;
4198                           }
4199                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4200                           /* Get a move list just to see the header, which
4201                              will tell us whether this is really bug or zh */
4202                           if (ics_getting_history == H_FALSE) {
4203                             ics_getting_history = H_REQUESTED;
4204                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4205                             SendToICS(str);
4206                           }
4207                         }
4208                         new_piece[0] = NULLCHAR;
4209                         sscanf(parse, "game %d white [%s black [%s <- %s",
4210                                &gamenum, white_holding, black_holding,
4211                                new_piece);
4212                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4213                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4214                         /* [HGM] copy holdings to board holdings area */
4215                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4216                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4217                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4218 #if ZIPPY
4219                         if (appData.zippyPlay && first.initDone) {
4220                             ZippyHoldings(white_holding, black_holding,
4221                                           new_piece);
4222                         }
4223 #endif /*ZIPPY*/
4224                         if (tinyLayout || smallLayout) {
4225                             char wh[16], bh[16];
4226                             PackHolding(wh, white_holding);
4227                             PackHolding(bh, black_holding);
4228                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4229                                     gameInfo.white, gameInfo.black);
4230                         } else {
4231                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4232                                     gameInfo.white, white_holding, _("vs."),
4233                                     gameInfo.black, black_holding);
4234                         }
4235                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4236                         DrawPosition(FALSE, boards[currentMove]);
4237                         DisplayTitle(str);
4238                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4239                         sscanf(parse, "game %d white [%s black [%s <- %s",
4240                                &gamenum, white_holding, black_holding,
4241                                new_piece);
4242                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4243                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4244                         /* [HGM] copy holdings to partner-board holdings area */
4245                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4246                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4247                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4248                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4249                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4250                       }
4251                     }
4252                     /* Suppress following prompt */
4253                     if (looking_at(buf, &i, "*% ")) {
4254                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4255                         savingComment = FALSE;
4256                         suppressKibitz = 0;
4257                     }
4258                     next_out = i;
4259                 }
4260                 continue;
4261             }
4262
4263             i++;                /* skip unparsed character and loop back */
4264         }
4265
4266         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4267 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4268 //          SendToPlayer(&buf[next_out], i - next_out);
4269             started != STARTED_HOLDINGS && leftover_start > next_out) {
4270             SendToPlayer(&buf[next_out], leftover_start - next_out);
4271             next_out = i;
4272         }
4273
4274         leftover_len = buf_len - leftover_start;
4275         /* if buffer ends with something we couldn't parse,
4276            reparse it after appending the next read */
4277
4278     } else if (count == 0) {
4279         RemoveInputSource(isr);
4280         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4281     } else {
4282         DisplayFatalError(_("Error reading from ICS"), error, 1);
4283     }
4284 }
4285
4286
4287 /* Board style 12 looks like this:
4288
4289    <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
4290
4291  * The "<12> " is stripped before it gets to this routine.  The two
4292  * trailing 0's (flip state and clock ticking) are later addition, and
4293  * some chess servers may not have them, or may have only the first.
4294  * Additional trailing fields may be added in the future.
4295  */
4296
4297 #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"
4298
4299 #define RELATION_OBSERVING_PLAYED    0
4300 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4301 #define RELATION_PLAYING_MYMOVE      1
4302 #define RELATION_PLAYING_NOTMYMOVE  -1
4303 #define RELATION_EXAMINING           2
4304 #define RELATION_ISOLATED_BOARD     -3
4305 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4306
4307 void
4308 ParseBoard12 (char *string)
4309 {
4310 #if ZIPPY
4311     int i, takeback;
4312     char *bookHit = NULL; // [HGM] book
4313 #endif
4314     GameMode newGameMode;
4315     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4316     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4317     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4318     char to_play, board_chars[200];
4319     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4320     char black[32], white[32];
4321     Board board;
4322     int prevMove = currentMove;
4323     int ticking = 2;
4324     ChessMove moveType;
4325     int fromX, fromY, toX, toY;
4326     char promoChar;
4327     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4328     Boolean weird = FALSE, reqFlag = FALSE;
4329
4330     fromX = fromY = toX = toY = -1;
4331
4332     newGame = FALSE;
4333
4334     if (appData.debugMode)
4335       fprintf(debugFP, "Parsing board: %s\n", string);
4336
4337     move_str[0] = NULLCHAR;
4338     elapsed_time[0] = NULLCHAR;
4339     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4340         int  i = 0, j;
4341         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4342             if(string[i] == ' ') { ranks++; files = 0; }
4343             else files++;
4344             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4345             i++;
4346         }
4347         for(j = 0; j <i; j++) board_chars[j] = string[j];
4348         board_chars[i] = '\0';
4349         string += i + 1;
4350     }
4351     n = sscanf(string, PATTERN, &to_play, &double_push,
4352                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4353                &gamenum, white, black, &relation, &basetime, &increment,
4354                &white_stren, &black_stren, &white_time, &black_time,
4355                &moveNum, str, elapsed_time, move_str, &ics_flip,
4356                &ticking);
4357
4358     if (n < 21) {
4359         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4360         DisplayError(str, 0);
4361         return;
4362     }
4363
4364     /* Convert the move number to internal form */
4365     moveNum = (moveNum - 1) * 2;
4366     if (to_play == 'B') moveNum++;
4367     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4368       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4369                         0, 1);
4370       return;
4371     }
4372
4373     switch (relation) {
4374       case RELATION_OBSERVING_PLAYED:
4375       case RELATION_OBSERVING_STATIC:
4376         if (gamenum == -1) {
4377             /* Old ICC buglet */
4378             relation = RELATION_OBSERVING_STATIC;
4379         }
4380         newGameMode = IcsObserving;
4381         break;
4382       case RELATION_PLAYING_MYMOVE:
4383       case RELATION_PLAYING_NOTMYMOVE:
4384         newGameMode =
4385           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4386             IcsPlayingWhite : IcsPlayingBlack;
4387         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4388         break;
4389       case RELATION_EXAMINING:
4390         newGameMode = IcsExamining;
4391         break;
4392       case RELATION_ISOLATED_BOARD:
4393       default:
4394         /* Just display this board.  If user was doing something else,
4395            we will forget about it until the next board comes. */
4396         newGameMode = IcsIdle;
4397         break;
4398       case RELATION_STARTING_POSITION:
4399         newGameMode = gameMode;
4400         break;
4401     }
4402
4403     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4404         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4405          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4406       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4407       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4408       static int lastBgGame = -1;
4409       char *toSqr;
4410       for (k = 0; k < ranks; k++) {
4411         for (j = 0; j < files; j++)
4412           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4413         if(gameInfo.holdingsWidth > 1) {
4414              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4415              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4416         }
4417       }
4418       CopyBoard(partnerBoard, board);
4419       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4420         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4421         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4422       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4423       if(toSqr = strchr(str, '-')) {
4424         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4425         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4426       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4427       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4428       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4429       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4430       if(twoBoards) {
4431           DisplayWhiteClock(white_time*fac, to_play == 'W');
4432           DisplayBlackClock(black_time*fac, to_play != 'W');
4433           activePartner = to_play;
4434           if(gamenum != lastBgGame) {
4435               char buf[MSG_SIZ];
4436               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4437               DisplayTitle(buf);
4438           }
4439           lastBgGame = gamenum;
4440           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4441                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4442       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4443                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4444       if(!twoBoards) DisplayMessage(partnerStatus, "");
4445         partnerBoardValid = TRUE;
4446       return;
4447     }
4448
4449     if(appData.dualBoard && appData.bgObserve) {
4450         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4451             SendToICS(ics_prefix), SendToICS("pobserve\n");
4452         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4453             char buf[MSG_SIZ];
4454             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4455             SendToICS(buf);
4456         }
4457     }
4458
4459     /* Modify behavior for initial board display on move listing
4460        of wild games.
4461        */
4462     switch (ics_getting_history) {
4463       case H_FALSE:
4464       case H_REQUESTED:
4465         break;
4466       case H_GOT_REQ_HEADER:
4467       case H_GOT_UNREQ_HEADER:
4468         /* This is the initial position of the current game */
4469         gamenum = ics_gamenum;
4470         moveNum = 0;            /* old ICS bug workaround */
4471         if (to_play == 'B') {
4472           startedFromSetupPosition = TRUE;
4473           blackPlaysFirst = TRUE;
4474           moveNum = 1;
4475           if (forwardMostMove == 0) forwardMostMove = 1;
4476           if (backwardMostMove == 0) backwardMostMove = 1;
4477           if (currentMove == 0) currentMove = 1;
4478         }
4479         newGameMode = gameMode;
4480         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4481         break;
4482       case H_GOT_UNWANTED_HEADER:
4483         /* This is an initial board that we don't want */
4484         return;
4485       case H_GETTING_MOVES:
4486         /* Should not happen */
4487         DisplayError(_("Error gathering move list: extra board"), 0);
4488         ics_getting_history = H_FALSE;
4489         return;
4490     }
4491
4492    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4493                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4494                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4495      /* [HGM] We seem to have switched variant unexpectedly
4496       * Try to guess new variant from board size
4497       */
4498           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4499           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4500           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4501           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4502           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4503           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4504           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4505           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4506           /* Get a move list just to see the header, which
4507              will tell us whether this is really bug or zh */
4508           if (ics_getting_history == H_FALSE) {
4509             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4510             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4511             SendToICS(str);
4512           }
4513     }
4514
4515     /* Take action if this is the first board of a new game, or of a
4516        different game than is currently being displayed.  */
4517     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4518         relation == RELATION_ISOLATED_BOARD) {
4519
4520         /* Forget the old game and get the history (if any) of the new one */
4521         if (gameMode != BeginningOfGame) {
4522           Reset(TRUE, TRUE);
4523         }
4524         newGame = TRUE;
4525         if (appData.autoRaiseBoard) BoardToTop();
4526         prevMove = -3;
4527         if (gamenum == -1) {
4528             newGameMode = IcsIdle;
4529         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4530                    appData.getMoveList && !reqFlag) {
4531             /* Need to get game history */
4532             ics_getting_history = H_REQUESTED;
4533             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4534             SendToICS(str);
4535         }
4536
4537         /* Initially flip the board to have black on the bottom if playing
4538            black or if the ICS flip flag is set, but let the user change
4539            it with the Flip View button. */
4540         flipView = appData.autoFlipView ?
4541           (newGameMode == IcsPlayingBlack) || ics_flip :
4542           appData.flipView;
4543
4544         /* Done with values from previous mode; copy in new ones */
4545         gameMode = newGameMode;
4546         ModeHighlight();
4547         ics_gamenum = gamenum;
4548         if (gamenum == gs_gamenum) {
4549             int klen = strlen(gs_kind);
4550             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4551             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4552             gameInfo.event = StrSave(str);
4553         } else {
4554             gameInfo.event = StrSave("ICS game");
4555         }
4556         gameInfo.site = StrSave(appData.icsHost);
4557         gameInfo.date = PGNDate();
4558         gameInfo.round = StrSave("-");
4559         gameInfo.white = StrSave(white);
4560         gameInfo.black = StrSave(black);
4561         timeControl = basetime * 60 * 1000;
4562         timeControl_2 = 0;
4563         timeIncrement = increment * 1000;
4564         movesPerSession = 0;
4565         gameInfo.timeControl = TimeControlTagValue();
4566         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4567   if (appData.debugMode) {
4568     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4569     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4570     setbuf(debugFP, NULL);
4571   }
4572
4573         gameInfo.outOfBook = NULL;
4574
4575         /* Do we have the ratings? */
4576         if (strcmp(player1Name, white) == 0 &&
4577             strcmp(player2Name, black) == 0) {
4578             if (appData.debugMode)
4579               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4580                       player1Rating, player2Rating);
4581             gameInfo.whiteRating = player1Rating;
4582             gameInfo.blackRating = player2Rating;
4583         } else if (strcmp(player2Name, white) == 0 &&
4584                    strcmp(player1Name, black) == 0) {
4585             if (appData.debugMode)
4586               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4587                       player2Rating, player1Rating);
4588             gameInfo.whiteRating = player2Rating;
4589             gameInfo.blackRating = player1Rating;
4590         }
4591         player1Name[0] = player2Name[0] = NULLCHAR;
4592
4593         /* Silence shouts if requested */
4594         if (appData.quietPlay &&
4595             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4596             SendToICS(ics_prefix);
4597             SendToICS("set shout 0\n");
4598         }
4599     }
4600
4601     /* Deal with midgame name changes */
4602     if (!newGame) {
4603         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4604             if (gameInfo.white) free(gameInfo.white);
4605             gameInfo.white = StrSave(white);
4606         }
4607         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4608             if (gameInfo.black) free(gameInfo.black);
4609             gameInfo.black = StrSave(black);
4610         }
4611     }
4612
4613     /* Throw away game result if anything actually changes in examine mode */
4614     if (gameMode == IcsExamining && !newGame) {
4615         gameInfo.result = GameUnfinished;
4616         if (gameInfo.resultDetails != NULL) {
4617             free(gameInfo.resultDetails);
4618             gameInfo.resultDetails = NULL;
4619         }
4620     }
4621
4622     /* In pausing && IcsExamining mode, we ignore boards coming
4623        in if they are in a different variation than we are. */
4624     if (pauseExamInvalid) return;
4625     if (pausing && gameMode == IcsExamining) {
4626         if (moveNum <= pauseExamForwardMostMove) {
4627             pauseExamInvalid = TRUE;
4628             forwardMostMove = pauseExamForwardMostMove;
4629             return;
4630         }
4631     }
4632
4633   if (appData.debugMode) {
4634     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4635   }
4636     /* Parse the board */
4637     for (k = 0; k < ranks; k++) {
4638       for (j = 0; j < files; j++)
4639         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4640       if(gameInfo.holdingsWidth > 1) {
4641            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4642            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4643       }
4644     }
4645     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4646       board[5][BOARD_RGHT+1] = WhiteAngel;
4647       board[6][BOARD_RGHT+1] = WhiteMarshall;
4648       board[1][0] = BlackMarshall;
4649       board[2][0] = BlackAngel;
4650       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4651     }
4652     CopyBoard(boards[moveNum], board);
4653     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4654     if (moveNum == 0) {
4655         startedFromSetupPosition =
4656           !CompareBoards(board, initialPosition);
4657         if(startedFromSetupPosition)
4658             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4659     }
4660
4661     /* [HGM] Set castling rights. Take the outermost Rooks,
4662        to make it also work for FRC opening positions. Note that board12
4663        is really defective for later FRC positions, as it has no way to
4664        indicate which Rook can castle if they are on the same side of King.
4665        For the initial position we grant rights to the outermost Rooks,
4666        and remember thos rights, and we then copy them on positions
4667        later in an FRC game. This means WB might not recognize castlings with
4668        Rooks that have moved back to their original position as illegal,
4669        but in ICS mode that is not its job anyway.
4670     */
4671     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4672     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4673
4674         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4675             if(board[0][i] == WhiteRook) j = i;
4676         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4677         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4678             if(board[0][i] == WhiteRook) j = i;
4679         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4680         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4681             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4682         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4683         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4684             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4685         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4686
4687         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4688         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4689         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4690             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4691         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4692             if(board[BOARD_HEIGHT-1][k] == bKing)
4693                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4694         if(gameInfo.variant == VariantTwoKings) {
4695             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4696             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4697             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4698         }
4699     } else { int r;
4700         r = boards[moveNum][CASTLING][0] = initialRights[0];
4701         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4702         r = boards[moveNum][CASTLING][1] = initialRights[1];
4703         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4704         r = boards[moveNum][CASTLING][3] = initialRights[3];
4705         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4706         r = boards[moveNum][CASTLING][4] = initialRights[4];
4707         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4708         /* wildcastle kludge: always assume King has rights */
4709         r = boards[moveNum][CASTLING][2] = initialRights[2];
4710         r = boards[moveNum][CASTLING][5] = initialRights[5];
4711     }
4712     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4713     boards[moveNum][EP_STATUS] = EP_NONE;
4714     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4715     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4716     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4717
4718
4719     if (ics_getting_history == H_GOT_REQ_HEADER ||
4720         ics_getting_history == H_GOT_UNREQ_HEADER) {
4721         /* This was an initial position from a move list, not
4722            the current position */
4723         return;
4724     }
4725
4726     /* Update currentMove and known move number limits */
4727     newMove = newGame || moveNum > forwardMostMove;
4728
4729     if (newGame) {
4730         forwardMostMove = backwardMostMove = currentMove = moveNum;
4731         if (gameMode == IcsExamining && moveNum == 0) {
4732           /* Workaround for ICS limitation: we are not told the wild
4733              type when starting to examine a game.  But if we ask for
4734              the move list, the move list header will tell us */
4735             ics_getting_history = H_REQUESTED;
4736             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4737             SendToICS(str);
4738         }
4739     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4740                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4741 #if ZIPPY
4742         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4743         /* [HGM] applied this also to an engine that is silently watching        */
4744         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4745             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4746             gameInfo.variant == currentlyInitializedVariant) {
4747           takeback = forwardMostMove - moveNum;
4748           for (i = 0; i < takeback; i++) {
4749             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4750             SendToProgram("undo\n", &first);
4751           }
4752         }
4753 #endif
4754
4755         forwardMostMove = moveNum;
4756         if (!pausing || currentMove > forwardMostMove)
4757           currentMove = forwardMostMove;
4758     } else {
4759         /* New part of history that is not contiguous with old part */
4760         if (pausing && gameMode == IcsExamining) {
4761             pauseExamInvalid = TRUE;
4762             forwardMostMove = pauseExamForwardMostMove;
4763             return;
4764         }
4765         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4766 #if ZIPPY
4767             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4768                 // [HGM] when we will receive the move list we now request, it will be
4769                 // fed to the engine from the first move on. So if the engine is not
4770                 // in the initial position now, bring it there.
4771                 InitChessProgram(&first, 0);
4772             }
4773 #endif
4774             ics_getting_history = H_REQUESTED;
4775             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4776             SendToICS(str);
4777         }
4778         forwardMostMove = backwardMostMove = currentMove = moveNum;
4779     }
4780
4781     /* Update the clocks */
4782     if (strchr(elapsed_time, '.')) {
4783       /* Time is in ms */
4784       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4785       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4786     } else {
4787       /* Time is in seconds */
4788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4790     }
4791
4792
4793 #if ZIPPY
4794     if (appData.zippyPlay && newGame &&
4795         gameMode != IcsObserving && gameMode != IcsIdle &&
4796         gameMode != IcsExamining)
4797       ZippyFirstBoard(moveNum, basetime, increment);
4798 #endif
4799
4800     /* Put the move on the move list, first converting
4801        to canonical algebraic form. */
4802     if (moveNum > 0) {
4803   if (appData.debugMode) {
4804     int f = forwardMostMove;
4805     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4806             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4807             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4808     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4809     fprintf(debugFP, "moveNum = %d\n", moveNum);
4810     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4811     setbuf(debugFP, NULL);
4812   }
4813         if (moveNum <= backwardMostMove) {
4814             /* We don't know what the board looked like before
4815                this move.  Punt. */
4816           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4817             strcat(parseList[moveNum - 1], " ");
4818             strcat(parseList[moveNum - 1], elapsed_time);
4819             moveList[moveNum - 1][0] = NULLCHAR;
4820         } else if (strcmp(move_str, "none") == 0) {
4821             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4822             /* Again, we don't know what the board looked like;
4823                this is really the start of the game. */
4824             parseList[moveNum - 1][0] = NULLCHAR;
4825             moveList[moveNum - 1][0] = NULLCHAR;
4826             backwardMostMove = moveNum;
4827             startedFromSetupPosition = TRUE;
4828             fromX = fromY = toX = toY = -1;
4829         } else {
4830           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4831           //                 So we parse the long-algebraic move string in stead of the SAN move
4832           int valid; char buf[MSG_SIZ], *prom;
4833
4834           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4835                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4836           // str looks something like "Q/a1-a2"; kill the slash
4837           if(str[1] == '/')
4838             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4839           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4840           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4841                 strcat(buf, prom); // long move lacks promo specification!
4842           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4843                 if(appData.debugMode)
4844                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4845                 safeStrCpy(move_str, buf, MSG_SIZ);
4846           }
4847           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4848                                 &fromX, &fromY, &toX, &toY, &promoChar)
4849                || ParseOneMove(buf, moveNum - 1, &moveType,
4850                                 &fromX, &fromY, &toX, &toY, &promoChar);
4851           // end of long SAN patch
4852           if (valid) {
4853             (void) CoordsToAlgebraic(boards[moveNum - 1],
4854                                      PosFlags(moveNum - 1),
4855                                      fromY, fromX, toY, toX, promoChar,
4856                                      parseList[moveNum-1]);
4857             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4858               case MT_NONE:
4859               case MT_STALEMATE:
4860               default:
4861                 break;
4862               case MT_CHECK:
4863                 if(!IS_SHOGI(gameInfo.variant))
4864                     strcat(parseList[moveNum - 1], "+");
4865                 break;
4866               case MT_CHECKMATE:
4867               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4868                 strcat(parseList[moveNum - 1], "#");
4869                 break;
4870             }
4871             strcat(parseList[moveNum - 1], " ");
4872             strcat(parseList[moveNum - 1], elapsed_time);
4873             /* currentMoveString is set as a side-effect of ParseOneMove */
4874             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4875             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4876             strcat(moveList[moveNum - 1], "\n");
4877
4878             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4879                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4880               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4881                 ChessSquare old, new = boards[moveNum][k][j];
4882                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4883                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4884                   if(old == new) continue;
4885                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4886                   else if(new == WhiteWazir || new == BlackWazir) {
4887                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4888                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4889                       else boards[moveNum][k][j] = old; // preserve type of Gold
4890                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4891                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4892               }
4893           } else {
4894             /* Move from ICS was illegal!?  Punt. */
4895             if (appData.debugMode) {
4896               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4897               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4898             }
4899             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4900             strcat(parseList[moveNum - 1], " ");
4901             strcat(parseList[moveNum - 1], elapsed_time);
4902             moveList[moveNum - 1][0] = NULLCHAR;
4903             fromX = fromY = toX = toY = -1;
4904           }
4905         }
4906   if (appData.debugMode) {
4907     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4908     setbuf(debugFP, NULL);
4909   }
4910
4911 #if ZIPPY
4912         /* Send move to chess program (BEFORE animating it). */
4913         if (appData.zippyPlay && !newGame && newMove &&
4914            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4915
4916             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4917                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4918                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4919                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4920                             move_str);
4921                     DisplayError(str, 0);
4922                 } else {
4923                     if (first.sendTime) {
4924                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4925                     }
4926                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4927                     if (firstMove && !bookHit) {
4928                         firstMove = FALSE;
4929                         if (first.useColors) {
4930                           SendToProgram(gameMode == IcsPlayingWhite ?
4931                                         "white\ngo\n" :
4932                                         "black\ngo\n", &first);
4933                         } else {
4934                           SendToProgram("go\n", &first);
4935                         }
4936                         first.maybeThinking = TRUE;
4937                     }
4938                 }
4939             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4940               if (moveList[moveNum - 1][0] == NULLCHAR) {
4941                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4942                 DisplayError(str, 0);
4943               } else {
4944                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4945                 SendMoveToProgram(moveNum - 1, &first);
4946               }
4947             }
4948         }
4949 #endif
4950     }
4951
4952     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4953         /* If move comes from a remote source, animate it.  If it
4954            isn't remote, it will have already been animated. */
4955         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4956             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4957         }
4958         if (!pausing && appData.highlightLastMove) {
4959             SetHighlights(fromX, fromY, toX, toY);
4960         }
4961     }
4962
4963     /* Start the clocks */
4964     whiteFlag = blackFlag = FALSE;
4965     appData.clockMode = !(basetime == 0 && increment == 0);
4966     if (ticking == 0) {
4967       ics_clock_paused = TRUE;
4968       StopClocks();
4969     } else if (ticking == 1) {
4970       ics_clock_paused = FALSE;
4971     }
4972     if (gameMode == IcsIdle ||
4973         relation == RELATION_OBSERVING_STATIC ||
4974         relation == RELATION_EXAMINING ||
4975         ics_clock_paused)
4976       DisplayBothClocks();
4977     else
4978       StartClocks();
4979
4980     /* Display opponents and material strengths */
4981     if (gameInfo.variant != VariantBughouse &&
4982         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4983         if (tinyLayout || smallLayout) {
4984             if(gameInfo.variant == VariantNormal)
4985               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4986                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4987                     basetime, increment);
4988             else
4989               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4990                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4991                     basetime, increment, (int) gameInfo.variant);
4992         } else {
4993             if(gameInfo.variant == VariantNormal)
4994               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4995                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4996                     basetime, increment);
4997             else
4998               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4999                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5000                     basetime, increment, VariantName(gameInfo.variant));
5001         }
5002         DisplayTitle(str);
5003   if (appData.debugMode) {
5004     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5005   }
5006     }
5007
5008
5009     /* Display the board */
5010     if (!pausing && !appData.noGUI) {
5011
5012       if (appData.premove)
5013           if (!gotPremove ||
5014              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5015              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5016               ClearPremoveHighlights();
5017
5018       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5019         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5020       DrawPosition(j, boards[currentMove]);
5021
5022       DisplayMove(moveNum - 1);
5023       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5024             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5025               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5026         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5027       }
5028     }
5029
5030     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5031 #if ZIPPY
5032     if(bookHit) { // [HGM] book: simulate book reply
5033         static char bookMove[MSG_SIZ]; // a bit generous?
5034
5035         programStats.nodes = programStats.depth = programStats.time =
5036         programStats.score = programStats.got_only_move = 0;
5037         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5038
5039         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5040         strcat(bookMove, bookHit);
5041         HandleMachineMove(bookMove, &first);
5042     }
5043 #endif
5044 }
5045
5046 void
5047 GetMoveListEvent ()
5048 {
5049     char buf[MSG_SIZ];
5050     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5051         ics_getting_history = H_REQUESTED;
5052         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5053         SendToICS(buf);
5054     }
5055 }
5056
5057 void
5058 SendToBoth (char *msg)
5059 {   // to make it easy to keep two engines in step in dual analysis
5060     SendToProgram(msg, &first);
5061     if(second.analyzing) SendToProgram(msg, &second);
5062 }
5063
5064 void
5065 AnalysisPeriodicEvent (int force)
5066 {
5067     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5068          && !force) || !appData.periodicUpdates)
5069       return;
5070
5071     /* Send . command to Crafty to collect stats */
5072     SendToBoth(".\n");
5073
5074     /* Don't send another until we get a response (this makes
5075        us stop sending to old Crafty's which don't understand
5076        the "." command (sending illegal cmds resets node count & time,
5077        which looks bad)) */
5078     programStats.ok_to_send = 0;
5079 }
5080
5081 void
5082 ics_update_width (int new_width)
5083 {
5084         ics_printf("set width %d\n", new_width);
5085 }
5086
5087 void
5088 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5089 {
5090     char buf[MSG_SIZ];
5091
5092     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5093         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5094             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5095             SendToProgram(buf, cps);
5096             return;
5097         }
5098         // null move in variant where engine does not understand it (for analysis purposes)
5099         SendBoard(cps, moveNum + 1); // send position after move in stead.
5100         return;
5101     }
5102     if (cps->useUsermove) {
5103       SendToProgram("usermove ", cps);
5104     }
5105     if (cps->useSAN) {
5106       char *space;
5107       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5108         int len = space - parseList[moveNum];
5109         memcpy(buf, parseList[moveNum], len);
5110         buf[len++] = '\n';
5111         buf[len] = NULLCHAR;
5112       } else {
5113         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5114       }
5115       SendToProgram(buf, cps);
5116     } else {
5117       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5118         AlphaRank(moveList[moveNum], 4);
5119         SendToProgram(moveList[moveNum], cps);
5120         AlphaRank(moveList[moveNum], 4); // and back
5121       } else
5122       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5123        * the engine. It would be nice to have a better way to identify castle
5124        * moves here. */
5125       if(appData.fischerCastling && cps->useOOCastle) {
5126         int fromX = moveList[moveNum][0] - AAA;
5127         int fromY = moveList[moveNum][1] - ONE;
5128         int toX = moveList[moveNum][2] - AAA;
5129         int toY = moveList[moveNum][3] - ONE;
5130         if((boards[moveNum][fromY][fromX] == WhiteKing
5131             && boards[moveNum][toY][toX] == WhiteRook)
5132            || (boards[moveNum][fromY][fromX] == BlackKing
5133                && boards[moveNum][toY][toX] == BlackRook)) {
5134           if(toX > fromX) SendToProgram("O-O\n", cps);
5135           else SendToProgram("O-O-O\n", cps);
5136         }
5137         else SendToProgram(moveList[moveNum], cps);
5138       } else
5139       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5140           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5141                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5142                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5143                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5144           SendToProgram(buf, cps);
5145       } else
5146       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5147         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5148           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5149           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5150                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5151         } else
5152           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5153                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5154         SendToProgram(buf, cps);
5155       }
5156       else SendToProgram(moveList[moveNum], cps);
5157       /* End of additions by Tord */
5158     }
5159
5160     /* [HGM] setting up the opening has brought engine in force mode! */
5161     /*       Send 'go' if we are in a mode where machine should play. */
5162     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5163         (gameMode == TwoMachinesPlay   ||
5164 #if ZIPPY
5165          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5166 #endif
5167          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5168         SendToProgram("go\n", cps);
5169   if (appData.debugMode) {
5170     fprintf(debugFP, "(extra)\n");
5171   }
5172     }
5173     setboardSpoiledMachineBlack = 0;
5174 }
5175
5176 void
5177 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5178 {
5179     char user_move[MSG_SIZ];
5180     char suffix[4];
5181
5182     if(gameInfo.variant == VariantSChess && promoChar) {
5183         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5184         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5185     } else suffix[0] = NULLCHAR;
5186
5187     switch (moveType) {
5188       default:
5189         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5190                 (int)moveType, fromX, fromY, toX, toY);
5191         DisplayError(user_move + strlen("say "), 0);
5192         break;
5193       case WhiteKingSideCastle:
5194       case BlackKingSideCastle:
5195       case WhiteQueenSideCastleWild:
5196       case BlackQueenSideCastleWild:
5197       /* PUSH Fabien */
5198       case WhiteHSideCastleFR:
5199       case BlackHSideCastleFR:
5200       /* POP Fabien */
5201         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5202         break;
5203       case WhiteQueenSideCastle:
5204       case BlackQueenSideCastle:
5205       case WhiteKingSideCastleWild:
5206       case BlackKingSideCastleWild:
5207       /* PUSH Fabien */
5208       case WhiteASideCastleFR:
5209       case BlackASideCastleFR:
5210       /* POP Fabien */
5211         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5212         break;
5213       case WhiteNonPromotion:
5214       case BlackNonPromotion:
5215         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5216         break;
5217       case WhitePromotion:
5218       case BlackPromotion:
5219         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5220            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5221           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5222                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5223                 PieceToChar(WhiteFerz));
5224         else if(gameInfo.variant == VariantGreat)
5225           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5227                 PieceToChar(WhiteMan));
5228         else
5229           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5231                 promoChar);
5232         break;
5233       case WhiteDrop:
5234       case BlackDrop:
5235       drop:
5236         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5237                  ToUpper(PieceToChar((ChessSquare) fromX)),
5238                  AAA + toX, ONE + toY);
5239         break;
5240       case IllegalMove:  /* could be a variant we don't quite understand */
5241         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5242       case NormalMove:
5243       case WhiteCapturesEnPassant:
5244       case BlackCapturesEnPassant:
5245         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5246                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5247         break;
5248     }
5249     SendToICS(user_move);
5250     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5251         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5252 }
5253
5254 void
5255 UploadGameEvent ()
5256 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5257     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5258     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5259     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5260       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5261       return;
5262     }
5263     if(gameMode != IcsExamining) { // is this ever not the case?
5264         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5265
5266         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5267           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5268         } else { // on FICS we must first go to general examine mode
5269           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5270         }
5271         if(gameInfo.variant != VariantNormal) {
5272             // try figure out wild number, as xboard names are not always valid on ICS
5273             for(i=1; i<=36; i++) {
5274               snprintf(buf, MSG_SIZ, "wild/%d", i);
5275                 if(StringToVariant(buf) == gameInfo.variant) break;
5276             }
5277             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5278             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5279             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5280         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5281         SendToICS(ics_prefix);
5282         SendToICS(buf);
5283         if(startedFromSetupPosition || backwardMostMove != 0) {
5284           fen = PositionToFEN(backwardMostMove, NULL, 1);
5285           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5286             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5287             SendToICS(buf);
5288           } else { // FICS: everything has to set by separate bsetup commands
5289             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5290             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5291             SendToICS(buf);
5292             if(!WhiteOnMove(backwardMostMove)) {
5293                 SendToICS("bsetup tomove black\n");
5294             }
5295             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5296             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5297             SendToICS(buf);
5298             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5299             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5300             SendToICS(buf);
5301             i = boards[backwardMostMove][EP_STATUS];
5302             if(i >= 0) { // set e.p.
5303               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5304                 SendToICS(buf);
5305             }
5306             bsetup++;
5307           }
5308         }
5309       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5310             SendToICS("bsetup done\n"); // switch to normal examining.
5311     }
5312     for(i = backwardMostMove; i<last; i++) {
5313         char buf[20];
5314         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5315         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5316             int len = strlen(moveList[i]);
5317             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5318             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5319         }
5320         SendToICS(buf);
5321     }
5322     SendToICS(ics_prefix);
5323     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5324 }
5325
5326 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5327
5328 void
5329 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5330 {
5331     if (rf == DROP_RANK) {
5332       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5333       sprintf(move, "%c@%c%c\n",
5334                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5335     } else {
5336         if (promoChar == 'x' || promoChar == NULLCHAR) {
5337           sprintf(move, "%c%c%c%c\n",
5338                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5339           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5340         } else {
5341             sprintf(move, "%c%c%c%c%c\n",
5342                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5343         }
5344     }
5345 }
5346
5347 void
5348 ProcessICSInitScript (FILE *f)
5349 {
5350     char buf[MSG_SIZ];
5351
5352     while (fgets(buf, MSG_SIZ, f)) {
5353         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5354     }
5355
5356     fclose(f);
5357 }
5358
5359
5360 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5361 int dragging;
5362 static ClickType lastClickType;
5363
5364 int
5365 Partner (ChessSquare *p)
5366 { // change piece into promotion partner if one shogi-promotes to the other
5367   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5368   ChessSquare partner;
5369   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5370   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5371   *p = partner;
5372   return 1;
5373 }
5374
5375 void
5376 Sweep (int step)
5377 {
5378     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5379     static int toggleFlag;
5380     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5381     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5382     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5383     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5384     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5385     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5386     do {
5387         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5388         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5389         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5390         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5391         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5392         if(!step) step = -1;
5393     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5394             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5395             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5396             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5397     if(toX >= 0) {
5398         int victim = boards[currentMove][toY][toX];
5399         boards[currentMove][toY][toX] = promoSweep;
5400         DrawPosition(FALSE, boards[currentMove]);
5401         boards[currentMove][toY][toX] = victim;
5402     } else
5403     ChangeDragPiece(promoSweep);
5404 }
5405
5406 int
5407 PromoScroll (int x, int y)
5408 {
5409   int step = 0;
5410
5411   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5412   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5413   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5414   if(!step) return FALSE;
5415   lastX = x; lastY = y;
5416   if((promoSweep < BlackPawn) == flipView) step = -step;
5417   if(step > 0) selectFlag = 1;
5418   if(!selectFlag) Sweep(step);
5419   return FALSE;
5420 }
5421
5422 void
5423 NextPiece (int step)
5424 {
5425     ChessSquare piece = boards[currentMove][toY][toX];
5426     do {
5427         pieceSweep -= step;
5428         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5429         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5430         if(!step) step = -1;
5431     } while(PieceToChar(pieceSweep) == '.');
5432     boards[currentMove][toY][toX] = pieceSweep;
5433     DrawPosition(FALSE, boards[currentMove]);
5434     boards[currentMove][toY][toX] = piece;
5435 }
5436 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5437 void
5438 AlphaRank (char *move, int n)
5439 {
5440 //    char *p = move, c; int x, y;
5441
5442     if (appData.debugMode) {
5443         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5444     }
5445
5446     if(move[1]=='*' &&
5447        move[2]>='0' && move[2]<='9' &&
5448        move[3]>='a' && move[3]<='x'    ) {
5449         move[1] = '@';
5450         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5451         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5452     } else
5453     if(move[0]>='0' && move[0]<='9' &&
5454        move[1]>='a' && move[1]<='x' &&
5455        move[2]>='0' && move[2]<='9' &&
5456        move[3]>='a' && move[3]<='x'    ) {
5457         /* input move, Shogi -> normal */
5458         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5459         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5460         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5461         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5462     } else
5463     if(move[1]=='@' &&
5464        move[3]>='0' && move[3]<='9' &&
5465        move[2]>='a' && move[2]<='x'    ) {
5466         move[1] = '*';
5467         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5468         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5469     } else
5470     if(
5471        move[0]>='a' && move[0]<='x' &&
5472        move[3]>='0' && move[3]<='9' &&
5473        move[2]>='a' && move[2]<='x'    ) {
5474          /* output move, normal -> Shogi */
5475         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5476         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5477         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5478         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5479         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5480     }
5481     if (appData.debugMode) {
5482         fprintf(debugFP, "   out = '%s'\n", move);
5483     }
5484 }
5485
5486 char yy_textstr[8000];
5487
5488 /* Parser for moves from gnuchess, ICS, or user typein box */
5489 Boolean
5490 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5491 {
5492     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5493
5494     switch (*moveType) {
5495       case WhitePromotion:
5496       case BlackPromotion:
5497       case WhiteNonPromotion:
5498       case BlackNonPromotion:
5499       case NormalMove:
5500       case FirstLeg:
5501       case WhiteCapturesEnPassant:
5502       case BlackCapturesEnPassant:
5503       case WhiteKingSideCastle:
5504       case WhiteQueenSideCastle:
5505       case BlackKingSideCastle:
5506       case BlackQueenSideCastle:
5507       case WhiteKingSideCastleWild:
5508       case WhiteQueenSideCastleWild:
5509       case BlackKingSideCastleWild:
5510       case BlackQueenSideCastleWild:
5511       /* Code added by Tord: */
5512       case WhiteHSideCastleFR:
5513       case WhiteASideCastleFR:
5514       case BlackHSideCastleFR:
5515       case BlackASideCastleFR:
5516       /* End of code added by Tord */
5517       case IllegalMove:         /* bug or odd chess variant */
5518         *fromX = currentMoveString[0] - AAA;
5519         *fromY = currentMoveString[1] - ONE;
5520         *toX = currentMoveString[2] - AAA;
5521         *toY = currentMoveString[3] - ONE;
5522         *promoChar = currentMoveString[4];
5523         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5524             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5525     if (appData.debugMode) {
5526         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5527     }
5528             *fromX = *fromY = *toX = *toY = 0;
5529             return FALSE;
5530         }
5531         if (appData.testLegality) {
5532           return (*moveType != IllegalMove);
5533         } else {
5534           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5535                          // [HGM] lion: if this is a double move we are less critical
5536                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5537         }
5538
5539       case WhiteDrop:
5540       case BlackDrop:
5541         *fromX = *moveType == WhiteDrop ?
5542           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5543           (int) CharToPiece(ToLower(currentMoveString[0]));
5544         *fromY = DROP_RANK;
5545         *toX = currentMoveString[2] - AAA;
5546         *toY = currentMoveString[3] - ONE;
5547         *promoChar = NULLCHAR;
5548         return TRUE;
5549
5550       case AmbiguousMove:
5551       case ImpossibleMove:
5552       case EndOfFile:
5553       case ElapsedTime:
5554       case Comment:
5555       case PGNTag:
5556       case NAG:
5557       case WhiteWins:
5558       case BlackWins:
5559       case GameIsDrawn:
5560       default:
5561     if (appData.debugMode) {
5562         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5563     }
5564         /* bug? */
5565         *fromX = *fromY = *toX = *toY = 0;
5566         *promoChar = NULLCHAR;
5567         return FALSE;
5568     }
5569 }
5570
5571 Boolean pushed = FALSE;
5572 char *lastParseAttempt;
5573
5574 void
5575 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5576 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5577   int fromX, fromY, toX, toY; char promoChar;
5578   ChessMove moveType;
5579   Boolean valid;
5580   int nr = 0;
5581
5582   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5583   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5584     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5585     pushed = TRUE;
5586   }
5587   endPV = forwardMostMove;
5588   do {
5589     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5590     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5591     lastParseAttempt = pv;
5592     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5593     if(!valid && nr == 0 &&
5594        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5595         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5596         // Hande case where played move is different from leading PV move
5597         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5598         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5599         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5600         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5601           endPV += 2; // if position different, keep this
5602           moveList[endPV-1][0] = fromX + AAA;
5603           moveList[endPV-1][1] = fromY + ONE;
5604           moveList[endPV-1][2] = toX + AAA;
5605           moveList[endPV-1][3] = toY + ONE;
5606           parseList[endPV-1][0] = NULLCHAR;
5607           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5608         }
5609       }
5610     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5611     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5612     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5613     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5614         valid++; // allow comments in PV
5615         continue;
5616     }
5617     nr++;
5618     if(endPV+1 > framePtr) break; // no space, truncate
5619     if(!valid) break;
5620     endPV++;
5621     CopyBoard(boards[endPV], boards[endPV-1]);
5622     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5623     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5624     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5625     CoordsToAlgebraic(boards[endPV - 1],
5626                              PosFlags(endPV - 1),
5627                              fromY, fromX, toY, toX, promoChar,
5628                              parseList[endPV - 1]);
5629   } while(valid);
5630   if(atEnd == 2) return; // used hidden, for PV conversion
5631   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5632   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5633   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5634                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5635   DrawPosition(TRUE, boards[currentMove]);
5636 }
5637
5638 int
5639 MultiPV (ChessProgramState *cps)
5640 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5641         int i;
5642         for(i=0; i<cps->nrOptions; i++)
5643             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5644                 return i;
5645         return -1;
5646 }
5647
5648 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5649
5650 Boolean
5651 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5652 {
5653         int startPV, multi, lineStart, origIndex = index;
5654         char *p, buf2[MSG_SIZ];
5655         ChessProgramState *cps = (pane ? &second : &first);
5656
5657         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5658         lastX = x; lastY = y;
5659         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5660         lineStart = startPV = index;
5661         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5662         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5663         index = startPV;
5664         do{ while(buf[index] && buf[index] != '\n') index++;
5665         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5666         buf[index] = 0;
5667         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5668                 int n = cps->option[multi].value;
5669                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5670                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5671                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5672                 cps->option[multi].value = n;
5673                 *start = *end = 0;
5674                 return FALSE;
5675         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5676                 ExcludeClick(origIndex - lineStart);
5677                 return FALSE;
5678         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5679                 Collapse(origIndex - lineStart);
5680                 return FALSE;
5681         }
5682         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5683         *start = startPV; *end = index-1;
5684         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5685         return TRUE;
5686 }
5687
5688 char *
5689 PvToSAN (char *pv)
5690 {
5691         static char buf[10*MSG_SIZ];
5692         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5693         *buf = NULLCHAR;
5694         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5695         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5696         for(i = forwardMostMove; i<endPV; i++){
5697             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5698             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5699             k += strlen(buf+k);
5700         }
5701         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5702         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5703         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5704         endPV = savedEnd;
5705         return buf;
5706 }
5707
5708 Boolean
5709 LoadPV (int x, int y)
5710 { // called on right mouse click to load PV
5711   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5712   lastX = x; lastY = y;
5713   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5714   extendGame = FALSE;
5715   return TRUE;
5716 }
5717
5718 void
5719 UnLoadPV ()
5720 {
5721   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5722   if(endPV < 0) return;
5723   if(appData.autoCopyPV) CopyFENToClipboard();
5724   endPV = -1;
5725   if(extendGame && currentMove > forwardMostMove) {
5726         Boolean saveAnimate = appData.animate;
5727         if(pushed) {
5728             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5729                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5730             } else storedGames--; // abandon shelved tail of original game
5731         }
5732         pushed = FALSE;
5733         forwardMostMove = currentMove;
5734         currentMove = oldFMM;
5735         appData.animate = FALSE;
5736         ToNrEvent(forwardMostMove);
5737         appData.animate = saveAnimate;
5738   }
5739   currentMove = forwardMostMove;
5740   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5741   ClearPremoveHighlights();
5742   DrawPosition(TRUE, boards[currentMove]);
5743 }
5744
5745 void
5746 MovePV (int x, int y, int h)
5747 { // step through PV based on mouse coordinates (called on mouse move)
5748   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5749
5750   // we must somehow check if right button is still down (might be released off board!)
5751   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5752   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5753   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5754   if(!step) return;
5755   lastX = x; lastY = y;
5756
5757   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5758   if(endPV < 0) return;
5759   if(y < margin) step = 1; else
5760   if(y > h - margin) step = -1;
5761   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5762   currentMove += step;
5763   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5764   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5765                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5766   DrawPosition(FALSE, boards[currentMove]);
5767 }
5768
5769
5770 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5771 // All positions will have equal probability, but the current method will not provide a unique
5772 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5773 #define DARK 1
5774 #define LITE 2
5775 #define ANY 3
5776
5777 int squaresLeft[4];
5778 int piecesLeft[(int)BlackPawn];
5779 int seed, nrOfShuffles;
5780
5781 void
5782 GetPositionNumber ()
5783 {       // sets global variable seed
5784         int i;
5785
5786         seed = appData.defaultFrcPosition;
5787         if(seed < 0) { // randomize based on time for negative FRC position numbers
5788                 for(i=0; i<50; i++) seed += random();
5789                 seed = random() ^ random() >> 8 ^ random() << 8;
5790                 if(seed<0) seed = -seed;
5791         }
5792 }
5793
5794 int
5795 put (Board board, int pieceType, int rank, int n, int shade)
5796 // put the piece on the (n-1)-th empty squares of the given shade
5797 {
5798         int i;
5799
5800         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5801                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5802                         board[rank][i] = (ChessSquare) pieceType;
5803                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5804                         squaresLeft[ANY]--;
5805                         piecesLeft[pieceType]--;
5806                         return i;
5807                 }
5808         }
5809         return -1;
5810 }
5811
5812
5813 void
5814 AddOnePiece (Board board, int pieceType, int rank, int shade)
5815 // calculate where the next piece goes, (any empty square), and put it there
5816 {
5817         int i;
5818
5819         i = seed % squaresLeft[shade];
5820         nrOfShuffles *= squaresLeft[shade];
5821         seed /= squaresLeft[shade];
5822         put(board, pieceType, rank, i, shade);
5823 }
5824
5825 void
5826 AddTwoPieces (Board board, int pieceType, int rank)
5827 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5828 {
5829         int i, n=squaresLeft[ANY], j=n-1, k;
5830
5831         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5832         i = seed % k;  // pick one
5833         nrOfShuffles *= k;
5834         seed /= k;
5835         while(i >= j) i -= j--;
5836         j = n - 1 - j; i += j;
5837         put(board, pieceType, rank, j, ANY);
5838         put(board, pieceType, rank, i, ANY);
5839 }
5840
5841 void
5842 SetUpShuffle (Board board, int number)
5843 {
5844         int i, p, first=1;
5845
5846         GetPositionNumber(); nrOfShuffles = 1;
5847
5848         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5849         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5850         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5851
5852         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5853
5854         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5855             p = (int) board[0][i];
5856             if(p < (int) BlackPawn) piecesLeft[p] ++;
5857             board[0][i] = EmptySquare;
5858         }
5859
5860         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5861             // shuffles restricted to allow normal castling put KRR first
5862             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5863                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5864             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5865                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5866             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5867                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5868             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5869                 put(board, WhiteRook, 0, 0, ANY);
5870             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5871         }
5872
5873         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5874             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5875             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5876                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5877                 while(piecesLeft[p] >= 2) {
5878                     AddOnePiece(board, p, 0, LITE);
5879                     AddOnePiece(board, p, 0, DARK);
5880                 }
5881                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5882             }
5883
5884         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5885             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5886             // but we leave King and Rooks for last, to possibly obey FRC restriction
5887             if(p == (int)WhiteRook) continue;
5888             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5889             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5890         }
5891
5892         // now everything is placed, except perhaps King (Unicorn) and Rooks
5893
5894         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5895             // Last King gets castling rights
5896             while(piecesLeft[(int)WhiteUnicorn]) {
5897                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5898                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5899             }
5900
5901             while(piecesLeft[(int)WhiteKing]) {
5902                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5903                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5904             }
5905
5906
5907         } else {
5908             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5909             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5910         }
5911
5912         // Only Rooks can be left; simply place them all
5913         while(piecesLeft[(int)WhiteRook]) {
5914                 i = put(board, WhiteRook, 0, 0, ANY);
5915                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5916                         if(first) {
5917                                 first=0;
5918                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5919                         }
5920                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5921                 }
5922         }
5923         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5924             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5925         }
5926
5927         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5928 }
5929
5930 int
5931 SetCharTable (char *table, const char * map)
5932 /* [HGM] moved here from winboard.c because of its general usefulness */
5933 /*       Basically a safe strcpy that uses the last character as King */
5934 {
5935     int result = FALSE; int NrPieces;
5936
5937     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5938                     && NrPieces >= 12 && !(NrPieces&1)) {
5939         int i; /* [HGM] Accept even length from 12 to 34 */
5940
5941         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5942         for( i=0; i<NrPieces/2-1; i++ ) {
5943             table[i] = map[i];
5944             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5945         }
5946         table[(int) WhiteKing]  = map[NrPieces/2-1];
5947         table[(int) BlackKing]  = map[NrPieces-1];
5948
5949         result = TRUE;
5950     }
5951
5952     return result;
5953 }
5954
5955 void
5956 Prelude (Board board)
5957 {       // [HGM] superchess: random selection of exo-pieces
5958         int i, j, k; ChessSquare p;
5959         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5960
5961         GetPositionNumber(); // use FRC position number
5962
5963         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5964             SetCharTable(pieceToChar, appData.pieceToCharTable);
5965             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5966                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5967         }
5968
5969         j = seed%4;                 seed /= 4;
5970         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5971         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5972         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5973         j = seed%3 + (seed%3 >= j); seed /= 3;
5974         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5975         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5976         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5977         j = seed%3;                 seed /= 3;
5978         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5979         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5980         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5981         j = seed%2 + (seed%2 >= j); seed /= 2;
5982         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5983         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5984         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5985         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5986         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5987         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5988         put(board, exoPieces[0],    0, 0, ANY);
5989         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5990 }
5991
5992 void
5993 InitPosition (int redraw)
5994 {
5995     ChessSquare (* pieces)[BOARD_FILES];
5996     int i, j, pawnRow=1, pieceRows=1, overrule,
5997     oldx = gameInfo.boardWidth,
5998     oldy = gameInfo.boardHeight,
5999     oldh = gameInfo.holdingsWidth;
6000     static int oldv;
6001
6002     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6003
6004     /* [AS] Initialize pv info list [HGM] and game status */
6005     {
6006         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6007             pvInfoList[i].depth = 0;
6008             boards[i][EP_STATUS] = EP_NONE;
6009             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6010         }
6011
6012         initialRulePlies = 0; /* 50-move counter start */
6013
6014         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6015         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6016     }
6017
6018
6019     /* [HGM] logic here is completely changed. In stead of full positions */
6020     /* the initialized data only consist of the two backranks. The switch */
6021     /* selects which one we will use, which is than copied to the Board   */
6022     /* initialPosition, which for the rest is initialized by Pawns and    */
6023     /* empty squares. This initial position is then copied to boards[0],  */
6024     /* possibly after shuffling, so that it remains available.            */
6025
6026     gameInfo.holdingsWidth = 0; /* default board sizes */
6027     gameInfo.boardWidth    = 8;
6028     gameInfo.boardHeight   = 8;
6029     gameInfo.holdingsSize  = 0;
6030     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6031     for(i=0; i<BOARD_FILES-2; i++)
6032       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6033     initialPosition[EP_STATUS] = EP_NONE;
6034     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6035     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6036          SetCharTable(pieceNickName, appData.pieceNickNames);
6037     else SetCharTable(pieceNickName, "............");
6038     pieces = FIDEArray;
6039
6040     switch (gameInfo.variant) {
6041     case VariantFischeRandom:
6042       shuffleOpenings = TRUE;
6043       appData.fischerCastling = TRUE;
6044     default:
6045       break;
6046     case VariantShatranj:
6047       pieces = ShatranjArray;
6048       nrCastlingRights = 0;
6049       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6050       break;
6051     case VariantMakruk:
6052       pieces = makrukArray;
6053       nrCastlingRights = 0;
6054       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6055       break;
6056     case VariantASEAN:
6057       pieces = aseanArray;
6058       nrCastlingRights = 0;
6059       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6060       break;
6061     case VariantTwoKings:
6062       pieces = twoKingsArray;
6063       break;
6064     case VariantGrand:
6065       pieces = GrandArray;
6066       nrCastlingRights = 0;
6067       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6068       gameInfo.boardWidth = 10;
6069       gameInfo.boardHeight = 10;
6070       gameInfo.holdingsSize = 7;
6071       break;
6072     case VariantCapaRandom:
6073       shuffleOpenings = TRUE;
6074       appData.fischerCastling = TRUE;
6075     case VariantCapablanca:
6076       pieces = CapablancaArray;
6077       gameInfo.boardWidth = 10;
6078       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6079       break;
6080     case VariantGothic:
6081       pieces = GothicArray;
6082       gameInfo.boardWidth = 10;
6083       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6084       break;
6085     case VariantSChess:
6086       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6087       gameInfo.holdingsSize = 7;
6088       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6089       break;
6090     case VariantJanus:
6091       pieces = JanusArray;
6092       gameInfo.boardWidth = 10;
6093       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6094       nrCastlingRights = 6;
6095         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6096         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6097         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6098         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6099         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6100         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6101       break;
6102     case VariantFalcon:
6103       pieces = FalconArray;
6104       gameInfo.boardWidth = 10;
6105       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6106       break;
6107     case VariantXiangqi:
6108       pieces = XiangqiArray;
6109       gameInfo.boardWidth  = 9;
6110       gameInfo.boardHeight = 10;
6111       nrCastlingRights = 0;
6112       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6113       break;
6114     case VariantShogi:
6115       pieces = ShogiArray;
6116       gameInfo.boardWidth  = 9;
6117       gameInfo.boardHeight = 9;
6118       gameInfo.holdingsSize = 7;
6119       nrCastlingRights = 0;
6120       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6121       break;
6122     case VariantChu:
6123       pieces = ChuArray; pieceRows = 3;
6124       gameInfo.boardWidth  = 12;
6125       gameInfo.boardHeight = 12;
6126       nrCastlingRights = 0;
6127       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6128                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6129       break;
6130     case VariantCourier:
6131       pieces = CourierArray;
6132       gameInfo.boardWidth  = 12;
6133       nrCastlingRights = 0;
6134       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6135       break;
6136     case VariantKnightmate:
6137       pieces = KnightmateArray;
6138       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6139       break;
6140     case VariantSpartan:
6141       pieces = SpartanArray;
6142       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6143       break;
6144     case VariantLion:
6145       pieces = lionArray;
6146       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6147       break;
6148     case VariantChuChess:
6149       pieces = ChuChessArray;
6150       gameInfo.boardWidth = 10;
6151       gameInfo.boardHeight = 10;
6152       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6153       break;
6154     case VariantFairy:
6155       pieces = fairyArray;
6156       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6157       break;
6158     case VariantGreat:
6159       pieces = GreatArray;
6160       gameInfo.boardWidth = 10;
6161       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6162       gameInfo.holdingsSize = 8;
6163       break;
6164     case VariantSuper:
6165       pieces = FIDEArray;
6166       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6167       gameInfo.holdingsSize = 8;
6168       startedFromSetupPosition = TRUE;
6169       break;
6170     case VariantCrazyhouse:
6171     case VariantBughouse:
6172       pieces = FIDEArray;
6173       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6174       gameInfo.holdingsSize = 5;
6175       break;
6176     case VariantWildCastle:
6177       pieces = FIDEArray;
6178       /* !!?shuffle with kings guaranteed to be on d or e file */
6179       shuffleOpenings = 1;
6180       break;
6181     case VariantNoCastle:
6182       pieces = FIDEArray;
6183       nrCastlingRights = 0;
6184       /* !!?unconstrained back-rank shuffle */
6185       shuffleOpenings = 1;
6186       break;
6187     }
6188
6189     overrule = 0;
6190     if(appData.NrFiles >= 0) {
6191         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6192         gameInfo.boardWidth = appData.NrFiles;
6193     }
6194     if(appData.NrRanks >= 0) {
6195         gameInfo.boardHeight = appData.NrRanks;
6196     }
6197     if(appData.holdingsSize >= 0) {
6198         i = appData.holdingsSize;
6199         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6200         gameInfo.holdingsSize = i;
6201     }
6202     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6203     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6204         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6205
6206     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6207     if(pawnRow < 1) pawnRow = 1;
6208     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6209        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6210     if(gameInfo.variant == VariantChu) pawnRow = 3;
6211
6212     /* User pieceToChar list overrules defaults */
6213     if(appData.pieceToCharTable != NULL)
6214         SetCharTable(pieceToChar, appData.pieceToCharTable);
6215
6216     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6217
6218         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6219             s = (ChessSquare) 0; /* account holding counts in guard band */
6220         for( i=0; i<BOARD_HEIGHT; i++ )
6221             initialPosition[i][j] = s;
6222
6223         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6224         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6225         initialPosition[pawnRow][j] = WhitePawn;
6226         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6227         if(gameInfo.variant == VariantXiangqi) {
6228             if(j&1) {
6229                 initialPosition[pawnRow][j] =
6230                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6231                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6232                    initialPosition[2][j] = WhiteCannon;
6233                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6234                 }
6235             }
6236         }
6237         if(gameInfo.variant == VariantChu) {
6238              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6239                initialPosition[pawnRow+1][j] = WhiteCobra,
6240                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6241              for(i=1; i<pieceRows; i++) {
6242                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6243                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6244              }
6245         }
6246         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6247             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6248                initialPosition[0][j] = WhiteRook;
6249                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6250             }
6251         }
6252         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6253     }
6254     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6255     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6256
6257             j=BOARD_LEFT+1;
6258             initialPosition[1][j] = WhiteBishop;
6259             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6260             j=BOARD_RGHT-2;
6261             initialPosition[1][j] = WhiteRook;
6262             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6263     }
6264
6265     if( nrCastlingRights == -1) {
6266         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6267         /*       This sets default castling rights from none to normal corners   */
6268         /* Variants with other castling rights must set them themselves above    */
6269         nrCastlingRights = 6;
6270
6271         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6272         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6273         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6274         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6275         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6276         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6277      }
6278
6279      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6280      if(gameInfo.variant == VariantGreat) { // promotion commoners
6281         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6282         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6283         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6284         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6285      }
6286      if( gameInfo.variant == VariantSChess ) {
6287       initialPosition[1][0] = BlackMarshall;
6288       initialPosition[2][0] = BlackAngel;
6289       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6290       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6291       initialPosition[1][1] = initialPosition[2][1] =
6292       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6293      }
6294   if (appData.debugMode) {
6295     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6296   }
6297     if(shuffleOpenings) {
6298         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6299         startedFromSetupPosition = TRUE;
6300     }
6301     if(startedFromPositionFile) {
6302       /* [HGM] loadPos: use PositionFile for every new game */
6303       CopyBoard(initialPosition, filePosition);
6304       for(i=0; i<nrCastlingRights; i++)
6305           initialRights[i] = filePosition[CASTLING][i];
6306       startedFromSetupPosition = TRUE;
6307     }
6308
6309     CopyBoard(boards[0], initialPosition);
6310
6311     if(oldx != gameInfo.boardWidth ||
6312        oldy != gameInfo.boardHeight ||
6313        oldv != gameInfo.variant ||
6314        oldh != gameInfo.holdingsWidth
6315                                          )
6316             InitDrawingSizes(-2 ,0);
6317
6318     oldv = gameInfo.variant;
6319     if (redraw)
6320       DrawPosition(TRUE, boards[currentMove]);
6321 }
6322
6323 void
6324 SendBoard (ChessProgramState *cps, int moveNum)
6325 {
6326     char message[MSG_SIZ];
6327
6328     if (cps->useSetboard) {
6329       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6330       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6331       SendToProgram(message, cps);
6332       free(fen);
6333
6334     } else {
6335       ChessSquare *bp;
6336       int i, j, left=0, right=BOARD_WIDTH;
6337       /* Kludge to set black to move, avoiding the troublesome and now
6338        * deprecated "black" command.
6339        */
6340       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6341         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6342
6343       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6344
6345       SendToProgram("edit\n", cps);
6346       SendToProgram("#\n", cps);
6347       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6348         bp = &boards[moveNum][i][left];
6349         for (j = left; j < right; j++, bp++) {
6350           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6351           if ((int) *bp < (int) BlackPawn) {
6352             if(j == BOARD_RGHT+1)
6353                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6354             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6355             if(message[0] == '+' || message[0] == '~') {
6356               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6357                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6358                         AAA + j, ONE + i);
6359             }
6360             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6361                 message[1] = BOARD_RGHT   - 1 - j + '1';
6362                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6363             }
6364             SendToProgram(message, cps);
6365           }
6366         }
6367       }
6368
6369       SendToProgram("c\n", cps);
6370       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6371         bp = &boards[moveNum][i][left];
6372         for (j = left; j < right; j++, bp++) {
6373           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6374           if (((int) *bp != (int) EmptySquare)
6375               && ((int) *bp >= (int) BlackPawn)) {
6376             if(j == BOARD_LEFT-2)
6377                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6378             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6379                     AAA + j, ONE + i);
6380             if(message[0] == '+' || message[0] == '~') {
6381               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6382                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6383                         AAA + j, ONE + i);
6384             }
6385             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6386                 message[1] = BOARD_RGHT   - 1 - j + '1';
6387                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6388             }
6389             SendToProgram(message, cps);
6390           }
6391         }
6392       }
6393
6394       SendToProgram(".\n", cps);
6395     }
6396     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6397 }
6398
6399 char exclusionHeader[MSG_SIZ];
6400 int exCnt, excludePtr;
6401 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6402 static Exclusion excluTab[200];
6403 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6404
6405 static void
6406 WriteMap (int s)
6407 {
6408     int j;
6409     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6410     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6411 }
6412
6413 static void
6414 ClearMap ()
6415 {
6416     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6417     excludePtr = 24; exCnt = 0;
6418     WriteMap(0);
6419 }
6420
6421 static void
6422 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6423 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6424     char buf[2*MOVE_LEN], *p;
6425     Exclusion *e = excluTab;
6426     int i;
6427     for(i=0; i<exCnt; i++)
6428         if(e[i].ff == fromX && e[i].fr == fromY &&
6429            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6430     if(i == exCnt) { // was not in exclude list; add it
6431         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6432         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6433             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6434             return; // abort
6435         }
6436         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6437         excludePtr++; e[i].mark = excludePtr++;
6438         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6439         exCnt++;
6440     }
6441     exclusionHeader[e[i].mark] = state;
6442 }
6443
6444 static int
6445 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6446 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6447     char buf[MSG_SIZ];
6448     int j, k;
6449     ChessMove moveType;
6450     if((signed char)promoChar == -1) { // kludge to indicate best move
6451         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6452             return 1; // if unparsable, abort
6453     }
6454     // update exclusion map (resolving toggle by consulting existing state)
6455     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6456     j = k%8; k >>= 3;
6457     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6458     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6459          excludeMap[k] |=   1<<j;
6460     else excludeMap[k] &= ~(1<<j);
6461     // update header
6462     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6463     // inform engine
6464     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6465     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6466     SendToBoth(buf);
6467     return (state == '+');
6468 }
6469
6470 static void
6471 ExcludeClick (int index)
6472 {
6473     int i, j;
6474     Exclusion *e = excluTab;
6475     if(index < 25) { // none, best or tail clicked
6476         if(index < 13) { // none: include all
6477             WriteMap(0); // clear map
6478             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6479             SendToBoth("include all\n"); // and inform engine
6480         } else if(index > 18) { // tail
6481             if(exclusionHeader[19] == '-') { // tail was excluded
6482                 SendToBoth("include all\n");
6483                 WriteMap(0); // clear map completely
6484                 // now re-exclude selected moves
6485                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6486                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6487             } else { // tail was included or in mixed state
6488                 SendToBoth("exclude all\n");
6489                 WriteMap(0xFF); // fill map completely
6490                 // now re-include selected moves
6491                 j = 0; // count them
6492                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6493                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6494                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6495             }
6496         } else { // best
6497             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6498         }
6499     } else {
6500         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6501             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6502             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6503             break;
6504         }
6505     }
6506 }
6507
6508 ChessSquare
6509 DefaultPromoChoice (int white)
6510 {
6511     ChessSquare result;
6512     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6513        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6514         result = WhiteFerz; // no choice
6515     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6516         result= WhiteKing; // in Suicide Q is the last thing we want
6517     else if(gameInfo.variant == VariantSpartan)
6518         result = white ? WhiteQueen : WhiteAngel;
6519     else result = WhiteQueen;
6520     if(!white) result = WHITE_TO_BLACK result;
6521     return result;
6522 }
6523
6524 static int autoQueen; // [HGM] oneclick
6525
6526 int
6527 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6528 {
6529     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6530     /* [HGM] add Shogi promotions */
6531     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6532     ChessSquare piece, partner;
6533     ChessMove moveType;
6534     Boolean premove;
6535
6536     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6537     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6538
6539     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6540       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6541         return FALSE;
6542
6543     piece = boards[currentMove][fromY][fromX];
6544     if(gameInfo.variant == VariantChu) {
6545         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6546         promotionZoneSize = BOARD_HEIGHT/3;
6547         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6548     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6549         promotionZoneSize = BOARD_HEIGHT/3;
6550         highestPromotingPiece = (int)WhiteAlfil;
6551     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6552         promotionZoneSize = 3;
6553     }
6554
6555     // Treat Lance as Pawn when it is not representing Amazon or Lance
6556     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6557         if(piece == WhiteLance) piece = WhitePawn; else
6558         if(piece == BlackLance) piece = BlackPawn;
6559     }
6560
6561     // next weed out all moves that do not touch the promotion zone at all
6562     if((int)piece >= BlackPawn) {
6563         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6564              return FALSE;
6565         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6566         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6567     } else {
6568         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6569            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6570         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6571              return FALSE;
6572     }
6573
6574     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6575
6576     // weed out mandatory Shogi promotions
6577     if(gameInfo.variant == VariantShogi) {
6578         if(piece >= BlackPawn) {
6579             if(toY == 0 && piece == BlackPawn ||
6580                toY == 0 && piece == BlackQueen ||
6581                toY <= 1 && piece == BlackKnight) {
6582                 *promoChoice = '+';
6583                 return FALSE;
6584             }
6585         } else {
6586             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6587                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6588                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6589                 *promoChoice = '+';
6590                 return FALSE;
6591             }
6592         }
6593     }
6594
6595     // weed out obviously illegal Pawn moves
6596     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6597         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6598         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6599         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6600         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6601         // note we are not allowed to test for valid (non-)capture, due to premove
6602     }
6603
6604     // we either have a choice what to promote to, or (in Shogi) whether to promote
6605     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6606        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6607         ChessSquare p=BlackFerz;  // no choice
6608         while(p < EmptySquare) {  //but make sure we use piece that exists
6609             *promoChoice = PieceToChar(p++);
6610             if(*promoChoice != '.') break;
6611         }
6612         return FALSE;
6613     }
6614     // no sense asking what we must promote to if it is going to explode...
6615     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6616         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6617         return FALSE;
6618     }
6619     // give caller the default choice even if we will not make it
6620     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6621     partner = piece; // pieces can promote if the pieceToCharTable says so
6622     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6623     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6624     if(        sweepSelect && gameInfo.variant != VariantGreat
6625                            && gameInfo.variant != VariantGrand
6626                            && gameInfo.variant != VariantSuper) return FALSE;
6627     if(autoQueen) return FALSE; // predetermined
6628
6629     // suppress promotion popup on illegal moves that are not premoves
6630     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6631               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6632     if(appData.testLegality && !premove) {
6633         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6634                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6635         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6636         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6637             return FALSE;
6638     }
6639
6640     return TRUE;
6641 }
6642
6643 int
6644 InPalace (int row, int column)
6645 {   /* [HGM] for Xiangqi */
6646     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6647          column < (BOARD_WIDTH + 4)/2 &&
6648          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6649     return FALSE;
6650 }
6651
6652 int
6653 PieceForSquare (int x, int y)
6654 {
6655   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6656      return -1;
6657   else
6658      return boards[currentMove][y][x];
6659 }
6660
6661 int
6662 OKToStartUserMove (int x, int y)
6663 {
6664     ChessSquare from_piece;
6665     int white_piece;
6666
6667     if (matchMode) return FALSE;
6668     if (gameMode == EditPosition) return TRUE;
6669
6670     if (x >= 0 && y >= 0)
6671       from_piece = boards[currentMove][y][x];
6672     else
6673       from_piece = EmptySquare;
6674
6675     if (from_piece == EmptySquare) return FALSE;
6676
6677     white_piece = (int)from_piece >= (int)WhitePawn &&
6678       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6679
6680     switch (gameMode) {
6681       case AnalyzeFile:
6682       case TwoMachinesPlay:
6683       case EndOfGame:
6684         return FALSE;
6685
6686       case IcsObserving:
6687       case IcsIdle:
6688         return FALSE;
6689
6690       case MachinePlaysWhite:
6691       case IcsPlayingBlack:
6692         if (appData.zippyPlay) return FALSE;
6693         if (white_piece) {
6694             DisplayMoveError(_("You are playing Black"));
6695             return FALSE;
6696         }
6697         break;
6698
6699       case MachinePlaysBlack:
6700       case IcsPlayingWhite:
6701         if (appData.zippyPlay) return FALSE;
6702         if (!white_piece) {
6703             DisplayMoveError(_("You are playing White"));
6704             return FALSE;
6705         }
6706         break;
6707
6708       case PlayFromGameFile:
6709             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6710       case EditGame:
6711         if (!white_piece && WhiteOnMove(currentMove)) {
6712             DisplayMoveError(_("It is White's turn"));
6713             return FALSE;
6714         }
6715         if (white_piece && !WhiteOnMove(currentMove)) {
6716             DisplayMoveError(_("It is Black's turn"));
6717             return FALSE;
6718         }
6719         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6720             /* Editing correspondence game history */
6721             /* Could disallow this or prompt for confirmation */
6722             cmailOldMove = -1;
6723         }
6724         break;
6725
6726       case BeginningOfGame:
6727         if (appData.icsActive) return FALSE;
6728         if (!appData.noChessProgram) {
6729             if (!white_piece) {
6730                 DisplayMoveError(_("You are playing White"));
6731                 return FALSE;
6732             }
6733         }
6734         break;
6735
6736       case Training:
6737         if (!white_piece && WhiteOnMove(currentMove)) {
6738             DisplayMoveError(_("It is White's turn"));
6739             return FALSE;
6740         }
6741         if (white_piece && !WhiteOnMove(currentMove)) {
6742             DisplayMoveError(_("It is Black's turn"));
6743             return FALSE;
6744         }
6745         break;
6746
6747       default:
6748       case IcsExamining:
6749         break;
6750     }
6751     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6752         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6753         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6754         && gameMode != AnalyzeFile && gameMode != Training) {
6755         DisplayMoveError(_("Displayed position is not current"));
6756         return FALSE;
6757     }
6758     return TRUE;
6759 }
6760
6761 Boolean
6762 OnlyMove (int *x, int *y, Boolean captures)
6763 {
6764     DisambiguateClosure cl;
6765     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6766     switch(gameMode) {
6767       case MachinePlaysBlack:
6768       case IcsPlayingWhite:
6769       case BeginningOfGame:
6770         if(!WhiteOnMove(currentMove)) return FALSE;
6771         break;
6772       case MachinePlaysWhite:
6773       case IcsPlayingBlack:
6774         if(WhiteOnMove(currentMove)) return FALSE;
6775         break;
6776       case EditGame:
6777         break;
6778       default:
6779         return FALSE;
6780     }
6781     cl.pieceIn = EmptySquare;
6782     cl.rfIn = *y;
6783     cl.ffIn = *x;
6784     cl.rtIn = -1;
6785     cl.ftIn = -1;
6786     cl.promoCharIn = NULLCHAR;
6787     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6788     if( cl.kind == NormalMove ||
6789         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6790         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6791         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6792       fromX = cl.ff;
6793       fromY = cl.rf;
6794       *x = cl.ft;
6795       *y = cl.rt;
6796       return TRUE;
6797     }
6798     if(cl.kind != ImpossibleMove) return FALSE;
6799     cl.pieceIn = EmptySquare;
6800     cl.rfIn = -1;
6801     cl.ffIn = -1;
6802     cl.rtIn = *y;
6803     cl.ftIn = *x;
6804     cl.promoCharIn = NULLCHAR;
6805     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6806     if( cl.kind == NormalMove ||
6807         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6808         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6809         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6810       fromX = cl.ff;
6811       fromY = cl.rf;
6812       *x = cl.ft;
6813       *y = cl.rt;
6814       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6815       return TRUE;
6816     }
6817     return FALSE;
6818 }
6819
6820 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6821 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6822 int lastLoadGameUseList = FALSE;
6823 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6824 ChessMove lastLoadGameStart = EndOfFile;
6825 int doubleClick;
6826 Boolean addToBookFlag;
6827
6828 void
6829 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6830 {
6831     ChessMove moveType;
6832     ChessSquare pup;
6833     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6834
6835     /* Check if the user is playing in turn.  This is complicated because we
6836        let the user "pick up" a piece before it is his turn.  So the piece he
6837        tried to pick up may have been captured by the time he puts it down!
6838        Therefore we use the color the user is supposed to be playing in this
6839        test, not the color of the piece that is currently on the starting
6840        square---except in EditGame mode, where the user is playing both
6841        sides; fortunately there the capture race can't happen.  (It can
6842        now happen in IcsExamining mode, but that's just too bad.  The user
6843        will get a somewhat confusing message in that case.)
6844        */
6845
6846     switch (gameMode) {
6847       case AnalyzeFile:
6848       case TwoMachinesPlay:
6849       case EndOfGame:
6850       case IcsObserving:
6851       case IcsIdle:
6852         /* We switched into a game mode where moves are not accepted,
6853            perhaps while the mouse button was down. */
6854         return;
6855
6856       case MachinePlaysWhite:
6857         /* User is moving for Black */
6858         if (WhiteOnMove(currentMove)) {
6859             DisplayMoveError(_("It is White's turn"));
6860             return;
6861         }
6862         break;
6863
6864       case MachinePlaysBlack:
6865         /* User is moving for White */
6866         if (!WhiteOnMove(currentMove)) {
6867             DisplayMoveError(_("It is Black's turn"));
6868             return;
6869         }
6870         break;
6871
6872       case PlayFromGameFile:
6873             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6874       case EditGame:
6875       case IcsExamining:
6876       case BeginningOfGame:
6877       case AnalyzeMode:
6878       case Training:
6879         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6880         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6881             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6882             /* User is moving for Black */
6883             if (WhiteOnMove(currentMove)) {
6884                 DisplayMoveError(_("It is White's turn"));
6885                 return;
6886             }
6887         } else {
6888             /* User is moving for White */
6889             if (!WhiteOnMove(currentMove)) {
6890                 DisplayMoveError(_("It is Black's turn"));
6891                 return;
6892             }
6893         }
6894         break;
6895
6896       case IcsPlayingBlack:
6897         /* User is moving for Black */
6898         if (WhiteOnMove(currentMove)) {
6899             if (!appData.premove) {
6900                 DisplayMoveError(_("It is White's turn"));
6901             } else if (toX >= 0 && toY >= 0) {
6902                 premoveToX = toX;
6903                 premoveToY = toY;
6904                 premoveFromX = fromX;
6905                 premoveFromY = fromY;
6906                 premovePromoChar = promoChar;
6907                 gotPremove = 1;
6908                 if (appData.debugMode)
6909                     fprintf(debugFP, "Got premove: fromX %d,"
6910                             "fromY %d, toX %d, toY %d\n",
6911                             fromX, fromY, toX, toY);
6912             }
6913             return;
6914         }
6915         break;
6916
6917       case IcsPlayingWhite:
6918         /* User is moving for White */
6919         if (!WhiteOnMove(currentMove)) {
6920             if (!appData.premove) {
6921                 DisplayMoveError(_("It is Black's turn"));
6922             } else if (toX >= 0 && toY >= 0) {
6923                 premoveToX = toX;
6924                 premoveToY = toY;
6925                 premoveFromX = fromX;
6926                 premoveFromY = fromY;
6927                 premovePromoChar = promoChar;
6928                 gotPremove = 1;
6929                 if (appData.debugMode)
6930                     fprintf(debugFP, "Got premove: fromX %d,"
6931                             "fromY %d, toX %d, toY %d\n",
6932                             fromX, fromY, toX, toY);
6933             }
6934             return;
6935         }
6936         break;
6937
6938       default:
6939         break;
6940
6941       case EditPosition:
6942         /* EditPosition, empty square, or different color piece;
6943            click-click move is possible */
6944         if (toX == -2 || toY == -2) {
6945             boards[0][fromY][fromX] = EmptySquare;
6946             DrawPosition(FALSE, boards[currentMove]);
6947             return;
6948         } else if (toX >= 0 && toY >= 0) {
6949             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6950                 ChessSquare q, p = boards[0][rf][ff];
6951                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6952                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6953                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6954                 if(PieceToChar(q) == '+') gatingPiece = p;
6955             }
6956             boards[0][toY][toX] = boards[0][fromY][fromX];
6957             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6958                 if(boards[0][fromY][0] != EmptySquare) {
6959                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6960                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6961                 }
6962             } else
6963             if(fromX == BOARD_RGHT+1) {
6964                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6965                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6966                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6967                 }
6968             } else
6969             boards[0][fromY][fromX] = gatingPiece;
6970             DrawPosition(FALSE, boards[currentMove]);
6971             return;
6972         }
6973         return;
6974     }
6975
6976     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6977     pup = boards[currentMove][toY][toX];
6978
6979     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6980     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6981          if( pup != EmptySquare ) return;
6982          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6983            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6984                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6985            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6986            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6987            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6988            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6989          fromY = DROP_RANK;
6990     }
6991
6992     /* [HGM] always test for legality, to get promotion info */
6993     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6994                                          fromY, fromX, toY, toX, promoChar);
6995
6996     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
6997
6998     /* [HGM] but possibly ignore an IllegalMove result */
6999     if (appData.testLegality) {
7000         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7001             DisplayMoveError(_("Illegal move"));
7002             return;
7003         }
7004     }
7005
7006     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7007         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7008              ClearPremoveHighlights(); // was included
7009         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7010         return;
7011     }
7012
7013     if(addToBookFlag) { // adding moves to book
7014         char buf[MSG_SIZ], move[MSG_SIZ];
7015         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7016         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7017         AddBookMove(buf);
7018         addToBookFlag = FALSE;
7019         ClearHighlights();
7020         return;
7021     }
7022
7023     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7024 }
7025
7026 /* Common tail of UserMoveEvent and DropMenuEvent */
7027 int
7028 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7029 {
7030     char *bookHit = 0;
7031
7032     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7033         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7034         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7035         if(WhiteOnMove(currentMove)) {
7036             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7037         } else {
7038             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7039         }
7040     }
7041
7042     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7043        move type in caller when we know the move is a legal promotion */
7044     if(moveType == NormalMove && promoChar)
7045         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7046
7047     /* [HGM] <popupFix> The following if has been moved here from
7048        UserMoveEvent(). Because it seemed to belong here (why not allow
7049        piece drops in training games?), and because it can only be
7050        performed after it is known to what we promote. */
7051     if (gameMode == Training) {
7052       /* compare the move played on the board to the next move in the
7053        * game. If they match, display the move and the opponent's response.
7054        * If they don't match, display an error message.
7055        */
7056       int saveAnimate;
7057       Board testBoard;
7058       CopyBoard(testBoard, boards[currentMove]);
7059       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7060
7061       if (CompareBoards(testBoard, boards[currentMove+1])) {
7062         ForwardInner(currentMove+1);
7063
7064         /* Autoplay the opponent's response.
7065          * if appData.animate was TRUE when Training mode was entered,
7066          * the response will be animated.
7067          */
7068         saveAnimate = appData.animate;
7069         appData.animate = animateTraining;
7070         ForwardInner(currentMove+1);
7071         appData.animate = saveAnimate;
7072
7073         /* check for the end of the game */
7074         if (currentMove >= forwardMostMove) {
7075           gameMode = PlayFromGameFile;
7076           ModeHighlight();
7077           SetTrainingModeOff();
7078           DisplayInformation(_("End of game"));
7079         }
7080       } else {
7081         DisplayError(_("Incorrect move"), 0);
7082       }
7083       return 1;
7084     }
7085
7086   /* Ok, now we know that the move is good, so we can kill
7087      the previous line in Analysis Mode */
7088   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7089                                 && currentMove < forwardMostMove) {
7090     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7091     else forwardMostMove = currentMove;
7092   }
7093
7094   ClearMap();
7095
7096   /* If we need the chess program but it's dead, restart it */
7097   ResurrectChessProgram();
7098
7099   /* A user move restarts a paused game*/
7100   if (pausing)
7101     PauseEvent();
7102
7103   thinkOutput[0] = NULLCHAR;
7104
7105   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7106
7107   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7108     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7109     return 1;
7110   }
7111
7112   if (gameMode == BeginningOfGame) {
7113     if (appData.noChessProgram) {
7114       gameMode = EditGame;
7115       SetGameInfo();
7116     } else {
7117       char buf[MSG_SIZ];
7118       gameMode = MachinePlaysBlack;
7119       StartClocks();
7120       SetGameInfo();
7121       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7122       DisplayTitle(buf);
7123       if (first.sendName) {
7124         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7125         SendToProgram(buf, &first);
7126       }
7127       StartClocks();
7128     }
7129     ModeHighlight();
7130   }
7131
7132   /* Relay move to ICS or chess engine */
7133   if (appData.icsActive) {
7134     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7135         gameMode == IcsExamining) {
7136       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7137         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7138         SendToICS("draw ");
7139         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7140       }
7141       // also send plain move, in case ICS does not understand atomic claims
7142       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7143       ics_user_moved = 1;
7144     }
7145   } else {
7146     if (first.sendTime && (gameMode == BeginningOfGame ||
7147                            gameMode == MachinePlaysWhite ||
7148                            gameMode == MachinePlaysBlack)) {
7149       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7150     }
7151     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7152          // [HGM] book: if program might be playing, let it use book
7153         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7154         first.maybeThinking = TRUE;
7155     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7156         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7157         SendBoard(&first, currentMove+1);
7158         if(second.analyzing) {
7159             if(!second.useSetboard) SendToProgram("undo\n", &second);
7160             SendBoard(&second, currentMove+1);
7161         }
7162     } else {
7163         SendMoveToProgram(forwardMostMove-1, &first);
7164         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7165     }
7166     if (currentMove == cmailOldMove + 1) {
7167       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7168     }
7169   }
7170
7171   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7172
7173   switch (gameMode) {
7174   case EditGame:
7175     if(appData.testLegality)
7176     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7177     case MT_NONE:
7178     case MT_CHECK:
7179       break;
7180     case MT_CHECKMATE:
7181     case MT_STAINMATE:
7182       if (WhiteOnMove(currentMove)) {
7183         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7184       } else {
7185         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7186       }
7187       break;
7188     case MT_STALEMATE:
7189       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7190       break;
7191     }
7192     break;
7193
7194   case MachinePlaysBlack:
7195   case MachinePlaysWhite:
7196     /* disable certain menu options while machine is thinking */
7197     SetMachineThinkingEnables();
7198     break;
7199
7200   default:
7201     break;
7202   }
7203
7204   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7205   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7206
7207   if(bookHit) { // [HGM] book: simulate book reply
7208         static char bookMove[MSG_SIZ]; // a bit generous?
7209
7210         programStats.nodes = programStats.depth = programStats.time =
7211         programStats.score = programStats.got_only_move = 0;
7212         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7213
7214         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7215         strcat(bookMove, bookHit);
7216         HandleMachineMove(bookMove, &first);
7217   }
7218   return 1;
7219 }
7220
7221 void
7222 MarkByFEN(char *fen)
7223 {
7224         int r, f;
7225         if(!appData.markers || !appData.highlightDragging) return;
7226         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7227         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7228         while(*fen) {
7229             int s = 0;
7230             marker[r][f] = 0;
7231             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7232             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7233             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7234             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7235             if(*fen == 'T') marker[r][f++] = 0; else
7236             if(*fen == 'Y') marker[r][f++] = 1; else
7237             if(*fen == 'G') marker[r][f++] = 3; else
7238             if(*fen == 'B') marker[r][f++] = 4; else
7239             if(*fen == 'C') marker[r][f++] = 5; else
7240             if(*fen == 'M') marker[r][f++] = 6; else
7241             if(*fen == 'W') marker[r][f++] = 7; else
7242             if(*fen == 'D') marker[r][f++] = 8; else
7243             if(*fen == 'R') marker[r][f++] = 2; else {
7244                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7245               f += s; fen -= s>0;
7246             }
7247             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7248             if(r < 0) break;
7249             fen++;
7250         }
7251         DrawPosition(TRUE, NULL);
7252 }
7253
7254 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7255
7256 void
7257 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7258 {
7259     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7260     Markers *m = (Markers *) closure;
7261     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7262         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7263                          || kind == WhiteCapturesEnPassant
7264                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7265     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7266 }
7267
7268 static int hoverSavedValid;
7269
7270 void
7271 MarkTargetSquares (int clear)
7272 {
7273   int x, y, sum=0;
7274   if(clear) { // no reason to ever suppress clearing
7275     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7276     hoverSavedValid = 0;
7277     if(!sum) return; // nothing was cleared,no redraw needed
7278   } else {
7279     int capt = 0;
7280     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7281        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7282     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7283     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7284       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7285       if(capt)
7286       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7287     }
7288   }
7289   DrawPosition(FALSE, NULL);
7290 }
7291
7292 int
7293 Explode (Board board, int fromX, int fromY, int toX, int toY)
7294 {
7295     if(gameInfo.variant == VariantAtomic &&
7296        (board[toY][toX] != EmptySquare ||                     // capture?
7297         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7298                          board[fromY][fromX] == BlackPawn   )
7299       )) {
7300         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7301         return TRUE;
7302     }
7303     return FALSE;
7304 }
7305
7306 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7307
7308 int
7309 CanPromote (ChessSquare piece, int y)
7310 {
7311         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7312         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7313         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7314         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7315            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7316            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7317          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7318         return (piece == BlackPawn && y <= zone ||
7319                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7320                 piece == BlackLance && y == 1 ||
7321                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7322 }
7323
7324 void
7325 HoverEvent (int xPix, int yPix, int x, int y)
7326 {
7327         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7328         int r, f;
7329         if(!first.highlight) return;
7330         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7331         if(x == oldX && y == oldY) return; // only do something if we enter new square
7332         oldFromX = fromX; oldFromY = fromY;
7333         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7334           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7335             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7336           hoverSavedValid = 1;
7337         } else if(oldX != x || oldY != y) {
7338           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7339           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7340           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7341             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7342           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7343             char buf[MSG_SIZ];
7344             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7345             SendToProgram(buf, &first);
7346           }
7347           oldX = x; oldY = y;
7348 //        SetHighlights(fromX, fromY, x, y);
7349         }
7350 }
7351
7352 void ReportClick(char *action, int x, int y)
7353 {
7354         char buf[MSG_SIZ]; // Inform engine of what user does
7355         int r, f;
7356         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7357           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7358         if(!first.highlight || gameMode == EditPosition) return;
7359         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7360         SendToProgram(buf, &first);
7361 }
7362
7363 void
7364 LeftClick (ClickType clickType, int xPix, int yPix)
7365 {
7366     int x, y;
7367     Boolean saveAnimate;
7368     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7369     char promoChoice = NULLCHAR;
7370     ChessSquare piece;
7371     static TimeMark lastClickTime, prevClickTime;
7372
7373     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7374
7375     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7376
7377     if (clickType == Press) ErrorPopDown();
7378     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7379
7380     x = EventToSquare(xPix, BOARD_WIDTH);
7381     y = EventToSquare(yPix, BOARD_HEIGHT);
7382     if (!flipView && y >= 0) {
7383         y = BOARD_HEIGHT - 1 - y;
7384     }
7385     if (flipView && x >= 0) {
7386         x = BOARD_WIDTH - 1 - x;
7387     }
7388
7389     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7390         defaultPromoChoice = promoSweep;
7391         promoSweep = EmptySquare;   // terminate sweep
7392         promoDefaultAltered = TRUE;
7393         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7394     }
7395
7396     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7397         if(clickType == Release) return; // ignore upclick of click-click destination
7398         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7399         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7400         if(gameInfo.holdingsWidth &&
7401                 (WhiteOnMove(currentMove)
7402                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7403                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7404             // click in right holdings, for determining promotion piece
7405             ChessSquare p = boards[currentMove][y][x];
7406             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7407             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7408             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7409                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7410                 fromX = fromY = -1;
7411                 return;
7412             }
7413         }
7414         DrawPosition(FALSE, boards[currentMove]);
7415         return;
7416     }
7417
7418     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7419     if(clickType == Press
7420             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7421               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7422               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7423         return;
7424
7425     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7426         // could be static click on premove from-square: abort premove
7427         gotPremove = 0;
7428         ClearPremoveHighlights();
7429     }
7430
7431     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7432         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7433
7434     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7435         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7436                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7437         defaultPromoChoice = DefaultPromoChoice(side);
7438     }
7439
7440     autoQueen = appData.alwaysPromoteToQueen;
7441
7442     if (fromX == -1) {
7443       int originalY = y;
7444       gatingPiece = EmptySquare;
7445       if (clickType != Press) {
7446         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7447             DragPieceEnd(xPix, yPix); dragging = 0;
7448             DrawPosition(FALSE, NULL);
7449         }
7450         return;
7451       }
7452       doubleClick = FALSE;
7453       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7454         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7455       }
7456       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7457       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7458          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7459          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7460             /* First square */
7461             if (OKToStartUserMove(fromX, fromY)) {
7462                 second = 0;
7463                 ReportClick("lift", x, y);
7464                 MarkTargetSquares(0);
7465                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7466                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7467                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7468                     promoSweep = defaultPromoChoice;
7469                     selectFlag = 0; lastX = xPix; lastY = yPix;
7470                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7471                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7472                 }
7473                 if (appData.highlightDragging) {
7474                     SetHighlights(fromX, fromY, -1, -1);
7475                 } else {
7476                     ClearHighlights();
7477                 }
7478             } else fromX = fromY = -1;
7479             return;
7480         }
7481     }
7482
7483     /* fromX != -1 */
7484     if (clickType == Press && gameMode != EditPosition) {
7485         ChessSquare fromP;
7486         ChessSquare toP;
7487         int frc;
7488
7489         // ignore off-board to clicks
7490         if(y < 0 || x < 0) return;
7491
7492         /* Check if clicking again on the same color piece */
7493         fromP = boards[currentMove][fromY][fromX];
7494         toP = boards[currentMove][y][x];
7495         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7496         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7497            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7498              WhitePawn <= toP && toP <= WhiteKing &&
7499              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7500              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7501             (BlackPawn <= fromP && fromP <= BlackKing &&
7502              BlackPawn <= toP && toP <= BlackKing &&
7503              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7504              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7505             /* Clicked again on same color piece -- changed his mind */
7506             second = (x == fromX && y == fromY);
7507             killX = killY = -1;
7508             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7509                 second = FALSE; // first double-click rather than scond click
7510                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7511             }
7512             promoDefaultAltered = FALSE;
7513             MarkTargetSquares(1);
7514            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7515             if (appData.highlightDragging) {
7516                 SetHighlights(x, y, -1, -1);
7517             } else {
7518                 ClearHighlights();
7519             }
7520             if (OKToStartUserMove(x, y)) {
7521                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7522                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7523                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7524                  gatingPiece = boards[currentMove][fromY][fromX];
7525                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7526                 fromX = x;
7527                 fromY = y; dragging = 1;
7528                 ReportClick("lift", x, y);
7529                 MarkTargetSquares(0);
7530                 DragPieceBegin(xPix, yPix, FALSE);
7531                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7532                     promoSweep = defaultPromoChoice;
7533                     selectFlag = 0; lastX = xPix; lastY = yPix;
7534                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7535                 }
7536             }
7537            }
7538            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7539            second = FALSE;
7540         }
7541         // ignore clicks on holdings
7542         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7543     }
7544
7545     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7546         DragPieceEnd(xPix, yPix); dragging = 0;
7547         if(clearFlag) {
7548             // a deferred attempt to click-click move an empty square on top of a piece
7549             boards[currentMove][y][x] = EmptySquare;
7550             ClearHighlights();
7551             DrawPosition(FALSE, boards[currentMove]);
7552             fromX = fromY = -1; clearFlag = 0;
7553             return;
7554         }
7555         if (appData.animateDragging) {
7556             /* Undo animation damage if any */
7557             DrawPosition(FALSE, NULL);
7558         }
7559         if (second || sweepSelecting) {
7560             /* Second up/down in same square; just abort move */
7561             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7562             second = sweepSelecting = 0;
7563             fromX = fromY = -1;
7564             gatingPiece = EmptySquare;
7565             MarkTargetSquares(1);
7566             ClearHighlights();
7567             gotPremove = 0;
7568             ClearPremoveHighlights();
7569         } else {
7570             /* First upclick in same square; start click-click mode */
7571             SetHighlights(x, y, -1, -1);
7572         }
7573         return;
7574     }
7575
7576     clearFlag = 0;
7577
7578     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7579         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7580         DisplayMessage(_("only marked squares are legal"),"");
7581         DrawPosition(TRUE, NULL);
7582         return; // ignore to-click
7583     }
7584
7585     /* we now have a different from- and (possibly off-board) to-square */
7586     /* Completed move */
7587     if(!sweepSelecting) {
7588         toX = x;
7589         toY = y;
7590     }
7591
7592     piece = boards[currentMove][fromY][fromX];
7593
7594     saveAnimate = appData.animate;
7595     if (clickType == Press) {
7596         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7597         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7598             // must be Edit Position mode with empty-square selected
7599             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7600             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7601             return;
7602         }
7603         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7604             return;
7605         }
7606         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7607             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7608         } else
7609         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7610         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7611           if(appData.sweepSelect) {
7612             promoSweep = defaultPromoChoice;
7613             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7614             selectFlag = 0; lastX = xPix; lastY = yPix;
7615             Sweep(0); // Pawn that is going to promote: preview promotion piece
7616             sweepSelecting = 1;
7617             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7618             MarkTargetSquares(1);
7619           }
7620           return; // promo popup appears on up-click
7621         }
7622         /* Finish clickclick move */
7623         if (appData.animate || appData.highlightLastMove) {
7624             SetHighlights(fromX, fromY, toX, toY);
7625         } else {
7626             ClearHighlights();
7627         }
7628     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7629         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7630         if (appData.animate || appData.highlightLastMove) {
7631             SetHighlights(fromX, fromY, toX, toY);
7632         } else {
7633             ClearHighlights();
7634         }
7635     } else {
7636 #if 0
7637 // [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
7638         /* Finish drag move */
7639         if (appData.highlightLastMove) {
7640             SetHighlights(fromX, fromY, toX, toY);
7641         } else {
7642             ClearHighlights();
7643         }
7644 #endif
7645         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7646         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7647           dragging *= 2;            // flag button-less dragging if we are dragging
7648           MarkTargetSquares(1);
7649           if(x == killX && y == killY) killX = killY = -1; else {
7650             killX = x; killY = y;     //remeber this square as intermediate
7651             ReportClick("put", x, y); // and inform engine
7652             ReportClick("lift", x, y);
7653             MarkTargetSquares(0);
7654             return;
7655           }
7656         }
7657         DragPieceEnd(xPix, yPix); dragging = 0;
7658         /* Don't animate move and drag both */
7659         appData.animate = FALSE;
7660     }
7661
7662     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7663     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7664         ChessSquare piece = boards[currentMove][fromY][fromX];
7665         if(gameMode == EditPosition && piece != EmptySquare &&
7666            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7667             int n;
7668
7669             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7670                 n = PieceToNumber(piece - (int)BlackPawn);
7671                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7672                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7673                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7674             } else
7675             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7676                 n = PieceToNumber(piece);
7677                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7678                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7679                 boards[currentMove][n][BOARD_WIDTH-2]++;
7680             }
7681             boards[currentMove][fromY][fromX] = EmptySquare;
7682         }
7683         ClearHighlights();
7684         fromX = fromY = -1;
7685         MarkTargetSquares(1);
7686         DrawPosition(TRUE, boards[currentMove]);
7687         return;
7688     }
7689
7690     // off-board moves should not be highlighted
7691     if(x < 0 || y < 0) ClearHighlights();
7692     else ReportClick("put", x, y);
7693
7694     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7695
7696     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7697         SetHighlights(fromX, fromY, toX, toY);
7698         MarkTargetSquares(1);
7699         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7700             // [HGM] super: promotion to captured piece selected from holdings
7701             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7702             promotionChoice = TRUE;
7703             // kludge follows to temporarily execute move on display, without promoting yet
7704             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7705             boards[currentMove][toY][toX] = p;
7706             DrawPosition(FALSE, boards[currentMove]);
7707             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7708             boards[currentMove][toY][toX] = q;
7709             DisplayMessage("Click in holdings to choose piece", "");
7710             return;
7711         }
7712         PromotionPopUp(promoChoice);
7713     } else {
7714         int oldMove = currentMove;
7715         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7716         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7717         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7718         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7719            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7720             DrawPosition(TRUE, boards[currentMove]);
7721         MarkTargetSquares(1);
7722         fromX = fromY = -1;
7723     }
7724     appData.animate = saveAnimate;
7725     if (appData.animate || appData.animateDragging) {
7726         /* Undo animation damage if needed */
7727         DrawPosition(FALSE, NULL);
7728     }
7729 }
7730
7731 int
7732 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7733 {   // front-end-free part taken out of PieceMenuPopup
7734     int whichMenu; int xSqr, ySqr;
7735
7736     if(seekGraphUp) { // [HGM] seekgraph
7737         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7738         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7739         return -2;
7740     }
7741
7742     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7743          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7744         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7745         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7746         if(action == Press)   {
7747             originalFlip = flipView;
7748             flipView = !flipView; // temporarily flip board to see game from partners perspective
7749             DrawPosition(TRUE, partnerBoard);
7750             DisplayMessage(partnerStatus, "");
7751             partnerUp = TRUE;
7752         } else if(action == Release) {
7753             flipView = originalFlip;
7754             DrawPosition(TRUE, boards[currentMove]);
7755             partnerUp = FALSE;
7756         }
7757         return -2;
7758     }
7759
7760     xSqr = EventToSquare(x, BOARD_WIDTH);
7761     ySqr = EventToSquare(y, BOARD_HEIGHT);
7762     if (action == Release) {
7763         if(pieceSweep != EmptySquare) {
7764             EditPositionMenuEvent(pieceSweep, toX, toY);
7765             pieceSweep = EmptySquare;
7766         } else UnLoadPV(); // [HGM] pv
7767     }
7768     if (action != Press) return -2; // return code to be ignored
7769     switch (gameMode) {
7770       case IcsExamining:
7771         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7772       case EditPosition:
7773         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7774         if (xSqr < 0 || ySqr < 0) return -1;
7775         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7776         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7777         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7778         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7779         NextPiece(0);
7780         return 2; // grab
7781       case IcsObserving:
7782         if(!appData.icsEngineAnalyze) return -1;
7783       case IcsPlayingWhite:
7784       case IcsPlayingBlack:
7785         if(!appData.zippyPlay) goto noZip;
7786       case AnalyzeMode:
7787       case AnalyzeFile:
7788       case MachinePlaysWhite:
7789       case MachinePlaysBlack:
7790       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7791         if (!appData.dropMenu) {
7792           LoadPV(x, y);
7793           return 2; // flag front-end to grab mouse events
7794         }
7795         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7796            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7797       case EditGame:
7798       noZip:
7799         if (xSqr < 0 || ySqr < 0) return -1;
7800         if (!appData.dropMenu || appData.testLegality &&
7801             gameInfo.variant != VariantBughouse &&
7802             gameInfo.variant != VariantCrazyhouse) return -1;
7803         whichMenu = 1; // drop menu
7804         break;
7805       default:
7806         return -1;
7807     }
7808
7809     if (((*fromX = xSqr) < 0) ||
7810         ((*fromY = ySqr) < 0)) {
7811         *fromX = *fromY = -1;
7812         return -1;
7813     }
7814     if (flipView)
7815       *fromX = BOARD_WIDTH - 1 - *fromX;
7816     else
7817       *fromY = BOARD_HEIGHT - 1 - *fromY;
7818
7819     return whichMenu;
7820 }
7821
7822 void
7823 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7824 {
7825 //    char * hint = lastHint;
7826     FrontEndProgramStats stats;
7827
7828     stats.which = cps == &first ? 0 : 1;
7829     stats.depth = cpstats->depth;
7830     stats.nodes = cpstats->nodes;
7831     stats.score = cpstats->score;
7832     stats.time = cpstats->time;
7833     stats.pv = cpstats->movelist;
7834     stats.hint = lastHint;
7835     stats.an_move_index = 0;
7836     stats.an_move_count = 0;
7837
7838     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7839         stats.hint = cpstats->move_name;
7840         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7841         stats.an_move_count = cpstats->nr_moves;
7842     }
7843
7844     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
7845
7846     SetProgramStats( &stats );
7847 }
7848
7849 void
7850 ClearEngineOutputPane (int which)
7851 {
7852     static FrontEndProgramStats dummyStats;
7853     dummyStats.which = which;
7854     dummyStats.pv = "#";
7855     SetProgramStats( &dummyStats );
7856 }
7857
7858 #define MAXPLAYERS 500
7859
7860 char *
7861 TourneyStandings (int display)
7862 {
7863     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7864     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7865     char result, *p, *names[MAXPLAYERS];
7866
7867     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7868         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7869     names[0] = p = strdup(appData.participants);
7870     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7871
7872     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7873
7874     while(result = appData.results[nr]) {
7875         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7876         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7877         wScore = bScore = 0;
7878         switch(result) {
7879           case '+': wScore = 2; break;
7880           case '-': bScore = 2; break;
7881           case '=': wScore = bScore = 1; break;
7882           case ' ':
7883           case '*': return strdup("busy"); // tourney not finished
7884         }
7885         score[w] += wScore;
7886         score[b] += bScore;
7887         games[w]++;
7888         games[b]++;
7889         nr++;
7890     }
7891     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7892     for(w=0; w<nPlayers; w++) {
7893         bScore = -1;
7894         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7895         ranking[w] = b; points[w] = bScore; score[b] = -2;
7896     }
7897     p = malloc(nPlayers*34+1);
7898     for(w=0; w<nPlayers && w<display; w++)
7899         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7900     free(names[0]);
7901     return p;
7902 }
7903
7904 void
7905 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7906 {       // count all piece types
7907         int p, f, r;
7908         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7909         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7910         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7911                 p = board[r][f];
7912                 pCnt[p]++;
7913                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7914                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7915                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7916                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7917                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7918                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7919         }
7920 }
7921
7922 int
7923 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7924 {
7925         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7926         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7927
7928         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7929         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7930         if(myPawns == 2 && nMine == 3) // KPP
7931             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7932         if(myPawns == 1 && nMine == 2) // KP
7933             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7934         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7935             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7936         if(myPawns) return FALSE;
7937         if(pCnt[WhiteRook+side])
7938             return pCnt[BlackRook-side] ||
7939                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7940                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7941                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7942         if(pCnt[WhiteCannon+side]) {
7943             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7944             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7945         }
7946         if(pCnt[WhiteKnight+side])
7947             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7948         return FALSE;
7949 }
7950
7951 int
7952 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7953 {
7954         VariantClass v = gameInfo.variant;
7955
7956         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7957         if(v == VariantShatranj) return TRUE; // always winnable through baring
7958         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7959         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7960
7961         if(v == VariantXiangqi) {
7962                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7963
7964                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7965                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7966                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7967                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7968                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7969                 if(stale) // we have at least one last-rank P plus perhaps C
7970                     return majors // KPKX
7971                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7972                 else // KCA*E*
7973                     return pCnt[WhiteFerz+side] // KCAK
7974                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7975                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7976                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7977
7978         } else if(v == VariantKnightmate) {
7979                 if(nMine == 1) return FALSE;
7980                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7981         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7982                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7983
7984                 if(nMine == 1) return FALSE; // bare King
7985                 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
7986                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7987                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7988                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7989                 if(pCnt[WhiteKnight+side])
7990                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7991                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7992                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7993                 if(nBishops)
7994                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7995                 if(pCnt[WhiteAlfil+side])
7996                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7997                 if(pCnt[WhiteWazir+side])
7998                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7999         }
8000
8001         return TRUE;
8002 }
8003
8004 int
8005 CompareWithRights (Board b1, Board b2)
8006 {
8007     int rights = 0;
8008     if(!CompareBoards(b1, b2)) return FALSE;
8009     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8010     /* compare castling rights */
8011     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8012            rights++; /* King lost rights, while rook still had them */
8013     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8014         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8015            rights++; /* but at least one rook lost them */
8016     }
8017     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8018            rights++;
8019     if( b1[CASTLING][5] != NoRights ) {
8020         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8021            rights++;
8022     }
8023     return rights == 0;
8024 }
8025
8026 int
8027 Adjudicate (ChessProgramState *cps)
8028 {       // [HGM] some adjudications useful with buggy engines
8029         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8030         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8031         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8032         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8033         int k, drop, count = 0; static int bare = 1;
8034         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8035         Boolean canAdjudicate = !appData.icsActive;
8036
8037         // most tests only when we understand the game, i.e. legality-checking on
8038             if( appData.testLegality )
8039             {   /* [HGM] Some more adjudications for obstinate engines */
8040                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8041                 static int moveCount = 6;
8042                 ChessMove result;
8043                 char *reason = NULL;
8044
8045                 /* Count what is on board. */
8046                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8047
8048                 /* Some material-based adjudications that have to be made before stalemate test */
8049                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8050                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8051                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8052                      if(canAdjudicate && appData.checkMates) {
8053                          if(engineOpponent)
8054                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8055                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8056                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8057                          return 1;
8058                      }
8059                 }
8060
8061                 /* Bare King in Shatranj (loses) or Losers (wins) */
8062                 if( nrW == 1 || nrB == 1) {
8063                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8064                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8065                      if(canAdjudicate && appData.checkMates) {
8066                          if(engineOpponent)
8067                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8068                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8069                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8070                          return 1;
8071                      }
8072                   } else
8073                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8074                   {    /* bare King */
8075                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8076                         if(canAdjudicate && appData.checkMates) {
8077                             /* but only adjudicate if adjudication enabled */
8078                             if(engineOpponent)
8079                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8080                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8081                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8082                             return 1;
8083                         }
8084                   }
8085                 } else bare = 1;
8086
8087
8088             // don't wait for engine to announce game end if we can judge ourselves
8089             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8090               case MT_CHECK:
8091                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8092                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8093                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8094                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8095                             checkCnt++;
8096                         if(checkCnt >= 2) {
8097                             reason = "Xboard adjudication: 3rd check";
8098                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8099                             break;
8100                         }
8101                     }
8102                 }
8103               case MT_NONE:
8104               default:
8105                 break;
8106               case MT_STEALMATE:
8107               case MT_STALEMATE:
8108               case MT_STAINMATE:
8109                 reason = "Xboard adjudication: Stalemate";
8110                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8111                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8112                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8113                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8114                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8115                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8116                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8117                                                                         EP_CHECKMATE : EP_WINS);
8118                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8119                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8120                 }
8121                 break;
8122               case MT_CHECKMATE:
8123                 reason = "Xboard adjudication: Checkmate";
8124                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8125                 if(gameInfo.variant == VariantShogi) {
8126                     if(forwardMostMove > backwardMostMove
8127                        && moveList[forwardMostMove-1][1] == '@'
8128                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8129                         reason = "XBoard adjudication: pawn-drop mate";
8130                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8131                     }
8132                 }
8133                 break;
8134             }
8135
8136                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8137                     case EP_STALEMATE:
8138                         result = GameIsDrawn; break;
8139                     case EP_CHECKMATE:
8140                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8141                     case EP_WINS:
8142                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8143                     default:
8144                         result = EndOfFile;
8145                 }
8146                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8147                     if(engineOpponent)
8148                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8149                     GameEnds( result, reason, GE_XBOARD );
8150                     return 1;
8151                 }
8152
8153                 /* Next absolutely insufficient mating material. */
8154                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8155                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8156                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8157
8158                      /* always flag draws, for judging claims */
8159                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8160
8161                      if(canAdjudicate && appData.materialDraws) {
8162                          /* but only adjudicate them if adjudication enabled */
8163                          if(engineOpponent) {
8164                            SendToProgram("force\n", engineOpponent); // suppress reply
8165                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8166                          }
8167                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8168                          return 1;
8169                      }
8170                 }
8171
8172                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8173                 if(gameInfo.variant == VariantXiangqi ?
8174                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8175                  : nrW + nrB == 4 &&
8176                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8177                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8178                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8179                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8180                    ) ) {
8181                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8182                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8183                           if(engineOpponent) {
8184                             SendToProgram("force\n", engineOpponent); // suppress reply
8185                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8186                           }
8187                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8188                           return 1;
8189                      }
8190                 } else moveCount = 6;
8191             }
8192
8193         // Repetition draws and 50-move rule can be applied independently of legality testing
8194
8195                 /* Check for rep-draws */
8196                 count = 0;
8197                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8198                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8199                 for(k = forwardMostMove-2;
8200                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8201                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8202                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8203                     k-=2)
8204                 {   int rights=0;
8205                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8206                         /* compare castling rights */
8207                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8208                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8209                                 rights++; /* King lost rights, while rook still had them */
8210                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8211                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8212                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8213                                    rights++; /* but at least one rook lost them */
8214                         }
8215                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8216                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8217                                 rights++;
8218                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8219                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8220                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8221                                    rights++;
8222                         }
8223                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8224                             && appData.drawRepeats > 1) {
8225                              /* adjudicate after user-specified nr of repeats */
8226                              int result = GameIsDrawn;
8227                              char *details = "XBoard adjudication: repetition draw";
8228                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8229                                 // [HGM] xiangqi: check for forbidden perpetuals
8230                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8231                                 for(m=forwardMostMove; m>k; m-=2) {
8232                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8233                                         ourPerpetual = 0; // the current mover did not always check
8234                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8235                                         hisPerpetual = 0; // the opponent did not always check
8236                                 }
8237                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8238                                                                         ourPerpetual, hisPerpetual);
8239                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8240                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8241                                     details = "Xboard adjudication: perpetual checking";
8242                                 } else
8243                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8244                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8245                                 } else
8246                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8247                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8248                                         result = BlackWins;
8249                                         details = "Xboard adjudication: repetition";
8250                                     }
8251                                 } else // it must be XQ
8252                                 // Now check for perpetual chases
8253                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8254                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8255                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8256                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8257                                         static char resdet[MSG_SIZ];
8258                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8259                                         details = resdet;
8260                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8261                                     } else
8262                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8263                                         break; // Abort repetition-checking loop.
8264                                 }
8265                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8266                              }
8267                              if(engineOpponent) {
8268                                SendToProgram("force\n", engineOpponent); // suppress reply
8269                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8270                              }
8271                              GameEnds( result, details, GE_XBOARD );
8272                              return 1;
8273                         }
8274                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8275                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8276                     }
8277                 }
8278
8279                 /* Now we test for 50-move draws. Determine ply count */
8280                 count = forwardMostMove;
8281                 /* look for last irreversble move */
8282                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8283                     count--;
8284                 /* if we hit starting position, add initial plies */
8285                 if( count == backwardMostMove )
8286                     count -= initialRulePlies;
8287                 count = forwardMostMove - count;
8288                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8289                         // adjust reversible move counter for checks in Xiangqi
8290                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8291                         if(i < backwardMostMove) i = backwardMostMove;
8292                         while(i <= forwardMostMove) {
8293                                 lastCheck = inCheck; // check evasion does not count
8294                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8295                                 if(inCheck || lastCheck) count--; // check does not count
8296                                 i++;
8297                         }
8298                 }
8299                 if( count >= 100)
8300                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8301                          /* this is used to judge if draw claims are legal */
8302                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8303                          if(engineOpponent) {
8304                            SendToProgram("force\n", engineOpponent); // suppress reply
8305                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8306                          }
8307                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8308                          return 1;
8309                 }
8310
8311                 /* if draw offer is pending, treat it as a draw claim
8312                  * when draw condition present, to allow engines a way to
8313                  * claim draws before making their move to avoid a race
8314                  * condition occurring after their move
8315                  */
8316                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8317                          char *p = NULL;
8318                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8319                              p = "Draw claim: 50-move rule";
8320                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8321                              p = "Draw claim: 3-fold repetition";
8322                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8323                              p = "Draw claim: insufficient mating material";
8324                          if( p != NULL && canAdjudicate) {
8325                              if(engineOpponent) {
8326                                SendToProgram("force\n", engineOpponent); // suppress reply
8327                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8328                              }
8329                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8330                              return 1;
8331                          }
8332                 }
8333
8334                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8335                     if(engineOpponent) {
8336                       SendToProgram("force\n", engineOpponent); // suppress reply
8337                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8338                     }
8339                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8340                     return 1;
8341                 }
8342         return 0;
8343 }
8344
8345 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8346 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8347 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8348
8349 static int
8350 BitbaseProbe ()
8351 {
8352     int pieces[10], squares[10], cnt=0, r, f, res;
8353     static int loaded;
8354     static PPROBE_EGBB probeBB;
8355     if(!appData.testLegality) return 10;
8356     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8357     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8358     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8359     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8360         ChessSquare piece = boards[forwardMostMove][r][f];
8361         int black = (piece >= BlackPawn);
8362         int type = piece - black*BlackPawn;
8363         if(piece == EmptySquare) continue;
8364         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8365         if(type == WhiteKing) type = WhiteQueen + 1;
8366         type = egbbCode[type];
8367         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8368         pieces[cnt] = type + black*6;
8369         if(++cnt > 5) return 11;
8370     }
8371     pieces[cnt] = squares[cnt] = 0;
8372     // probe EGBB
8373     if(loaded == 2) return 13; // loading failed before
8374     if(loaded == 0) {
8375         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8376         HMODULE lib;
8377         PLOAD_EGBB loadBB;
8378         loaded = 2; // prepare for failure
8379         if(!path) return 13; // no egbb installed
8380         strncpy(buf, path + 8, MSG_SIZ);
8381         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8382         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8383         lib = LoadLibrary(buf);
8384         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8385         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8386         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8387         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8388         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8389         loaded = 1; // success!
8390     }
8391     res = probeBB(forwardMostMove & 1, pieces, squares);
8392     return res > 0 ? 1 : res < 0 ? -1 : 0;
8393 }
8394
8395 char *
8396 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8397 {   // [HGM] book: this routine intercepts moves to simulate book replies
8398     char *bookHit = NULL;
8399
8400     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8401         char buf[MSG_SIZ];
8402         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8403         SendToProgram(buf, cps);
8404     }
8405     //first determine if the incoming move brings opponent into his book
8406     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8407         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8408     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8409     if(bookHit != NULL && !cps->bookSuspend) {
8410         // make sure opponent is not going to reply after receiving move to book position
8411         SendToProgram("force\n", cps);
8412         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8413     }
8414     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8415     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8416     // now arrange restart after book miss
8417     if(bookHit) {
8418         // after a book hit we never send 'go', and the code after the call to this routine
8419         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8420         char buf[MSG_SIZ], *move = bookHit;
8421         if(cps->useSAN) {
8422             int fromX, fromY, toX, toY;
8423             char promoChar;
8424             ChessMove moveType;
8425             move = buf + 30;
8426             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8427                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8428                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8429                                     PosFlags(forwardMostMove),
8430                                     fromY, fromX, toY, toX, promoChar, move);
8431             } else {
8432                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8433                 bookHit = NULL;
8434             }
8435         }
8436         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8437         SendToProgram(buf, cps);
8438         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8439     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8440         SendToProgram("go\n", cps);
8441         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8442     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8443         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8444             SendToProgram("go\n", cps);
8445         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8446     }
8447     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8448 }
8449
8450 int
8451 LoadError (char *errmess, ChessProgramState *cps)
8452 {   // unloads engine and switches back to -ncp mode if it was first
8453     if(cps->initDone) return FALSE;
8454     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8455     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8456     cps->pr = NoProc;
8457     if(cps == &first) {
8458         appData.noChessProgram = TRUE;
8459         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8460         gameMode = BeginningOfGame; ModeHighlight();
8461         SetNCPMode();
8462     }
8463     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8464     DisplayMessage("", ""); // erase waiting message
8465     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8466     return TRUE;
8467 }
8468
8469 char *savedMessage;
8470 ChessProgramState *savedState;
8471 void
8472 DeferredBookMove (void)
8473 {
8474         if(savedState->lastPing != savedState->lastPong)
8475                     ScheduleDelayedEvent(DeferredBookMove, 10);
8476         else
8477         HandleMachineMove(savedMessage, savedState);
8478 }
8479
8480 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8481 static ChessProgramState *stalledEngine;
8482 static char stashedInputMove[MSG_SIZ];
8483
8484 void
8485 HandleMachineMove (char *message, ChessProgramState *cps)
8486 {
8487     static char firstLeg[20];
8488     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8489     char realname[MSG_SIZ];
8490     int fromX, fromY, toX, toY;
8491     ChessMove moveType;
8492     char promoChar, roar;
8493     char *p, *pv=buf1;
8494     int machineWhite, oldError;
8495     char *bookHit;
8496
8497     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8498         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8499         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8500             DisplayError(_("Invalid pairing from pairing engine"), 0);
8501             return;
8502         }
8503         pairingReceived = 1;
8504         NextMatchGame();
8505         return; // Skim the pairing messages here.
8506     }
8507
8508     oldError = cps->userError; cps->userError = 0;
8509
8510 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8511     /*
8512      * Kludge to ignore BEL characters
8513      */
8514     while (*message == '\007') message++;
8515
8516     /*
8517      * [HGM] engine debug message: ignore lines starting with '#' character
8518      */
8519     if(cps->debug && *message == '#') return;
8520
8521     /*
8522      * Look for book output
8523      */
8524     if (cps == &first && bookRequested) {
8525         if (message[0] == '\t' || message[0] == ' ') {
8526             /* Part of the book output is here; append it */
8527             strcat(bookOutput, message);
8528             strcat(bookOutput, "  \n");
8529             return;
8530         } else if (bookOutput[0] != NULLCHAR) {
8531             /* All of book output has arrived; display it */
8532             char *p = bookOutput;
8533             while (*p != NULLCHAR) {
8534                 if (*p == '\t') *p = ' ';
8535                 p++;
8536             }
8537             DisplayInformation(bookOutput);
8538             bookRequested = FALSE;
8539             /* Fall through to parse the current output */
8540         }
8541     }
8542
8543     /*
8544      * Look for machine move.
8545      */
8546     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8547         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8548     {
8549         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8550             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8551             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8552             stalledEngine = cps;
8553             if(appData.ponderNextMove) { // bring opponent out of ponder
8554                 if(gameMode == TwoMachinesPlay) {
8555                     if(cps->other->pause)
8556                         PauseEngine(cps->other);
8557                     else
8558                         SendToProgram("easy\n", cps->other);
8559                 }
8560             }
8561             StopClocks();
8562             return;
8563         }
8564
8565         /* This method is only useful on engines that support ping */
8566         if (cps->lastPing != cps->lastPong) {
8567           if (gameMode == BeginningOfGame) {
8568             /* Extra move from before last new; ignore */
8569             if (appData.debugMode) {
8570                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8571             }
8572           } else {
8573             if (appData.debugMode) {
8574                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8575                         cps->which, gameMode);
8576             }
8577
8578             SendToProgram("undo\n", cps);
8579           }
8580           return;
8581         }
8582
8583         switch (gameMode) {
8584           case BeginningOfGame:
8585             /* Extra move from before last reset; ignore */
8586             if (appData.debugMode) {
8587                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8588             }
8589             return;
8590
8591           case EndOfGame:
8592           case IcsIdle:
8593           default:
8594             /* Extra move after we tried to stop.  The mode test is
8595                not a reliable way of detecting this problem, but it's
8596                the best we can do on engines that don't support ping.
8597             */
8598             if (appData.debugMode) {
8599                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8600                         cps->which, gameMode);
8601             }
8602             SendToProgram("undo\n", cps);
8603             return;
8604
8605           case MachinePlaysWhite:
8606           case IcsPlayingWhite:
8607             machineWhite = TRUE;
8608             break;
8609
8610           case MachinePlaysBlack:
8611           case IcsPlayingBlack:
8612             machineWhite = FALSE;
8613             break;
8614
8615           case TwoMachinesPlay:
8616             machineWhite = (cps->twoMachinesColor[0] == 'w');
8617             break;
8618         }
8619         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8620             if (appData.debugMode) {
8621                 fprintf(debugFP,
8622                         "Ignoring move out of turn by %s, gameMode %d"
8623                         ", forwardMost %d\n",
8624                         cps->which, gameMode, forwardMostMove);
8625             }
8626             return;
8627         }
8628
8629         if(cps->alphaRank) AlphaRank(machineMove, 4);
8630
8631         // [HGM] lion: (some very limited) support for Alien protocol
8632         killX = killY = -1;
8633         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8634             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8635             return;
8636         } else if(firstLeg[0]) { // there was a previous leg;
8637             // only support case where same piece makes two step (and don't even test that!)
8638             char buf[20], *p = machineMove+1, *q = buf+1, f;
8639             safeStrCpy(buf, machineMove, 20);
8640             while(isdigit(*q)) q++; // find start of to-square
8641             safeStrCpy(machineMove, firstLeg, 20);
8642             while(isdigit(*p)) p++;
8643             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8644             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8645             firstLeg[0] = NULLCHAR;
8646         }
8647
8648         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8649                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8650             /* Machine move could not be parsed; ignore it. */
8651           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8652                     machineMove, _(cps->which));
8653             DisplayMoveError(buf1);
8654             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8655                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8656             if (gameMode == TwoMachinesPlay) {
8657               GameEnds(machineWhite ? BlackWins : WhiteWins,
8658                        buf1, GE_XBOARD);
8659             }
8660             return;
8661         }
8662
8663         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8664         /* So we have to redo legality test with true e.p. status here,  */
8665         /* to make sure an illegal e.p. capture does not slip through,   */
8666         /* to cause a forfeit on a justified illegal-move complaint      */
8667         /* of the opponent.                                              */
8668         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8669            ChessMove moveType;
8670            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8671                              fromY, fromX, toY, toX, promoChar);
8672             if(moveType == IllegalMove) {
8673               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8674                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8675                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8676                            buf1, GE_XBOARD);
8677                 return;
8678            } else if(!appData.fischerCastling)
8679            /* [HGM] Kludge to handle engines that send FRC-style castling
8680               when they shouldn't (like TSCP-Gothic) */
8681            switch(moveType) {
8682              case WhiteASideCastleFR:
8683              case BlackASideCastleFR:
8684                toX+=2;
8685                currentMoveString[2]++;
8686                break;
8687              case WhiteHSideCastleFR:
8688              case BlackHSideCastleFR:
8689                toX--;
8690                currentMoveString[2]--;
8691                break;
8692              default: ; // nothing to do, but suppresses warning of pedantic compilers
8693            }
8694         }
8695         hintRequested = FALSE;
8696         lastHint[0] = NULLCHAR;
8697         bookRequested = FALSE;
8698         /* Program may be pondering now */
8699         cps->maybeThinking = TRUE;
8700         if (cps->sendTime == 2) cps->sendTime = 1;
8701         if (cps->offeredDraw) cps->offeredDraw--;
8702
8703         /* [AS] Save move info*/
8704         pvInfoList[ forwardMostMove ].score = programStats.score;
8705         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8706         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8707
8708         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8709
8710         /* Test suites abort the 'game' after one move */
8711         if(*appData.finger) {
8712            static FILE *f;
8713            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8714            if(!f) f = fopen(appData.finger, "w");
8715            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8716            else { DisplayFatalError("Bad output file", errno, 0); return; }
8717            free(fen);
8718            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8719         }
8720
8721         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8722         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8723             int count = 0;
8724
8725             while( count < adjudicateLossPlies ) {
8726                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8727
8728                 if( count & 1 ) {
8729                     score = -score; /* Flip score for winning side */
8730                 }
8731 printf("score=%d count=%d\n",score,count);
8732                 if( score > appData.adjudicateLossThreshold ) {
8733                     break;
8734                 }
8735
8736                 count++;
8737             }
8738
8739             if( count >= adjudicateLossPlies ) {
8740                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8741
8742                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8743                     "Xboard adjudication",
8744                     GE_XBOARD );
8745
8746                 return;
8747             }
8748         }
8749
8750         if(Adjudicate(cps)) {
8751             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8752             return; // [HGM] adjudicate: for all automatic game ends
8753         }
8754
8755 #if ZIPPY
8756         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8757             first.initDone) {
8758           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8759                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8760                 SendToICS("draw ");
8761                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8762           }
8763           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8764           ics_user_moved = 1;
8765           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8766                 char buf[3*MSG_SIZ];
8767
8768                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8769                         programStats.score / 100.,
8770                         programStats.depth,
8771                         programStats.time / 100.,
8772                         (unsigned int)programStats.nodes,
8773                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8774                         programStats.movelist);
8775                 SendToICS(buf);
8776           }
8777         }
8778 #endif
8779
8780         /* [AS] Clear stats for next move */
8781         ClearProgramStats();
8782         thinkOutput[0] = NULLCHAR;
8783         hiddenThinkOutputState = 0;
8784
8785         bookHit = NULL;
8786         if (gameMode == TwoMachinesPlay) {
8787             /* [HGM] relaying draw offers moved to after reception of move */
8788             /* and interpreting offer as claim if it brings draw condition */
8789             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8790                 SendToProgram("draw\n", cps->other);
8791             }
8792             if (cps->other->sendTime) {
8793                 SendTimeRemaining(cps->other,
8794                                   cps->other->twoMachinesColor[0] == 'w');
8795             }
8796             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8797             if (firstMove && !bookHit) {
8798                 firstMove = FALSE;
8799                 if (cps->other->useColors) {
8800                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8801                 }
8802                 SendToProgram("go\n", cps->other);
8803             }
8804             cps->other->maybeThinking = TRUE;
8805         }
8806
8807         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8808
8809         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8810
8811         if (!pausing && appData.ringBellAfterMoves) {
8812             if(!roar) RingBell();
8813         }
8814
8815         /*
8816          * Reenable menu items that were disabled while
8817          * machine was thinking
8818          */
8819         if (gameMode != TwoMachinesPlay)
8820             SetUserThinkingEnables();
8821
8822         // [HGM] book: after book hit opponent has received move and is now in force mode
8823         // force the book reply into it, and then fake that it outputted this move by jumping
8824         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8825         if(bookHit) {
8826                 static char bookMove[MSG_SIZ]; // a bit generous?
8827
8828                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8829                 strcat(bookMove, bookHit);
8830                 message = bookMove;
8831                 cps = cps->other;
8832                 programStats.nodes = programStats.depth = programStats.time =
8833                 programStats.score = programStats.got_only_move = 0;
8834                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8835
8836                 if(cps->lastPing != cps->lastPong) {
8837                     savedMessage = message; // args for deferred call
8838                     savedState = cps;
8839                     ScheduleDelayedEvent(DeferredBookMove, 10);
8840                     return;
8841                 }
8842                 goto FakeBookMove;
8843         }
8844
8845         return;
8846     }
8847
8848     /* Set special modes for chess engines.  Later something general
8849      *  could be added here; for now there is just one kludge feature,
8850      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8851      *  when "xboard" is given as an interactive command.
8852      */
8853     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8854         cps->useSigint = FALSE;
8855         cps->useSigterm = FALSE;
8856     }
8857     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8858       ParseFeatures(message+8, cps);
8859       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8860     }
8861
8862     if (!strncmp(message, "setup ", 6) && 
8863         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8864           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8865                                         ) { // [HGM] allow first engine to define opening position
8866       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8867       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8868       *buf = NULLCHAR;
8869       if(sscanf(message, "setup (%s", buf) == 1) {
8870         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8871         ASSIGN(appData.pieceToCharTable, buf);
8872       }
8873       if(startedFromSetupPosition) return;
8874       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8875       if(dummy >= 3) {
8876         while(message[s] && message[s++] != ' ');
8877         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8878            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8879             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8880             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8881           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8882           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8883         }
8884       }
8885       ParseFEN(boards[0], &dummy, message+s, FALSE);
8886       DrawPosition(TRUE, boards[0]);
8887       startedFromSetupPosition = TRUE;
8888       return;
8889     }
8890     if(sscanf(message, "piece %c %s", &promoChar, buf1) == 2) {
8891       ChessSquare piece = CharToPiece(promoChar);
8892       if(piece < EmptySquare && !appData.testLegality) { ASSIGN(pieceDesc[piece], buf1); pieceDefs = TRUE; }
8893       return;
8894     }
8895     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8896      * want this, I was asked to put it in, and obliged.
8897      */
8898     if (!strncmp(message, "setboard ", 9)) {
8899         Board initial_position;
8900
8901         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8902
8903         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8904             DisplayError(_("Bad FEN received from engine"), 0);
8905             return ;
8906         } else {
8907            Reset(TRUE, FALSE);
8908            CopyBoard(boards[0], initial_position);
8909            initialRulePlies = FENrulePlies;
8910            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8911            else gameMode = MachinePlaysBlack;
8912            DrawPosition(FALSE, boards[currentMove]);
8913         }
8914         return;
8915     }
8916
8917     /*
8918      * Look for communication commands
8919      */
8920     if (!strncmp(message, "telluser ", 9)) {
8921         if(message[9] == '\\' && message[10] == '\\')
8922             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8923         PlayTellSound();
8924         DisplayNote(message + 9);
8925         return;
8926     }
8927     if (!strncmp(message, "tellusererror ", 14)) {
8928         cps->userError = 1;
8929         if(message[14] == '\\' && message[15] == '\\')
8930             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8931         PlayTellSound();
8932         DisplayError(message + 14, 0);
8933         return;
8934     }
8935     if (!strncmp(message, "tellopponent ", 13)) {
8936       if (appData.icsActive) {
8937         if (loggedOn) {
8938           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8939           SendToICS(buf1);
8940         }
8941       } else {
8942         DisplayNote(message + 13);
8943       }
8944       return;
8945     }
8946     if (!strncmp(message, "tellothers ", 11)) {
8947       if (appData.icsActive) {
8948         if (loggedOn) {
8949           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8950           SendToICS(buf1);
8951         }
8952       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8953       return;
8954     }
8955     if (!strncmp(message, "tellall ", 8)) {
8956       if (appData.icsActive) {
8957         if (loggedOn) {
8958           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8959           SendToICS(buf1);
8960         }
8961       } else {
8962         DisplayNote(message + 8);
8963       }
8964       return;
8965     }
8966     if (strncmp(message, "warning", 7) == 0) {
8967         /* Undocumented feature, use tellusererror in new code */
8968         DisplayError(message, 0);
8969         return;
8970     }
8971     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8972         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8973         strcat(realname, " query");
8974         AskQuestion(realname, buf2, buf1, cps->pr);
8975         return;
8976     }
8977     /* Commands from the engine directly to ICS.  We don't allow these to be
8978      *  sent until we are logged on. Crafty kibitzes have been known to
8979      *  interfere with the login process.
8980      */
8981     if (loggedOn) {
8982         if (!strncmp(message, "tellics ", 8)) {
8983             SendToICS(message + 8);
8984             SendToICS("\n");
8985             return;
8986         }
8987         if (!strncmp(message, "tellicsnoalias ", 15)) {
8988             SendToICS(ics_prefix);
8989             SendToICS(message + 15);
8990             SendToICS("\n");
8991             return;
8992         }
8993         /* The following are for backward compatibility only */
8994         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8995             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8996             SendToICS(ics_prefix);
8997             SendToICS(message);
8998             SendToICS("\n");
8999             return;
9000         }
9001     }
9002     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9003         if(initPing == cps->lastPong) {
9004             if(gameInfo.variant == VariantUnknown) {
9005                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9006                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9007                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9008             }
9009             initPing = -1;
9010         }
9011         return;
9012     }
9013     if(!strncmp(message, "highlight ", 10)) {
9014         if(appData.testLegality && appData.markers) return;
9015         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9016         return;
9017     }
9018     if(!strncmp(message, "click ", 6)) {
9019         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9020         if(appData.testLegality || !appData.oneClick) return;
9021         sscanf(message+6, "%c%d%c", &f, &y, &c);
9022         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9023         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9024         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9025         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9026         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9027         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9028             LeftClick(Release, lastLeftX, lastLeftY);
9029         controlKey  = (c == ',');
9030         LeftClick(Press, x, y);
9031         LeftClick(Release, x, y);
9032         first.highlight = f;
9033         return;
9034     }
9035     /*
9036      * If the move is illegal, cancel it and redraw the board.
9037      * Also deal with other error cases.  Matching is rather loose
9038      * here to accommodate engines written before the spec.
9039      */
9040     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9041         strncmp(message, "Error", 5) == 0) {
9042         if (StrStr(message, "name") ||
9043             StrStr(message, "rating") || StrStr(message, "?") ||
9044             StrStr(message, "result") || StrStr(message, "board") ||
9045             StrStr(message, "bk") || StrStr(message, "computer") ||
9046             StrStr(message, "variant") || StrStr(message, "hint") ||
9047             StrStr(message, "random") || StrStr(message, "depth") ||
9048             StrStr(message, "accepted")) {
9049             return;
9050         }
9051         if (StrStr(message, "protover")) {
9052           /* Program is responding to input, so it's apparently done
9053              initializing, and this error message indicates it is
9054              protocol version 1.  So we don't need to wait any longer
9055              for it to initialize and send feature commands. */
9056           FeatureDone(cps, 1);
9057           cps->protocolVersion = 1;
9058           return;
9059         }
9060         cps->maybeThinking = FALSE;
9061
9062         if (StrStr(message, "draw")) {
9063             /* Program doesn't have "draw" command */
9064             cps->sendDrawOffers = 0;
9065             return;
9066         }
9067         if (cps->sendTime != 1 &&
9068             (StrStr(message, "time") || StrStr(message, "otim"))) {
9069           /* Program apparently doesn't have "time" or "otim" command */
9070           cps->sendTime = 0;
9071           return;
9072         }
9073         if (StrStr(message, "analyze")) {
9074             cps->analysisSupport = FALSE;
9075             cps->analyzing = FALSE;
9076 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9077             EditGameEvent(); // [HGM] try to preserve loaded game
9078             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9079             DisplayError(buf2, 0);
9080             return;
9081         }
9082         if (StrStr(message, "(no matching move)st")) {
9083           /* Special kludge for GNU Chess 4 only */
9084           cps->stKludge = TRUE;
9085           SendTimeControl(cps, movesPerSession, timeControl,
9086                           timeIncrement, appData.searchDepth,
9087                           searchTime);
9088           return;
9089         }
9090         if (StrStr(message, "(no matching move)sd")) {
9091           /* Special kludge for GNU Chess 4 only */
9092           cps->sdKludge = TRUE;
9093           SendTimeControl(cps, movesPerSession, timeControl,
9094                           timeIncrement, appData.searchDepth,
9095                           searchTime);
9096           return;
9097         }
9098         if (!StrStr(message, "llegal")) {
9099             return;
9100         }
9101         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9102             gameMode == IcsIdle) return;
9103         if (forwardMostMove <= backwardMostMove) return;
9104         if (pausing) PauseEvent();
9105       if(appData.forceIllegal) {
9106             // [HGM] illegal: machine refused move; force position after move into it
9107           SendToProgram("force\n", cps);
9108           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9109                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9110                 // when black is to move, while there might be nothing on a2 or black
9111                 // might already have the move. So send the board as if white has the move.
9112                 // But first we must change the stm of the engine, as it refused the last move
9113                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9114                 if(WhiteOnMove(forwardMostMove)) {
9115                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9116                     SendBoard(cps, forwardMostMove); // kludgeless board
9117                 } else {
9118                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9119                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9120                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9121                 }
9122           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9123             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9124                  gameMode == TwoMachinesPlay)
9125               SendToProgram("go\n", cps);
9126             return;
9127       } else
9128         if (gameMode == PlayFromGameFile) {
9129             /* Stop reading this game file */
9130             gameMode = EditGame;
9131             ModeHighlight();
9132         }
9133         /* [HGM] illegal-move claim should forfeit game when Xboard */
9134         /* only passes fully legal moves                            */
9135         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9136             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9137                                 "False illegal-move claim", GE_XBOARD );
9138             return; // do not take back move we tested as valid
9139         }
9140         currentMove = forwardMostMove-1;
9141         DisplayMove(currentMove-1); /* before DisplayMoveError */
9142         SwitchClocks(forwardMostMove-1); // [HGM] race
9143         DisplayBothClocks();
9144         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9145                 parseList[currentMove], _(cps->which));
9146         DisplayMoveError(buf1);
9147         DrawPosition(FALSE, boards[currentMove]);
9148
9149         SetUserThinkingEnables();
9150         return;
9151     }
9152     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9153         /* Program has a broken "time" command that
9154            outputs a string not ending in newline.
9155            Don't use it. */
9156         cps->sendTime = 0;
9157     }
9158     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9159         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9160             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9161     }
9162
9163     /*
9164      * If chess program startup fails, exit with an error message.
9165      * Attempts to recover here are futile. [HGM] Well, we try anyway
9166      */
9167     if ((StrStr(message, "unknown host") != NULL)
9168         || (StrStr(message, "No remote directory") != NULL)
9169         || (StrStr(message, "not found") != NULL)
9170         || (StrStr(message, "No such file") != NULL)
9171         || (StrStr(message, "can't alloc") != NULL)
9172         || (StrStr(message, "Permission denied") != NULL)) {
9173
9174         cps->maybeThinking = FALSE;
9175         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9176                 _(cps->which), cps->program, cps->host, message);
9177         RemoveInputSource(cps->isr);
9178         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9179             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9180             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9181         }
9182         return;
9183     }
9184
9185     /*
9186      * Look for hint output
9187      */
9188     if (sscanf(message, "Hint: %s", buf1) == 1) {
9189         if (cps == &first && hintRequested) {
9190             hintRequested = FALSE;
9191             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9192                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9193                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9194                                     PosFlags(forwardMostMove),
9195                                     fromY, fromX, toY, toX, promoChar, buf1);
9196                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9197                 DisplayInformation(buf2);
9198             } else {
9199                 /* Hint move could not be parsed!? */
9200               snprintf(buf2, sizeof(buf2),
9201                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9202                         buf1, _(cps->which));
9203                 DisplayError(buf2, 0);
9204             }
9205         } else {
9206           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9207         }
9208         return;
9209     }
9210
9211     /*
9212      * Ignore other messages if game is not in progress
9213      */
9214     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9215         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9216
9217     /*
9218      * look for win, lose, draw, or draw offer
9219      */
9220     if (strncmp(message, "1-0", 3) == 0) {
9221         char *p, *q, *r = "";
9222         p = strchr(message, '{');
9223         if (p) {
9224             q = strchr(p, '}');
9225             if (q) {
9226                 *q = NULLCHAR;
9227                 r = p + 1;
9228             }
9229         }
9230         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9231         return;
9232     } else if (strncmp(message, "0-1", 3) == 0) {
9233         char *p, *q, *r = "";
9234         p = strchr(message, '{');
9235         if (p) {
9236             q = strchr(p, '}');
9237             if (q) {
9238                 *q = NULLCHAR;
9239                 r = p + 1;
9240             }
9241         }
9242         /* Kludge for Arasan 4.1 bug */
9243         if (strcmp(r, "Black resigns") == 0) {
9244             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9245             return;
9246         }
9247         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9248         return;
9249     } else if (strncmp(message, "1/2", 3) == 0) {
9250         char *p, *q, *r = "";
9251         p = strchr(message, '{');
9252         if (p) {
9253             q = strchr(p, '}');
9254             if (q) {
9255                 *q = NULLCHAR;
9256                 r = p + 1;
9257             }
9258         }
9259
9260         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9261         return;
9262
9263     } else if (strncmp(message, "White resign", 12) == 0) {
9264         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9265         return;
9266     } else if (strncmp(message, "Black resign", 12) == 0) {
9267         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9268         return;
9269     } else if (strncmp(message, "White matches", 13) == 0 ||
9270                strncmp(message, "Black matches", 13) == 0   ) {
9271         /* [HGM] ignore GNUShogi noises */
9272         return;
9273     } else if (strncmp(message, "White", 5) == 0 &&
9274                message[5] != '(' &&
9275                StrStr(message, "Black") == NULL) {
9276         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9277         return;
9278     } else if (strncmp(message, "Black", 5) == 0 &&
9279                message[5] != '(') {
9280         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9281         return;
9282     } else if (strcmp(message, "resign") == 0 ||
9283                strcmp(message, "computer resigns") == 0) {
9284         switch (gameMode) {
9285           case MachinePlaysBlack:
9286           case IcsPlayingBlack:
9287             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9288             break;
9289           case MachinePlaysWhite:
9290           case IcsPlayingWhite:
9291             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9292             break;
9293           case TwoMachinesPlay:
9294             if (cps->twoMachinesColor[0] == 'w')
9295               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9296             else
9297               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9298             break;
9299           default:
9300             /* can't happen */
9301             break;
9302         }
9303         return;
9304     } else if (strncmp(message, "opponent mates", 14) == 0) {
9305         switch (gameMode) {
9306           case MachinePlaysBlack:
9307           case IcsPlayingBlack:
9308             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9309             break;
9310           case MachinePlaysWhite:
9311           case IcsPlayingWhite:
9312             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9313             break;
9314           case TwoMachinesPlay:
9315             if (cps->twoMachinesColor[0] == 'w')
9316               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9317             else
9318               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9319             break;
9320           default:
9321             /* can't happen */
9322             break;
9323         }
9324         return;
9325     } else if (strncmp(message, "computer mates", 14) == 0) {
9326         switch (gameMode) {
9327           case MachinePlaysBlack:
9328           case IcsPlayingBlack:
9329             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9330             break;
9331           case MachinePlaysWhite:
9332           case IcsPlayingWhite:
9333             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9334             break;
9335           case TwoMachinesPlay:
9336             if (cps->twoMachinesColor[0] == 'w')
9337               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9338             else
9339               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9340             break;
9341           default:
9342             /* can't happen */
9343             break;
9344         }
9345         return;
9346     } else if (strncmp(message, "checkmate", 9) == 0) {
9347         if (WhiteOnMove(forwardMostMove)) {
9348             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9349         } else {
9350             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9351         }
9352         return;
9353     } else if (strstr(message, "Draw") != NULL ||
9354                strstr(message, "game is a draw") != NULL) {
9355         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9356         return;
9357     } else if (strstr(message, "offer") != NULL &&
9358                strstr(message, "draw") != NULL) {
9359 #if ZIPPY
9360         if (appData.zippyPlay && first.initDone) {
9361             /* Relay offer to ICS */
9362             SendToICS(ics_prefix);
9363             SendToICS("draw\n");
9364         }
9365 #endif
9366         cps->offeredDraw = 2; /* valid until this engine moves twice */
9367         if (gameMode == TwoMachinesPlay) {
9368             if (cps->other->offeredDraw) {
9369                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9370             /* [HGM] in two-machine mode we delay relaying draw offer      */
9371             /* until after we also have move, to see if it is really claim */
9372             }
9373         } else if (gameMode == MachinePlaysWhite ||
9374                    gameMode == MachinePlaysBlack) {
9375           if (userOfferedDraw) {
9376             DisplayInformation(_("Machine accepts your draw offer"));
9377             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9378           } else {
9379             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9380           }
9381         }
9382     }
9383
9384
9385     /*
9386      * Look for thinking output
9387      */
9388     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9389           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9390                                 ) {
9391         int plylev, mvleft, mvtot, curscore, time;
9392         char mvname[MOVE_LEN];
9393         u64 nodes; // [DM]
9394         char plyext;
9395         int ignore = FALSE;
9396         int prefixHint = FALSE;
9397         mvname[0] = NULLCHAR;
9398
9399         switch (gameMode) {
9400           case MachinePlaysBlack:
9401           case IcsPlayingBlack:
9402             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9403             break;
9404           case MachinePlaysWhite:
9405           case IcsPlayingWhite:
9406             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9407             break;
9408           case AnalyzeMode:
9409           case AnalyzeFile:
9410             break;
9411           case IcsObserving: /* [DM] icsEngineAnalyze */
9412             if (!appData.icsEngineAnalyze) ignore = TRUE;
9413             break;
9414           case TwoMachinesPlay:
9415             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9416                 ignore = TRUE;
9417             }
9418             break;
9419           default:
9420             ignore = TRUE;
9421             break;
9422         }
9423
9424         if (!ignore) {
9425             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9426             buf1[0] = NULLCHAR;
9427             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9428                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9429
9430                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9431                     nodes += u64Const(0x100000000);
9432
9433                 if (plyext != ' ' && plyext != '\t') {
9434                     time *= 100;
9435                 }
9436
9437                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9438                 if( cps->scoreIsAbsolute &&
9439                     ( gameMode == MachinePlaysBlack ||
9440                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9441                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9442                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9443                      !WhiteOnMove(currentMove)
9444                     ) )
9445                 {
9446                     curscore = -curscore;
9447                 }
9448
9449                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9450
9451                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9452                         char buf[MSG_SIZ];
9453                         FILE *f;
9454                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9455                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9456                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9457                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9458                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9459                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9460                                 fclose(f);
9461                         }
9462                         else
9463                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9464                           DisplayError(_("failed writing PV"), 0);
9465                 }
9466
9467                 tempStats.depth = plylev;
9468                 tempStats.nodes = nodes;
9469                 tempStats.time = time;
9470                 tempStats.score = curscore;
9471                 tempStats.got_only_move = 0;
9472
9473                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9474                         int ticklen;
9475
9476                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9477                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9478                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9479                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9480                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9481                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9482                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9483                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9484                 }
9485
9486                 /* Buffer overflow protection */
9487                 if (pv[0] != NULLCHAR) {
9488                     if (strlen(pv) >= sizeof(tempStats.movelist)
9489                         && appData.debugMode) {
9490                         fprintf(debugFP,
9491                                 "PV is too long; using the first %u bytes.\n",
9492                                 (unsigned) sizeof(tempStats.movelist) - 1);
9493                     }
9494
9495                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9496                 } else {
9497                     sprintf(tempStats.movelist, " no PV\n");
9498                 }
9499
9500                 if (tempStats.seen_stat) {
9501                     tempStats.ok_to_send = 1;
9502                 }
9503
9504                 if (strchr(tempStats.movelist, '(') != NULL) {
9505                     tempStats.line_is_book = 1;
9506                     tempStats.nr_moves = 0;
9507                     tempStats.moves_left = 0;
9508                 } else {
9509                     tempStats.line_is_book = 0;
9510                 }
9511
9512                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9513                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9514
9515                 SendProgramStatsToFrontend( cps, &tempStats );
9516
9517                 /*
9518                     [AS] Protect the thinkOutput buffer from overflow... this
9519                     is only useful if buf1 hasn't overflowed first!
9520                 */
9521                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9522                          plylev,
9523                          (gameMode == TwoMachinesPlay ?
9524                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9525                          ((double) curscore) / 100.0,
9526                          prefixHint ? lastHint : "",
9527                          prefixHint ? " " : "" );
9528
9529                 if( buf1[0] != NULLCHAR ) {
9530                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9531
9532                     if( strlen(pv) > max_len ) {
9533                         if( appData.debugMode) {
9534                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9535                         }
9536                         pv[max_len+1] = '\0';
9537                     }
9538
9539                     strcat( thinkOutput, pv);
9540                 }
9541
9542                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9543                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9544                     DisplayMove(currentMove - 1);
9545                 }
9546                 return;
9547
9548             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9549                 /* crafty (9.25+) says "(only move) <move>"
9550                  * if there is only 1 legal move
9551                  */
9552                 sscanf(p, "(only move) %s", buf1);
9553                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9554                 sprintf(programStats.movelist, "%s (only move)", buf1);
9555                 programStats.depth = 1;
9556                 programStats.nr_moves = 1;
9557                 programStats.moves_left = 1;
9558                 programStats.nodes = 1;
9559                 programStats.time = 1;
9560                 programStats.got_only_move = 1;
9561
9562                 /* Not really, but we also use this member to
9563                    mean "line isn't going to change" (Crafty
9564                    isn't searching, so stats won't change) */
9565                 programStats.line_is_book = 1;
9566
9567                 SendProgramStatsToFrontend( cps, &programStats );
9568
9569                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9570                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9571                     DisplayMove(currentMove - 1);
9572                 }
9573                 return;
9574             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9575                               &time, &nodes, &plylev, &mvleft,
9576                               &mvtot, mvname) >= 5) {
9577                 /* The stat01: line is from Crafty (9.29+) in response
9578                    to the "." command */
9579                 programStats.seen_stat = 1;
9580                 cps->maybeThinking = TRUE;
9581
9582                 if (programStats.got_only_move || !appData.periodicUpdates)
9583                   return;
9584
9585                 programStats.depth = plylev;
9586                 programStats.time = time;
9587                 programStats.nodes = nodes;
9588                 programStats.moves_left = mvleft;
9589                 programStats.nr_moves = mvtot;
9590                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9591                 programStats.ok_to_send = 1;
9592                 programStats.movelist[0] = '\0';
9593
9594                 SendProgramStatsToFrontend( cps, &programStats );
9595
9596                 return;
9597
9598             } else if (strncmp(message,"++",2) == 0) {
9599                 /* Crafty 9.29+ outputs this */
9600                 programStats.got_fail = 2;
9601                 return;
9602
9603             } else if (strncmp(message,"--",2) == 0) {
9604                 /* Crafty 9.29+ outputs this */
9605                 programStats.got_fail = 1;
9606                 return;
9607
9608             } else if (thinkOutput[0] != NULLCHAR &&
9609                        strncmp(message, "    ", 4) == 0) {
9610                 unsigned message_len;
9611
9612                 p = message;
9613                 while (*p && *p == ' ') p++;
9614
9615                 message_len = strlen( p );
9616
9617                 /* [AS] Avoid buffer overflow */
9618                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9619                     strcat(thinkOutput, " ");
9620                     strcat(thinkOutput, p);
9621                 }
9622
9623                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9624                     strcat(programStats.movelist, " ");
9625                     strcat(programStats.movelist, p);
9626                 }
9627
9628                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9629                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9630                     DisplayMove(currentMove - 1);
9631                 }
9632                 return;
9633             }
9634         }
9635         else {
9636             buf1[0] = NULLCHAR;
9637
9638             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9639                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9640             {
9641                 ChessProgramStats cpstats;
9642
9643                 if (plyext != ' ' && plyext != '\t') {
9644                     time *= 100;
9645                 }
9646
9647                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9648                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9649                     curscore = -curscore;
9650                 }
9651
9652                 cpstats.depth = plylev;
9653                 cpstats.nodes = nodes;
9654                 cpstats.time = time;
9655                 cpstats.score = curscore;
9656                 cpstats.got_only_move = 0;
9657                 cpstats.movelist[0] = '\0';
9658
9659                 if (buf1[0] != NULLCHAR) {
9660                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9661                 }
9662
9663                 cpstats.ok_to_send = 0;
9664                 cpstats.line_is_book = 0;
9665                 cpstats.nr_moves = 0;
9666                 cpstats.moves_left = 0;
9667
9668                 SendProgramStatsToFrontend( cps, &cpstats );
9669             }
9670         }
9671     }
9672 }
9673
9674
9675 /* Parse a game score from the character string "game", and
9676    record it as the history of the current game.  The game
9677    score is NOT assumed to start from the standard position.
9678    The display is not updated in any way.
9679    */
9680 void
9681 ParseGameHistory (char *game)
9682 {
9683     ChessMove moveType;
9684     int fromX, fromY, toX, toY, boardIndex;
9685     char promoChar;
9686     char *p, *q;
9687     char buf[MSG_SIZ];
9688
9689     if (appData.debugMode)
9690       fprintf(debugFP, "Parsing game history: %s\n", game);
9691
9692     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9693     gameInfo.site = StrSave(appData.icsHost);
9694     gameInfo.date = PGNDate();
9695     gameInfo.round = StrSave("-");
9696
9697     /* Parse out names of players */
9698     while (*game == ' ') game++;
9699     p = buf;
9700     while (*game != ' ') *p++ = *game++;
9701     *p = NULLCHAR;
9702     gameInfo.white = StrSave(buf);
9703     while (*game == ' ') game++;
9704     p = buf;
9705     while (*game != ' ' && *game != '\n') *p++ = *game++;
9706     *p = NULLCHAR;
9707     gameInfo.black = StrSave(buf);
9708
9709     /* Parse moves */
9710     boardIndex = blackPlaysFirst ? 1 : 0;
9711     yynewstr(game);
9712     for (;;) {
9713         yyboardindex = boardIndex;
9714         moveType = (ChessMove) Myylex();
9715         switch (moveType) {
9716           case IllegalMove:             /* maybe suicide chess, etc. */
9717   if (appData.debugMode) {
9718     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9719     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9720     setbuf(debugFP, NULL);
9721   }
9722           case WhitePromotion:
9723           case BlackPromotion:
9724           case WhiteNonPromotion:
9725           case BlackNonPromotion:
9726           case NormalMove:
9727           case FirstLeg:
9728           case WhiteCapturesEnPassant:
9729           case BlackCapturesEnPassant:
9730           case WhiteKingSideCastle:
9731           case WhiteQueenSideCastle:
9732           case BlackKingSideCastle:
9733           case BlackQueenSideCastle:
9734           case WhiteKingSideCastleWild:
9735           case WhiteQueenSideCastleWild:
9736           case BlackKingSideCastleWild:
9737           case BlackQueenSideCastleWild:
9738           /* PUSH Fabien */
9739           case WhiteHSideCastleFR:
9740           case WhiteASideCastleFR:
9741           case BlackHSideCastleFR:
9742           case BlackASideCastleFR:
9743           /* POP Fabien */
9744             fromX = currentMoveString[0] - AAA;
9745             fromY = currentMoveString[1] - ONE;
9746             toX = currentMoveString[2] - AAA;
9747             toY = currentMoveString[3] - ONE;
9748             promoChar = currentMoveString[4];
9749             break;
9750           case WhiteDrop:
9751           case BlackDrop:
9752             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9753             fromX = moveType == WhiteDrop ?
9754               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9755             (int) CharToPiece(ToLower(currentMoveString[0]));
9756             fromY = DROP_RANK;
9757             toX = currentMoveString[2] - AAA;
9758             toY = currentMoveString[3] - ONE;
9759             promoChar = NULLCHAR;
9760             break;
9761           case AmbiguousMove:
9762             /* bug? */
9763             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9764   if (appData.debugMode) {
9765     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9766     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9767     setbuf(debugFP, NULL);
9768   }
9769             DisplayError(buf, 0);
9770             return;
9771           case ImpossibleMove:
9772             /* bug? */
9773             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9774   if (appData.debugMode) {
9775     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9776     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9777     setbuf(debugFP, NULL);
9778   }
9779             DisplayError(buf, 0);
9780             return;
9781           case EndOfFile:
9782             if (boardIndex < backwardMostMove) {
9783                 /* Oops, gap.  How did that happen? */
9784                 DisplayError(_("Gap in move list"), 0);
9785                 return;
9786             }
9787             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9788             if (boardIndex > forwardMostMove) {
9789                 forwardMostMove = boardIndex;
9790             }
9791             return;
9792           case ElapsedTime:
9793             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9794                 strcat(parseList[boardIndex-1], " ");
9795                 strcat(parseList[boardIndex-1], yy_text);
9796             }
9797             continue;
9798           case Comment:
9799           case PGNTag:
9800           case NAG:
9801           default:
9802             /* ignore */
9803             continue;
9804           case WhiteWins:
9805           case BlackWins:
9806           case GameIsDrawn:
9807           case GameUnfinished:
9808             if (gameMode == IcsExamining) {
9809                 if (boardIndex < backwardMostMove) {
9810                     /* Oops, gap.  How did that happen? */
9811                     return;
9812                 }
9813                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9814                 return;
9815             }
9816             gameInfo.result = moveType;
9817             p = strchr(yy_text, '{');
9818             if (p == NULL) p = strchr(yy_text, '(');
9819             if (p == NULL) {
9820                 p = yy_text;
9821                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9822             } else {
9823                 q = strchr(p, *p == '{' ? '}' : ')');
9824                 if (q != NULL) *q = NULLCHAR;
9825                 p++;
9826             }
9827             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9828             gameInfo.resultDetails = StrSave(p);
9829             continue;
9830         }
9831         if (boardIndex >= forwardMostMove &&
9832             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9833             backwardMostMove = blackPlaysFirst ? 1 : 0;
9834             return;
9835         }
9836         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9837                                  fromY, fromX, toY, toX, promoChar,
9838                                  parseList[boardIndex]);
9839         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9840         /* currentMoveString is set as a side-effect of yylex */
9841         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9842         strcat(moveList[boardIndex], "\n");
9843         boardIndex++;
9844         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9845         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9846           case MT_NONE:
9847           case MT_STALEMATE:
9848           default:
9849             break;
9850           case MT_CHECK:
9851             if(!IS_SHOGI(gameInfo.variant))
9852                 strcat(parseList[boardIndex - 1], "+");
9853             break;
9854           case MT_CHECKMATE:
9855           case MT_STAINMATE:
9856             strcat(parseList[boardIndex - 1], "#");
9857             break;
9858         }
9859     }
9860 }
9861
9862
9863 /* Apply a move to the given board  */
9864 void
9865 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9866 {
9867   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9868   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9869
9870     /* [HGM] compute & store e.p. status and castling rights for new position */
9871     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9872
9873       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9874       oldEP = (signed char)board[EP_STATUS];
9875       board[EP_STATUS] = EP_NONE;
9876
9877   if (fromY == DROP_RANK) {
9878         /* must be first */
9879         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9880             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9881             return;
9882         }
9883         piece = board[toY][toX] = (ChessSquare) fromX;
9884   } else {
9885 //      ChessSquare victim;
9886       int i;
9887
9888       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9889 //           victim = board[killY][killX],
9890            board[killY][killX] = EmptySquare,
9891            board[EP_STATUS] = EP_CAPTURE;
9892
9893       if( board[toY][toX] != EmptySquare ) {
9894            board[EP_STATUS] = EP_CAPTURE;
9895            if( (fromX != toX || fromY != toY) && // not igui!
9896                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9897                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9898                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9899            }
9900       }
9901
9902       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9903            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9904                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9905       } else
9906       if( board[fromY][fromX] == WhitePawn ) {
9907            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9908                board[EP_STATUS] = EP_PAWN_MOVE;
9909            if( toY-fromY==2) {
9910                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9911                         gameInfo.variant != VariantBerolina || toX < fromX)
9912                       board[EP_STATUS] = toX | berolina;
9913                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9914                         gameInfo.variant != VariantBerolina || toX > fromX)
9915                       board[EP_STATUS] = toX;
9916            }
9917       } else
9918       if( board[fromY][fromX] == BlackPawn ) {
9919            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9920                board[EP_STATUS] = EP_PAWN_MOVE;
9921            if( toY-fromY== -2) {
9922                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9923                         gameInfo.variant != VariantBerolina || toX < fromX)
9924                       board[EP_STATUS] = toX | berolina;
9925                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9926                         gameInfo.variant != VariantBerolina || toX > fromX)
9927                       board[EP_STATUS] = toX;
9928            }
9929        }
9930
9931        for(i=0; i<nrCastlingRights; i++) {
9932            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9933               board[CASTLING][i] == toX   && castlingRank[i] == toY
9934              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9935        }
9936
9937        if(gameInfo.variant == VariantSChess) { // update virginity
9938            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9939            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9940            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9941            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9942        }
9943
9944      if (fromX == toX && fromY == toY) return;
9945
9946      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9947      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9948      if(gameInfo.variant == VariantKnightmate)
9949          king += (int) WhiteUnicorn - (int) WhiteKing;
9950
9951     /* Code added by Tord: */
9952     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9953     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9954         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9955       board[fromY][fromX] = EmptySquare;
9956       board[toY][toX] = EmptySquare;
9957       if((toX > fromX) != (piece == WhiteRook)) {
9958         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9959       } else {
9960         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9961       }
9962     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9963                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9964       board[fromY][fromX] = EmptySquare;
9965       board[toY][toX] = EmptySquare;
9966       if((toX > fromX) != (piece == BlackRook)) {
9967         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9968       } else {
9969         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9970       }
9971     /* End of code added by Tord */
9972
9973     } else if (board[fromY][fromX] == king
9974         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9975         && toY == fromY && toX > fromX+1) {
9976         board[fromY][fromX] = EmptySquare;
9977         board[toY][toX] = king;
9978         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9979         board[fromY][BOARD_RGHT-1] = EmptySquare;
9980     } else if (board[fromY][fromX] == king
9981         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9982                && toY == fromY && toX < fromX-1) {
9983         board[fromY][fromX] = EmptySquare;
9984         board[toY][toX] = king;
9985         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9986         board[fromY][BOARD_LEFT] = EmptySquare;
9987     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9988                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9989                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9990                ) {
9991         /* white pawn promotion */
9992         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9993         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9994             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9995         board[fromY][fromX] = EmptySquare;
9996     } else if ((fromY >= BOARD_HEIGHT>>1)
9997                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9998                && (toX != fromX)
9999                && gameInfo.variant != VariantXiangqi
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         captured = board[toY - 1][toX];
10006         board[toY - 1][toX] = EmptySquare;
10007     } else if ((fromY == BOARD_HEIGHT-4)
10008                && (toX == fromX)
10009                && gameInfo.variant == VariantBerolina
10010                && (board[fromY][fromX] == WhitePawn)
10011                && (board[toY][toX] == EmptySquare)) {
10012         board[fromY][fromX] = EmptySquare;
10013         board[toY][toX] = WhitePawn;
10014         if(oldEP & EP_BEROLIN_A) {
10015                 captured = board[fromY][fromX-1];
10016                 board[fromY][fromX-1] = EmptySquare;
10017         }else{  captured = board[fromY][fromX+1];
10018                 board[fromY][fromX+1] = EmptySquare;
10019         }
10020     } else if (board[fromY][fromX] == king
10021         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10022                && toY == fromY && toX > fromX+1) {
10023         board[fromY][fromX] = EmptySquare;
10024         board[toY][toX] = king;
10025         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10026         board[fromY][BOARD_RGHT-1] = EmptySquare;
10027     } else if (board[fromY][fromX] == king
10028         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10029                && toY == fromY && toX < fromX-1) {
10030         board[fromY][fromX] = EmptySquare;
10031         board[toY][toX] = king;
10032         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10033         board[fromY][BOARD_LEFT] = EmptySquare;
10034     } else if (fromY == 7 && fromX == 3
10035                && board[fromY][fromX] == BlackKing
10036                && toY == 7 && toX == 5) {
10037         board[fromY][fromX] = EmptySquare;
10038         board[toY][toX] = BlackKing;
10039         board[fromY][7] = EmptySquare;
10040         board[toY][4] = BlackRook;
10041     } else if (fromY == 7 && fromX == 3
10042                && board[fromY][fromX] == BlackKing
10043                && toY == 7 && toX == 1) {
10044         board[fromY][fromX] = EmptySquare;
10045         board[toY][toX] = BlackKing;
10046         board[fromY][0] = EmptySquare;
10047         board[toY][2] = BlackRook;
10048     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10049                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10050                && toY < promoRank && promoChar
10051                ) {
10052         /* black pawn promotion */
10053         board[toY][toX] = CharToPiece(ToLower(promoChar));
10054         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10055             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10056         board[fromY][fromX] = EmptySquare;
10057     } else if ((fromY < BOARD_HEIGHT>>1)
10058                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10059                && (toX != fromX)
10060                && gameInfo.variant != VariantXiangqi
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         captured = board[toY + 1][toX];
10067         board[toY + 1][toX] = EmptySquare;
10068     } else if ((fromY == 3)
10069                && (toX == fromX)
10070                && gameInfo.variant == VariantBerolina
10071                && (board[fromY][fromX] == BlackPawn)
10072                && (board[toY][toX] == EmptySquare)) {
10073         board[fromY][fromX] = EmptySquare;
10074         board[toY][toX] = BlackPawn;
10075         if(oldEP & EP_BEROLIN_A) {
10076                 captured = board[fromY][fromX-1];
10077                 board[fromY][fromX-1] = EmptySquare;
10078         }else{  captured = board[fromY][fromX+1];
10079                 board[fromY][fromX+1] = EmptySquare;
10080         }
10081     } else {
10082         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10083         board[fromY][fromX] = EmptySquare;
10084         board[toY][toX] = piece;
10085     }
10086   }
10087
10088     if (gameInfo.holdingsWidth != 0) {
10089
10090       /* !!A lot more code needs to be written to support holdings  */
10091       /* [HGM] OK, so I have written it. Holdings are stored in the */
10092       /* penultimate board files, so they are automaticlly stored   */
10093       /* in the game history.                                       */
10094       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10095                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10096         /* Delete from holdings, by decreasing count */
10097         /* and erasing image if necessary            */
10098         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10099         if(p < (int) BlackPawn) { /* white drop */
10100              p -= (int)WhitePawn;
10101                  p = PieceToNumber((ChessSquare)p);
10102              if(p >= gameInfo.holdingsSize) p = 0;
10103              if(--board[p][BOARD_WIDTH-2] <= 0)
10104                   board[p][BOARD_WIDTH-1] = EmptySquare;
10105              if((int)board[p][BOARD_WIDTH-2] < 0)
10106                         board[p][BOARD_WIDTH-2] = 0;
10107         } else {                  /* black drop */
10108              p -= (int)BlackPawn;
10109                  p = PieceToNumber((ChessSquare)p);
10110              if(p >= gameInfo.holdingsSize) p = 0;
10111              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10112                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10113              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10114                         board[BOARD_HEIGHT-1-p][1] = 0;
10115         }
10116       }
10117       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10118           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10119         /* [HGM] holdings: Add to holdings, if holdings exist */
10120         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10121                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10122                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10123         }
10124         p = (int) captured;
10125         if (p >= (int) BlackPawn) {
10126           p -= (int)BlackPawn;
10127           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10128                   /* in Shogi restore piece to its original  first */
10129                   captured = (ChessSquare) (DEMOTED captured);
10130                   p = DEMOTED p;
10131           }
10132           p = PieceToNumber((ChessSquare)p);
10133           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10134           board[p][BOARD_WIDTH-2]++;
10135           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10136         } else {
10137           p -= (int)WhitePawn;
10138           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10139                   captured = (ChessSquare) (DEMOTED captured);
10140                   p = DEMOTED p;
10141           }
10142           p = PieceToNumber((ChessSquare)p);
10143           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10144           board[BOARD_HEIGHT-1-p][1]++;
10145           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10146         }
10147       }
10148     } else if (gameInfo.variant == VariantAtomic) {
10149       if (captured != EmptySquare) {
10150         int y, x;
10151         for (y = toY-1; y <= toY+1; y++) {
10152           for (x = toX-1; x <= toX+1; x++) {
10153             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10154                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10155               board[y][x] = EmptySquare;
10156             }
10157           }
10158         }
10159         board[toY][toX] = EmptySquare;
10160       }
10161     }
10162
10163     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10164         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10165     } else
10166     if(promoChar == '+') {
10167         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10168         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10169         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10170           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10171     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10172         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10173         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10174            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10175         board[toY][toX] = newPiece;
10176     }
10177     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10178                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10179         // [HGM] superchess: take promotion piece out of holdings
10180         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10181         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10182             if(!--board[k][BOARD_WIDTH-2])
10183                 board[k][BOARD_WIDTH-1] = EmptySquare;
10184         } else {
10185             if(!--board[BOARD_HEIGHT-1-k][1])
10186                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10187         }
10188     }
10189 }
10190
10191 /* Updates forwardMostMove */
10192 void
10193 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10194 {
10195     int x = toX, y = toY;
10196     char *s = parseList[forwardMostMove];
10197     ChessSquare p = boards[forwardMostMove][toY][toX];
10198 //    forwardMostMove++; // [HGM] bare: moved downstream
10199
10200     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10201     (void) CoordsToAlgebraic(boards[forwardMostMove],
10202                              PosFlags(forwardMostMove),
10203                              fromY, fromX, y, x, promoChar,
10204                              s);
10205     if(killX >= 0 && killY >= 0)
10206         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10207
10208     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10209         int timeLeft; static int lastLoadFlag=0; int king, piece;
10210         piece = boards[forwardMostMove][fromY][fromX];
10211         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10212         if(gameInfo.variant == VariantKnightmate)
10213             king += (int) WhiteUnicorn - (int) WhiteKing;
10214         if(forwardMostMove == 0) {
10215             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10216                 fprintf(serverMoves, "%s;", UserName());
10217             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10218                 fprintf(serverMoves, "%s;", second.tidy);
10219             fprintf(serverMoves, "%s;", first.tidy);
10220             if(gameMode == MachinePlaysWhite)
10221                 fprintf(serverMoves, "%s;", UserName());
10222             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10223                 fprintf(serverMoves, "%s;", second.tidy);
10224         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10225         lastLoadFlag = loadFlag;
10226         // print base move
10227         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10228         // print castling suffix
10229         if( toY == fromY && piece == king ) {
10230             if(toX-fromX > 1)
10231                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10232             if(fromX-toX >1)
10233                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10234         }
10235         // e.p. suffix
10236         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10237              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10238              boards[forwardMostMove][toY][toX] == EmptySquare
10239              && fromX != toX && fromY != toY)
10240                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10241         // promotion suffix
10242         if(promoChar != NULLCHAR) {
10243             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10244                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10245                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10246             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10247         }
10248         if(!loadFlag) {
10249                 char buf[MOVE_LEN*2], *p; int len;
10250             fprintf(serverMoves, "/%d/%d",
10251                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10252             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10253             else                      timeLeft = blackTimeRemaining/1000;
10254             fprintf(serverMoves, "/%d", timeLeft);
10255                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10256                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10257                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10258                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10259             fprintf(serverMoves, "/%s", buf);
10260         }
10261         fflush(serverMoves);
10262     }
10263
10264     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10265         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10266       return;
10267     }
10268     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10269     if (commentList[forwardMostMove+1] != NULL) {
10270         free(commentList[forwardMostMove+1]);
10271         commentList[forwardMostMove+1] = NULL;
10272     }
10273     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10274     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10275     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10276     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10277     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10278     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10279     adjustedClock = FALSE;
10280     gameInfo.result = GameUnfinished;
10281     if (gameInfo.resultDetails != NULL) {
10282         free(gameInfo.resultDetails);
10283         gameInfo.resultDetails = NULL;
10284     }
10285     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10286                               moveList[forwardMostMove - 1]);
10287     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10288       case MT_NONE:
10289       case MT_STALEMATE:
10290       default:
10291         break;
10292       case MT_CHECK:
10293         if(!IS_SHOGI(gameInfo.variant))
10294             strcat(parseList[forwardMostMove - 1], "+");
10295         break;
10296       case MT_CHECKMATE:
10297       case MT_STAINMATE:
10298         strcat(parseList[forwardMostMove - 1], "#");
10299         break;
10300     }
10301 }
10302
10303 /* Updates currentMove if not pausing */
10304 void
10305 ShowMove (int fromX, int fromY, int toX, int toY)
10306 {
10307     int instant = (gameMode == PlayFromGameFile) ?
10308         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10309     if(appData.noGUI) return;
10310     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10311         if (!instant) {
10312             if (forwardMostMove == currentMove + 1) {
10313                 AnimateMove(boards[forwardMostMove - 1],
10314                             fromX, fromY, toX, toY);
10315             }
10316         }
10317         currentMove = forwardMostMove;
10318     }
10319
10320     killX = killY = -1; // [HGM] lion: used up
10321
10322     if (instant) return;
10323
10324     DisplayMove(currentMove - 1);
10325     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10326             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10327                 SetHighlights(fromX, fromY, toX, toY);
10328             }
10329     }
10330     DrawPosition(FALSE, boards[currentMove]);
10331     DisplayBothClocks();
10332     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10333 }
10334
10335 void
10336 SendEgtPath (ChessProgramState *cps)
10337 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10338         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10339
10340         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10341
10342         while(*p) {
10343             char c, *q = name+1, *r, *s;
10344
10345             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10346             while(*p && *p != ',') *q++ = *p++;
10347             *q++ = ':'; *q = 0;
10348             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10349                 strcmp(name, ",nalimov:") == 0 ) {
10350                 // take nalimov path from the menu-changeable option first, if it is defined
10351               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10352                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10353             } else
10354             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10355                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10356                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10357                 s = r = StrStr(s, ":") + 1; // beginning of path info
10358                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10359                 c = *r; *r = 0;             // temporarily null-terminate path info
10360                     *--q = 0;               // strip of trailig ':' from name
10361                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10362                 *r = c;
10363                 SendToProgram(buf,cps);     // send egtbpath command for this format
10364             }
10365             if(*p == ',') p++; // read away comma to position for next format name
10366         }
10367 }
10368
10369 static int
10370 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10371 {
10372       int width = 8, height = 8, holdings = 0;             // most common sizes
10373       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10374       // correct the deviations default for each variant
10375       if( v == VariantXiangqi ) width = 9,  height = 10;
10376       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10377       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10378       if( v == VariantCapablanca || v == VariantCapaRandom ||
10379           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10380                                 width = 10;
10381       if( v == VariantCourier ) width = 12;
10382       if( v == VariantSuper )                            holdings = 8;
10383       if( v == VariantGreat )   width = 10,              holdings = 8;
10384       if( v == VariantSChess )                           holdings = 7;
10385       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10386       if( v == VariantChuChess) width = 10, height = 10;
10387       if( v == VariantChu )     width = 12, height = 12;
10388       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10389              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10390              holdingsSize >= 0 && holdingsSize != holdings;
10391 }
10392
10393 char variantError[MSG_SIZ];
10394
10395 char *
10396 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10397 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10398       char *p, *variant = VariantName(v);
10399       static char b[MSG_SIZ];
10400       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10401            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10402                                                holdingsSize, variant); // cook up sized variant name
10403            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10404            if(StrStr(list, b) == NULL) {
10405                // specific sized variant not known, check if general sizing allowed
10406                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10407                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10408                             boardWidth, boardHeight, holdingsSize, engine);
10409                    return NULL;
10410                }
10411                /* [HGM] here we really should compare with the maximum supported board size */
10412            }
10413       } else snprintf(b, MSG_SIZ,"%s", variant);
10414       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10415       p = StrStr(list, b);
10416       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10417       if(p == NULL) {
10418           // occurs not at all in list, or only as sub-string
10419           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10420           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10421               int l = strlen(variantError);
10422               char *q;
10423               while(p != list && p[-1] != ',') p--;
10424               q = strchr(p, ',');
10425               if(q) *q = NULLCHAR;
10426               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10427               if(q) *q= ',';
10428           }
10429           return NULL;
10430       }
10431       return b;
10432 }
10433
10434 void
10435 InitChessProgram (ChessProgramState *cps, int setup)
10436 /* setup needed to setup FRC opening position */
10437 {
10438     char buf[MSG_SIZ], *b;
10439     if (appData.noChessProgram) return;
10440     hintRequested = FALSE;
10441     bookRequested = FALSE;
10442
10443     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10444     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10445     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10446     if(cps->memSize) { /* [HGM] memory */
10447       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10448         SendToProgram(buf, cps);
10449     }
10450     SendEgtPath(cps); /* [HGM] EGT */
10451     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10452       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10453         SendToProgram(buf, cps);
10454     }
10455
10456     setboardSpoiledMachineBlack = FALSE;
10457     SendToProgram(cps->initString, cps);
10458     if (gameInfo.variant != VariantNormal &&
10459         gameInfo.variant != VariantLoadable
10460         /* [HGM] also send variant if board size non-standard */
10461         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10462
10463       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10464                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10465       if (b == NULL) {
10466         DisplayFatalError(variantError, 0, 1);
10467         return;
10468       }
10469
10470       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10471       SendToProgram(buf, cps);
10472     }
10473     currentlyInitializedVariant = gameInfo.variant;
10474
10475     /* [HGM] send opening position in FRC to first engine */
10476     if(setup) {
10477           SendToProgram("force\n", cps);
10478           SendBoard(cps, 0);
10479           /* engine is now in force mode! Set flag to wake it up after first move. */
10480           setboardSpoiledMachineBlack = 1;
10481     }
10482
10483     if (cps->sendICS) {
10484       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10485       SendToProgram(buf, cps);
10486     }
10487     cps->maybeThinking = FALSE;
10488     cps->offeredDraw = 0;
10489     if (!appData.icsActive) {
10490         SendTimeControl(cps, movesPerSession, timeControl,
10491                         timeIncrement, appData.searchDepth,
10492                         searchTime);
10493     }
10494     if (appData.showThinking
10495         // [HGM] thinking: four options require thinking output to be sent
10496         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10497                                 ) {
10498         SendToProgram("post\n", cps);
10499     }
10500     SendToProgram("hard\n", cps);
10501     if (!appData.ponderNextMove) {
10502         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10503            it without being sure what state we are in first.  "hard"
10504            is not a toggle, so that one is OK.
10505          */
10506         SendToProgram("easy\n", cps);
10507     }
10508     if (cps->usePing) {
10509       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10510       SendToProgram(buf, cps);
10511     }
10512     cps->initDone = TRUE;
10513     ClearEngineOutputPane(cps == &second);
10514 }
10515
10516
10517 void
10518 ResendOptions (ChessProgramState *cps)
10519 { // send the stored value of the options
10520   int i;
10521   char buf[MSG_SIZ];
10522   Option *opt = cps->option;
10523   for(i=0; i<cps->nrOptions; i++, opt++) {
10524       switch(opt->type) {
10525         case Spin:
10526         case Slider:
10527         case CheckBox:
10528             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10529           break;
10530         case ComboBox:
10531           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10532           break;
10533         default:
10534             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10535           break;
10536         case Button:
10537         case SaveButton:
10538           continue;
10539       }
10540       SendToProgram(buf, cps);
10541   }
10542 }
10543
10544 void
10545 StartChessProgram (ChessProgramState *cps)
10546 {
10547     char buf[MSG_SIZ];
10548     int err;
10549
10550     if (appData.noChessProgram) return;
10551     cps->initDone = FALSE;
10552
10553     if (strcmp(cps->host, "localhost") == 0) {
10554         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10555     } else if (*appData.remoteShell == NULLCHAR) {
10556         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10557     } else {
10558         if (*appData.remoteUser == NULLCHAR) {
10559           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10560                     cps->program);
10561         } else {
10562           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10563                     cps->host, appData.remoteUser, cps->program);
10564         }
10565         err = StartChildProcess(buf, "", &cps->pr);
10566     }
10567
10568     if (err != 0) {
10569       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10570         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10571         if(cps != &first) return;
10572         appData.noChessProgram = TRUE;
10573         ThawUI();
10574         SetNCPMode();
10575 //      DisplayFatalError(buf, err, 1);
10576 //      cps->pr = NoProc;
10577 //      cps->isr = NULL;
10578         return;
10579     }
10580
10581     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10582     if (cps->protocolVersion > 1) {
10583       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10584       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10585         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10586         cps->comboCnt = 0;  //                and values of combo boxes
10587       }
10588       SendToProgram(buf, cps);
10589       if(cps->reload) ResendOptions(cps);
10590     } else {
10591       SendToProgram("xboard\n", cps);
10592     }
10593 }
10594
10595 void
10596 TwoMachinesEventIfReady P((void))
10597 {
10598   static int curMess = 0;
10599   if (first.lastPing != first.lastPong) {
10600     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10601     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10602     return;
10603   }
10604   if (second.lastPing != second.lastPong) {
10605     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10606     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10607     return;
10608   }
10609   DisplayMessage("", ""); curMess = 0;
10610   TwoMachinesEvent();
10611 }
10612
10613 char *
10614 MakeName (char *template)
10615 {
10616     time_t clock;
10617     struct tm *tm;
10618     static char buf[MSG_SIZ];
10619     char *p = buf;
10620     int i;
10621
10622     clock = time((time_t *)NULL);
10623     tm = localtime(&clock);
10624
10625     while(*p++ = *template++) if(p[-1] == '%') {
10626         switch(*template++) {
10627           case 0:   *p = 0; return buf;
10628           case 'Y': i = tm->tm_year+1900; break;
10629           case 'y': i = tm->tm_year-100; break;
10630           case 'M': i = tm->tm_mon+1; break;
10631           case 'd': i = tm->tm_mday; break;
10632           case 'h': i = tm->tm_hour; break;
10633           case 'm': i = tm->tm_min; break;
10634           case 's': i = tm->tm_sec; break;
10635           default:  i = 0;
10636         }
10637         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10638     }
10639     return buf;
10640 }
10641
10642 int
10643 CountPlayers (char *p)
10644 {
10645     int n = 0;
10646     while(p = strchr(p, '\n')) p++, n++; // count participants
10647     return n;
10648 }
10649
10650 FILE *
10651 WriteTourneyFile (char *results, FILE *f)
10652 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10653     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10654     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10655         // create a file with tournament description
10656         fprintf(f, "-participants {%s}\n", appData.participants);
10657         fprintf(f, "-seedBase %d\n", appData.seedBase);
10658         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10659         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10660         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10661         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10662         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10663         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10664         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10665         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10666         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10667         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10668         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10669         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10670         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10671         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10672         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10673         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10674         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10675         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10676         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10677         fprintf(f, "-smpCores %d\n", appData.smpCores);
10678         if(searchTime > 0)
10679                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10680         else {
10681                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10682                 fprintf(f, "-tc %s\n", appData.timeControl);
10683                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10684         }
10685         fprintf(f, "-results \"%s\"\n", results);
10686     }
10687     return f;
10688 }
10689
10690 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10691
10692 void
10693 Substitute (char *participants, int expunge)
10694 {
10695     int i, changed, changes=0, nPlayers=0;
10696     char *p, *q, *r, buf[MSG_SIZ];
10697     if(participants == NULL) return;
10698     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10699     r = p = participants; q = appData.participants;
10700     while(*p && *p == *q) {
10701         if(*p == '\n') r = p+1, nPlayers++;
10702         p++; q++;
10703     }
10704     if(*p) { // difference
10705         while(*p && *p++ != '\n');
10706         while(*q && *q++ != '\n');
10707       changed = nPlayers;
10708         changes = 1 + (strcmp(p, q) != 0);
10709     }
10710     if(changes == 1) { // a single engine mnemonic was changed
10711         q = r; while(*q) nPlayers += (*q++ == '\n');
10712         p = buf; while(*r && (*p = *r++) != '\n') p++;
10713         *p = NULLCHAR;
10714         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10715         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10716         if(mnemonic[i]) { // The substitute is valid
10717             FILE *f;
10718             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10719                 flock(fileno(f), LOCK_EX);
10720                 ParseArgsFromFile(f);
10721                 fseek(f, 0, SEEK_SET);
10722                 FREE(appData.participants); appData.participants = participants;
10723                 if(expunge) { // erase results of replaced engine
10724                     int len = strlen(appData.results), w, b, dummy;
10725                     for(i=0; i<len; i++) {
10726                         Pairing(i, nPlayers, &w, &b, &dummy);
10727                         if((w == changed || b == changed) && appData.results[i] == '*') {
10728                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10729                             fclose(f);
10730                             return;
10731                         }
10732                     }
10733                     for(i=0; i<len; i++) {
10734                         Pairing(i, nPlayers, &w, &b, &dummy);
10735                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10736                     }
10737                 }
10738                 WriteTourneyFile(appData.results, f);
10739                 fclose(f); // release lock
10740                 return;
10741             }
10742         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10743     }
10744     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10745     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10746     free(participants);
10747     return;
10748 }
10749
10750 int
10751 CheckPlayers (char *participants)
10752 {
10753         int i;
10754         char buf[MSG_SIZ], *p;
10755         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10756         while(p = strchr(participants, '\n')) {
10757             *p = NULLCHAR;
10758             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10759             if(!mnemonic[i]) {
10760                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10761                 *p = '\n';
10762                 DisplayError(buf, 0);
10763                 return 1;
10764             }
10765             *p = '\n';
10766             participants = p + 1;
10767         }
10768         return 0;
10769 }
10770
10771 int
10772 CreateTourney (char *name)
10773 {
10774         FILE *f;
10775         if(matchMode && strcmp(name, appData.tourneyFile)) {
10776              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10777         }
10778         if(name[0] == NULLCHAR) {
10779             if(appData.participants[0])
10780                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10781             return 0;
10782         }
10783         f = fopen(name, "r");
10784         if(f) { // file exists
10785             ASSIGN(appData.tourneyFile, name);
10786             ParseArgsFromFile(f); // parse it
10787         } else {
10788             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10789             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10790                 DisplayError(_("Not enough participants"), 0);
10791                 return 0;
10792             }
10793             if(CheckPlayers(appData.participants)) return 0;
10794             ASSIGN(appData.tourneyFile, name);
10795             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10796             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10797         }
10798         fclose(f);
10799         appData.noChessProgram = FALSE;
10800         appData.clockMode = TRUE;
10801         SetGNUMode();
10802         return 1;
10803 }
10804
10805 int
10806 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10807 {
10808     char buf[MSG_SIZ], *p, *q;
10809     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10810     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10811     skip = !all && group[0]; // if group requested, we start in skip mode
10812     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10813         p = names; q = buf; header = 0;
10814         while(*p && *p != '\n') *q++ = *p++;
10815         *q = 0;
10816         if(*p == '\n') p++;
10817         if(buf[0] == '#') {
10818             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10819             depth++; // we must be entering a new group
10820             if(all) continue; // suppress printing group headers when complete list requested
10821             header = 1;
10822             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10823         }
10824         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10825         if(engineList[i]) free(engineList[i]);
10826         engineList[i] = strdup(buf);
10827         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10828         if(engineMnemonic[i]) free(engineMnemonic[i]);
10829         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10830             strcat(buf, " (");
10831             sscanf(q + 8, "%s", buf + strlen(buf));
10832             strcat(buf, ")");
10833         }
10834         engineMnemonic[i] = strdup(buf);
10835         i++;
10836     }
10837     engineList[i] = engineMnemonic[i] = NULL;
10838     return i;
10839 }
10840
10841 // following implemented as macro to avoid type limitations
10842 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10843
10844 void
10845 SwapEngines (int n)
10846 {   // swap settings for first engine and other engine (so far only some selected options)
10847     int h;
10848     char *p;
10849     if(n == 0) return;
10850     SWAP(directory, p)
10851     SWAP(chessProgram, p)
10852     SWAP(isUCI, h)
10853     SWAP(hasOwnBookUCI, h)
10854     SWAP(protocolVersion, h)
10855     SWAP(reuse, h)
10856     SWAP(scoreIsAbsolute, h)
10857     SWAP(timeOdds, h)
10858     SWAP(logo, p)
10859     SWAP(pgnName, p)
10860     SWAP(pvSAN, h)
10861     SWAP(engOptions, p)
10862     SWAP(engInitString, p)
10863     SWAP(computerString, p)
10864     SWAP(features, p)
10865     SWAP(fenOverride, p)
10866     SWAP(NPS, h)
10867     SWAP(accumulateTC, h)
10868     SWAP(drawDepth, h)
10869     SWAP(host, p)
10870     SWAP(pseudo, h)
10871 }
10872
10873 int
10874 GetEngineLine (char *s, int n)
10875 {
10876     int i;
10877     char buf[MSG_SIZ];
10878     extern char *icsNames;
10879     if(!s || !*s) return 0;
10880     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10881     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10882     if(!mnemonic[i]) return 0;
10883     if(n == 11) return 1; // just testing if there was a match
10884     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10885     if(n == 1) SwapEngines(n);
10886     ParseArgsFromString(buf);
10887     if(n == 1) SwapEngines(n);
10888     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10889         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10890         ParseArgsFromString(buf);
10891     }
10892     return 1;
10893 }
10894
10895 int
10896 SetPlayer (int player, char *p)
10897 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10898     int i;
10899     char buf[MSG_SIZ], *engineName;
10900     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10901     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10902     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10903     if(mnemonic[i]) {
10904         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10905         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10906         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10907         ParseArgsFromString(buf);
10908     } else { // no engine with this nickname is installed!
10909         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10910         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10911         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10912         ModeHighlight();
10913         DisplayError(buf, 0);
10914         return 0;
10915     }
10916     free(engineName);
10917     return i;
10918 }
10919
10920 char *recentEngines;
10921
10922 void
10923 RecentEngineEvent (int nr)
10924 {
10925     int n;
10926 //    SwapEngines(1); // bump first to second
10927 //    ReplaceEngine(&second, 1); // and load it there
10928     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10929     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10930     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10931         ReplaceEngine(&first, 0);
10932         FloatToFront(&appData.recentEngineList, command[n]);
10933     }
10934 }
10935
10936 int
10937 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10938 {   // determine players from game number
10939     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10940
10941     if(appData.tourneyType == 0) {
10942         roundsPerCycle = (nPlayers - 1) | 1;
10943         pairingsPerRound = nPlayers / 2;
10944     } else if(appData.tourneyType > 0) {
10945         roundsPerCycle = nPlayers - appData.tourneyType;
10946         pairingsPerRound = appData.tourneyType;
10947     }
10948     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10949     gamesPerCycle = gamesPerRound * roundsPerCycle;
10950     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10951     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10952     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10953     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10954     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10955     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10956
10957     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10958     if(appData.roundSync) *syncInterval = gamesPerRound;
10959
10960     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10961
10962     if(appData.tourneyType == 0) {
10963         if(curPairing == (nPlayers-1)/2 ) {
10964             *whitePlayer = curRound;
10965             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10966         } else {
10967             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10968             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10969             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10970             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10971         }
10972     } else if(appData.tourneyType > 1) {
10973         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10974         *whitePlayer = curRound + appData.tourneyType;
10975     } else if(appData.tourneyType > 0) {
10976         *whitePlayer = curPairing;
10977         *blackPlayer = curRound + appData.tourneyType;
10978     }
10979
10980     // take care of white/black alternation per round.
10981     // For cycles and games this is already taken care of by default, derived from matchGame!
10982     return curRound & 1;
10983 }
10984
10985 int
10986 NextTourneyGame (int nr, int *swapColors)
10987 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10988     char *p, *q;
10989     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10990     FILE *tf;
10991     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10992     tf = fopen(appData.tourneyFile, "r");
10993     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10994     ParseArgsFromFile(tf); fclose(tf);
10995     InitTimeControls(); // TC might be altered from tourney file
10996
10997     nPlayers = CountPlayers(appData.participants); // count participants
10998     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10999     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11000
11001     if(syncInterval) {
11002         p = q = appData.results;
11003         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11004         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11005             DisplayMessage(_("Waiting for other game(s)"),"");
11006             waitingForGame = TRUE;
11007             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11008             return 0;
11009         }
11010         waitingForGame = FALSE;
11011     }
11012
11013     if(appData.tourneyType < 0) {
11014         if(nr>=0 && !pairingReceived) {
11015             char buf[1<<16];
11016             if(pairing.pr == NoProc) {
11017                 if(!appData.pairingEngine[0]) {
11018                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11019                     return 0;
11020                 }
11021                 StartChessProgram(&pairing); // starts the pairing engine
11022             }
11023             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11024             SendToProgram(buf, &pairing);
11025             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11026             SendToProgram(buf, &pairing);
11027             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11028         }
11029         pairingReceived = 0;                              // ... so we continue here
11030         *swapColors = 0;
11031         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11032         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11033         matchGame = 1; roundNr = nr / syncInterval + 1;
11034     }
11035
11036     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11037
11038     // redefine engines, engine dir, etc.
11039     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11040     if(first.pr == NoProc) {
11041       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11042       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11043     }
11044     if(second.pr == NoProc) {
11045       SwapEngines(1);
11046       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11047       SwapEngines(1);         // and make that valid for second engine by swapping
11048       InitEngine(&second, 1);
11049     }
11050     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11051     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11052     return OK;
11053 }
11054
11055 void
11056 NextMatchGame ()
11057 {   // performs game initialization that does not invoke engines, and then tries to start the game
11058     int res, firstWhite, swapColors = 0;
11059     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11060     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
11061         char buf[MSG_SIZ];
11062         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11063         if(strcmp(buf, currentDebugFile)) { // name has changed
11064             FILE *f = fopen(buf, "w");
11065             if(f) { // if opening the new file failed, just keep using the old one
11066                 ASSIGN(currentDebugFile, buf);
11067                 fclose(debugFP);
11068                 debugFP = f;
11069             }
11070             if(appData.serverFileName) {
11071                 if(serverFP) fclose(serverFP);
11072                 serverFP = fopen(appData.serverFileName, "w");
11073                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11074                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11075             }
11076         }
11077     }
11078     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11079     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11080     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11081     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11082     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11083     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11084     Reset(FALSE, first.pr != NoProc);
11085     res = LoadGameOrPosition(matchGame); // setup game
11086     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11087     if(!res) return; // abort when bad game/pos file
11088     TwoMachinesEvent();
11089 }
11090
11091 void
11092 UserAdjudicationEvent (int result)
11093 {
11094     ChessMove gameResult = GameIsDrawn;
11095
11096     if( result > 0 ) {
11097         gameResult = WhiteWins;
11098     }
11099     else if( result < 0 ) {
11100         gameResult = BlackWins;
11101     }
11102
11103     if( gameMode == TwoMachinesPlay ) {
11104         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11105     }
11106 }
11107
11108
11109 // [HGM] save: calculate checksum of game to make games easily identifiable
11110 int
11111 StringCheckSum (char *s)
11112 {
11113         int i = 0;
11114         if(s==NULL) return 0;
11115         while(*s) i = i*259 + *s++;
11116         return i;
11117 }
11118
11119 int
11120 GameCheckSum ()
11121 {
11122         int i, sum=0;
11123         for(i=backwardMostMove; i<forwardMostMove; i++) {
11124                 sum += pvInfoList[i].depth;
11125                 sum += StringCheckSum(parseList[i]);
11126                 sum += StringCheckSum(commentList[i]);
11127                 sum *= 261;
11128         }
11129         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11130         return sum + StringCheckSum(commentList[i]);
11131 } // end of save patch
11132
11133 void
11134 GameEnds (ChessMove result, char *resultDetails, int whosays)
11135 {
11136     GameMode nextGameMode;
11137     int isIcsGame;
11138     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11139
11140     if(endingGame) return; /* [HGM] crash: forbid recursion */
11141     endingGame = 1;
11142     if(twoBoards) { // [HGM] dual: switch back to one board
11143         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11144         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11145     }
11146     if (appData.debugMode) {
11147       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11148               result, resultDetails ? resultDetails : "(null)", whosays);
11149     }
11150
11151     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11152
11153     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11154
11155     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11156         /* If we are playing on ICS, the server decides when the
11157            game is over, but the engine can offer to draw, claim
11158            a draw, or resign.
11159          */
11160 #if ZIPPY
11161         if (appData.zippyPlay && first.initDone) {
11162             if (result == GameIsDrawn) {
11163                 /* In case draw still needs to be claimed */
11164                 SendToICS(ics_prefix);
11165                 SendToICS("draw\n");
11166             } else if (StrCaseStr(resultDetails, "resign")) {
11167                 SendToICS(ics_prefix);
11168                 SendToICS("resign\n");
11169             }
11170         }
11171 #endif
11172         endingGame = 0; /* [HGM] crash */
11173         return;
11174     }
11175
11176     /* If we're loading the game from a file, stop */
11177     if (whosays == GE_FILE) {
11178       (void) StopLoadGameTimer();
11179       gameFileFP = NULL;
11180     }
11181
11182     /* Cancel draw offers */
11183     first.offeredDraw = second.offeredDraw = 0;
11184
11185     /* If this is an ICS game, only ICS can really say it's done;
11186        if not, anyone can. */
11187     isIcsGame = (gameMode == IcsPlayingWhite ||
11188                  gameMode == IcsPlayingBlack ||
11189                  gameMode == IcsObserving    ||
11190                  gameMode == IcsExamining);
11191
11192     if (!isIcsGame || whosays == GE_ICS) {
11193         /* OK -- not an ICS game, or ICS said it was done */
11194         StopClocks();
11195         if (!isIcsGame && !appData.noChessProgram)
11196           SetUserThinkingEnables();
11197
11198         /* [HGM] if a machine claims the game end we verify this claim */
11199         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11200             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11201                 char claimer;
11202                 ChessMove trueResult = (ChessMove) -1;
11203
11204                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11205                                             first.twoMachinesColor[0] :
11206                                             second.twoMachinesColor[0] ;
11207
11208                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11209                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11210                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11211                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11212                 } else
11213                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11214                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11215                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11216                 } else
11217                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11218                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11219                 }
11220
11221                 // now verify win claims, but not in drop games, as we don't understand those yet
11222                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11223                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11224                     (result == WhiteWins && claimer == 'w' ||
11225                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11226                       if (appData.debugMode) {
11227                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11228                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11229                       }
11230                       if(result != trueResult) {
11231                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11232                               result = claimer == 'w' ? BlackWins : WhiteWins;
11233                               resultDetails = buf;
11234                       }
11235                 } else
11236                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11237                     && (forwardMostMove <= backwardMostMove ||
11238                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11239                         (claimer=='b')==(forwardMostMove&1))
11240                                                                                   ) {
11241                       /* [HGM] verify: draws that were not flagged are false claims */
11242                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11243                       result = claimer == 'w' ? BlackWins : WhiteWins;
11244                       resultDetails = buf;
11245                 }
11246                 /* (Claiming a loss is accepted no questions asked!) */
11247             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11248                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11249                 result = GameUnfinished;
11250                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11251             }
11252             /* [HGM] bare: don't allow bare King to win */
11253             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11254                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11255                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11256                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11257                && result != GameIsDrawn)
11258             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11259                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11260                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11261                         if(p >= 0 && p <= (int)WhiteKing) k++;
11262                 }
11263                 if (appData.debugMode) {
11264                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11265                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11266                 }
11267                 if(k <= 1) {
11268                         result = GameIsDrawn;
11269                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11270                         resultDetails = buf;
11271                 }
11272             }
11273         }
11274
11275
11276         if(serverMoves != NULL && !loadFlag) { char c = '=';
11277             if(result==WhiteWins) c = '+';
11278             if(result==BlackWins) c = '-';
11279             if(resultDetails != NULL)
11280                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11281         }
11282         if (resultDetails != NULL) {
11283             gameInfo.result = result;
11284             gameInfo.resultDetails = StrSave(resultDetails);
11285
11286             /* display last move only if game was not loaded from file */
11287             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11288                 DisplayMove(currentMove - 1);
11289
11290             if (forwardMostMove != 0) {
11291                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11292                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11293                                                                 ) {
11294                     if (*appData.saveGameFile != NULLCHAR) {
11295                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11296                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11297                         else
11298                         SaveGameToFile(appData.saveGameFile, TRUE);
11299                     } else if (appData.autoSaveGames) {
11300                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11301                     }
11302                     if (*appData.savePositionFile != NULLCHAR) {
11303                         SavePositionToFile(appData.savePositionFile);
11304                     }
11305                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11306                 }
11307             }
11308
11309             /* Tell program how game ended in case it is learning */
11310             /* [HGM] Moved this to after saving the PGN, just in case */
11311             /* engine died and we got here through time loss. In that */
11312             /* case we will get a fatal error writing the pipe, which */
11313             /* would otherwise lose us the PGN.                       */
11314             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11315             /* output during GameEnds should never be fatal anymore   */
11316             if (gameMode == MachinePlaysWhite ||
11317                 gameMode == MachinePlaysBlack ||
11318                 gameMode == TwoMachinesPlay ||
11319                 gameMode == IcsPlayingWhite ||
11320                 gameMode == IcsPlayingBlack ||
11321                 gameMode == BeginningOfGame) {
11322                 char buf[MSG_SIZ];
11323                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11324                         resultDetails);
11325                 if (first.pr != NoProc) {
11326                     SendToProgram(buf, &first);
11327                 }
11328                 if (second.pr != NoProc &&
11329                     gameMode == TwoMachinesPlay) {
11330                     SendToProgram(buf, &second);
11331                 }
11332             }
11333         }
11334
11335         if (appData.icsActive) {
11336             if (appData.quietPlay &&
11337                 (gameMode == IcsPlayingWhite ||
11338                  gameMode == IcsPlayingBlack)) {
11339                 SendToICS(ics_prefix);
11340                 SendToICS("set shout 1\n");
11341             }
11342             nextGameMode = IcsIdle;
11343             ics_user_moved = FALSE;
11344             /* clean up premove.  It's ugly when the game has ended and the
11345              * premove highlights are still on the board.
11346              */
11347             if (gotPremove) {
11348               gotPremove = FALSE;
11349               ClearPremoveHighlights();
11350               DrawPosition(FALSE, boards[currentMove]);
11351             }
11352             if (whosays == GE_ICS) {
11353                 switch (result) {
11354                 case WhiteWins:
11355                     if (gameMode == IcsPlayingWhite)
11356                         PlayIcsWinSound();
11357                     else if(gameMode == IcsPlayingBlack)
11358                         PlayIcsLossSound();
11359                     break;
11360                 case BlackWins:
11361                     if (gameMode == IcsPlayingBlack)
11362                         PlayIcsWinSound();
11363                     else if(gameMode == IcsPlayingWhite)
11364                         PlayIcsLossSound();
11365                     break;
11366                 case GameIsDrawn:
11367                     PlayIcsDrawSound();
11368                     break;
11369                 default:
11370                     PlayIcsUnfinishedSound();
11371                 }
11372             }
11373             if(appData.quitNext) { ExitEvent(0); return; }
11374         } else if (gameMode == EditGame ||
11375                    gameMode == PlayFromGameFile ||
11376                    gameMode == AnalyzeMode ||
11377                    gameMode == AnalyzeFile) {
11378             nextGameMode = gameMode;
11379         } else {
11380             nextGameMode = EndOfGame;
11381         }
11382         pausing = FALSE;
11383         ModeHighlight();
11384     } else {
11385         nextGameMode = gameMode;
11386     }
11387
11388     if (appData.noChessProgram) {
11389         gameMode = nextGameMode;
11390         ModeHighlight();
11391         endingGame = 0; /* [HGM] crash */
11392         return;
11393     }
11394
11395     if (first.reuse) {
11396         /* Put first chess program into idle state */
11397         if (first.pr != NoProc &&
11398             (gameMode == MachinePlaysWhite ||
11399              gameMode == MachinePlaysBlack ||
11400              gameMode == TwoMachinesPlay ||
11401              gameMode == IcsPlayingWhite ||
11402              gameMode == IcsPlayingBlack ||
11403              gameMode == BeginningOfGame)) {
11404             SendToProgram("force\n", &first);
11405             if (first.usePing) {
11406               char buf[MSG_SIZ];
11407               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11408               SendToProgram(buf, &first);
11409             }
11410         }
11411     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11412         /* Kill off first chess program */
11413         if (first.isr != NULL)
11414           RemoveInputSource(first.isr);
11415         first.isr = NULL;
11416
11417         if (first.pr != NoProc) {
11418             ExitAnalyzeMode();
11419             DoSleep( appData.delayBeforeQuit );
11420             SendToProgram("quit\n", &first);
11421             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11422             first.reload = TRUE;
11423         }
11424         first.pr = NoProc;
11425     }
11426     if (second.reuse) {
11427         /* Put second chess program into idle state */
11428         if (second.pr != NoProc &&
11429             gameMode == TwoMachinesPlay) {
11430             SendToProgram("force\n", &second);
11431             if (second.usePing) {
11432               char buf[MSG_SIZ];
11433               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11434               SendToProgram(buf, &second);
11435             }
11436         }
11437     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11438         /* Kill off second chess program */
11439         if (second.isr != NULL)
11440           RemoveInputSource(second.isr);
11441         second.isr = NULL;
11442
11443         if (second.pr != NoProc) {
11444             DoSleep( appData.delayBeforeQuit );
11445             SendToProgram("quit\n", &second);
11446             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11447             second.reload = TRUE;
11448         }
11449         second.pr = NoProc;
11450     }
11451
11452     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11453         char resChar = '=';
11454         switch (result) {
11455         case WhiteWins:
11456           resChar = '+';
11457           if (first.twoMachinesColor[0] == 'w') {
11458             first.matchWins++;
11459           } else {
11460             second.matchWins++;
11461           }
11462           break;
11463         case BlackWins:
11464           resChar = '-';
11465           if (first.twoMachinesColor[0] == 'b') {
11466             first.matchWins++;
11467           } else {
11468             second.matchWins++;
11469           }
11470           break;
11471         case GameUnfinished:
11472           resChar = ' ';
11473         default:
11474           break;
11475         }
11476
11477         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11478         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11479             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11480             ReserveGame(nextGame, resChar); // sets nextGame
11481             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11482             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11483         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11484
11485         if (nextGame <= appData.matchGames && !abortMatch) {
11486             gameMode = nextGameMode;
11487             matchGame = nextGame; // this will be overruled in tourney mode!
11488             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11489             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11490             endingGame = 0; /* [HGM] crash */
11491             return;
11492         } else {
11493             gameMode = nextGameMode;
11494             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11495                      first.tidy, second.tidy,
11496                      first.matchWins, second.matchWins,
11497                      appData.matchGames - (first.matchWins + second.matchWins));
11498             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11499             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11500             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11501             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11502                 first.twoMachinesColor = "black\n";
11503                 second.twoMachinesColor = "white\n";
11504             } else {
11505                 first.twoMachinesColor = "white\n";
11506                 second.twoMachinesColor = "black\n";
11507             }
11508         }
11509     }
11510     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11511         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11512       ExitAnalyzeMode();
11513     gameMode = nextGameMode;
11514     ModeHighlight();
11515     endingGame = 0;  /* [HGM] crash */
11516     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11517         if(matchMode == TRUE) { // match through command line: exit with or without popup
11518             if(ranking) {
11519                 ToNrEvent(forwardMostMove);
11520                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11521                 else ExitEvent(0);
11522             } else DisplayFatalError(buf, 0, 0);
11523         } else { // match through menu; just stop, with or without popup
11524             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11525             ModeHighlight();
11526             if(ranking){
11527                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11528             } else DisplayNote(buf);
11529       }
11530       if(ranking) free(ranking);
11531     }
11532 }
11533
11534 /* Assumes program was just initialized (initString sent).
11535    Leaves program in force mode. */
11536 void
11537 FeedMovesToProgram (ChessProgramState *cps, int upto)
11538 {
11539     int i;
11540
11541     if (appData.debugMode)
11542       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11543               startedFromSetupPosition ? "position and " : "",
11544               backwardMostMove, upto, cps->which);
11545     if(currentlyInitializedVariant != gameInfo.variant) {
11546       char buf[MSG_SIZ];
11547         // [HGM] variantswitch: make engine aware of new variant
11548         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11549                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11550                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11551         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11552         SendToProgram(buf, cps);
11553         currentlyInitializedVariant = gameInfo.variant;
11554     }
11555     SendToProgram("force\n", cps);
11556     if (startedFromSetupPosition) {
11557         SendBoard(cps, backwardMostMove);
11558     if (appData.debugMode) {
11559         fprintf(debugFP, "feedMoves\n");
11560     }
11561     }
11562     for (i = backwardMostMove; i < upto; i++) {
11563         SendMoveToProgram(i, cps);
11564     }
11565 }
11566
11567
11568 int
11569 ResurrectChessProgram ()
11570 {
11571      /* The chess program may have exited.
11572         If so, restart it and feed it all the moves made so far. */
11573     static int doInit = 0;
11574
11575     if (appData.noChessProgram) return 1;
11576
11577     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11578         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11579         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11580         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11581     } else {
11582         if (first.pr != NoProc) return 1;
11583         StartChessProgram(&first);
11584     }
11585     InitChessProgram(&first, FALSE);
11586     FeedMovesToProgram(&first, currentMove);
11587
11588     if (!first.sendTime) {
11589         /* can't tell gnuchess what its clock should read,
11590            so we bow to its notion. */
11591         ResetClocks();
11592         timeRemaining[0][currentMove] = whiteTimeRemaining;
11593         timeRemaining[1][currentMove] = blackTimeRemaining;
11594     }
11595
11596     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11597                 appData.icsEngineAnalyze) && first.analysisSupport) {
11598       SendToProgram("analyze\n", &first);
11599       first.analyzing = TRUE;
11600     }
11601     return 1;
11602 }
11603
11604 /*
11605  * Button procedures
11606  */
11607 void
11608 Reset (int redraw, int init)
11609 {
11610     int i;
11611
11612     if (appData.debugMode) {
11613         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11614                 redraw, init, gameMode);
11615     }
11616     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11617     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11618     CleanupTail(); // [HGM] vari: delete any stored variations
11619     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11620     pausing = pauseExamInvalid = FALSE;
11621     startedFromSetupPosition = blackPlaysFirst = FALSE;
11622     firstMove = TRUE;
11623     whiteFlag = blackFlag = FALSE;
11624     userOfferedDraw = FALSE;
11625     hintRequested = bookRequested = FALSE;
11626     first.maybeThinking = FALSE;
11627     second.maybeThinking = FALSE;
11628     first.bookSuspend = FALSE; // [HGM] book
11629     second.bookSuspend = FALSE;
11630     thinkOutput[0] = NULLCHAR;
11631     lastHint[0] = NULLCHAR;
11632     ClearGameInfo(&gameInfo);
11633     gameInfo.variant = StringToVariant(appData.variant);
11634     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11635     ics_user_moved = ics_clock_paused = FALSE;
11636     ics_getting_history = H_FALSE;
11637     ics_gamenum = -1;
11638     white_holding[0] = black_holding[0] = NULLCHAR;
11639     ClearProgramStats();
11640     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11641
11642     ResetFrontEnd();
11643     ClearHighlights();
11644     flipView = appData.flipView;
11645     ClearPremoveHighlights();
11646     gotPremove = FALSE;
11647     alarmSounded = FALSE;
11648     killX = killY = -1; // [HGM] lion
11649
11650     GameEnds(EndOfFile, NULL, GE_PLAYER);
11651     if(appData.serverMovesName != NULL) {
11652         /* [HGM] prepare to make moves file for broadcasting */
11653         clock_t t = clock();
11654         if(serverMoves != NULL) fclose(serverMoves);
11655         serverMoves = fopen(appData.serverMovesName, "r");
11656         if(serverMoves != NULL) {
11657             fclose(serverMoves);
11658             /* delay 15 sec before overwriting, so all clients can see end */
11659             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11660         }
11661         serverMoves = fopen(appData.serverMovesName, "w");
11662     }
11663
11664     ExitAnalyzeMode();
11665     gameMode = BeginningOfGame;
11666     ModeHighlight();
11667     if(appData.icsActive) gameInfo.variant = VariantNormal;
11668     currentMove = forwardMostMove = backwardMostMove = 0;
11669     MarkTargetSquares(1);
11670     InitPosition(redraw);
11671     for (i = 0; i < MAX_MOVES; i++) {
11672         if (commentList[i] != NULL) {
11673             free(commentList[i]);
11674             commentList[i] = NULL;
11675         }
11676     }
11677     ResetClocks();
11678     timeRemaining[0][0] = whiteTimeRemaining;
11679     timeRemaining[1][0] = blackTimeRemaining;
11680
11681     if (first.pr == NoProc) {
11682         StartChessProgram(&first);
11683     }
11684     if (init) {
11685             InitChessProgram(&first, startedFromSetupPosition);
11686     }
11687     DisplayTitle("");
11688     DisplayMessage("", "");
11689     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11690     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11691     ClearMap();        // [HGM] exclude: invalidate map
11692 }
11693
11694 void
11695 AutoPlayGameLoop ()
11696 {
11697     for (;;) {
11698         if (!AutoPlayOneMove())
11699           return;
11700         if (matchMode || appData.timeDelay == 0)
11701           continue;
11702         if (appData.timeDelay < 0)
11703           return;
11704         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11705         break;
11706     }
11707 }
11708
11709 void
11710 AnalyzeNextGame()
11711 {
11712     ReloadGame(1); // next game
11713 }
11714
11715 int
11716 AutoPlayOneMove ()
11717 {
11718     int fromX, fromY, toX, toY;
11719
11720     if (appData.debugMode) {
11721       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11722     }
11723
11724     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11725       return FALSE;
11726
11727     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11728       pvInfoList[currentMove].depth = programStats.depth;
11729       pvInfoList[currentMove].score = programStats.score;
11730       pvInfoList[currentMove].time  = 0;
11731       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11732       else { // append analysis of final position as comment
11733         char buf[MSG_SIZ];
11734         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11735         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11736       }
11737       programStats.depth = 0;
11738     }
11739
11740     if (currentMove >= forwardMostMove) {
11741       if(gameMode == AnalyzeFile) {
11742           if(appData.loadGameIndex == -1) {
11743             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11744           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11745           } else {
11746           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11747         }
11748       }
11749 //      gameMode = EndOfGame;
11750 //      ModeHighlight();
11751
11752       /* [AS] Clear current move marker at the end of a game */
11753       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11754
11755       return FALSE;
11756     }
11757
11758     toX = moveList[currentMove][2] - AAA;
11759     toY = moveList[currentMove][3] - ONE;
11760
11761     if (moveList[currentMove][1] == '@') {
11762         if (appData.highlightLastMove) {
11763             SetHighlights(-1, -1, toX, toY);
11764         }
11765     } else {
11766         int viaX = moveList[currentMove][5] - AAA;
11767         int viaY = moveList[currentMove][6] - ONE;
11768         fromX = moveList[currentMove][0] - AAA;
11769         fromY = moveList[currentMove][1] - ONE;
11770
11771         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11772
11773         if(moveList[currentMove][4] == ';') { // multi-leg
11774             ChessSquare piece = boards[currentMove][viaY][viaX];
11775             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11776             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11777             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11778             boards[currentMove][viaY][viaX] = piece;
11779         } else
11780         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11781
11782         if (appData.highlightLastMove) {
11783             SetHighlights(fromX, fromY, toX, toY);
11784         }
11785     }
11786     DisplayMove(currentMove);
11787     SendMoveToProgram(currentMove++, &first);
11788     DisplayBothClocks();
11789     DrawPosition(FALSE, boards[currentMove]);
11790     // [HGM] PV info: always display, routine tests if empty
11791     DisplayComment(currentMove - 1, commentList[currentMove]);
11792     return TRUE;
11793 }
11794
11795
11796 int
11797 LoadGameOneMove (ChessMove readAhead)
11798 {
11799     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11800     char promoChar = NULLCHAR;
11801     ChessMove moveType;
11802     char move[MSG_SIZ];
11803     char *p, *q;
11804
11805     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11806         gameMode != AnalyzeMode && gameMode != Training) {
11807         gameFileFP = NULL;
11808         return FALSE;
11809     }
11810
11811     yyboardindex = forwardMostMove;
11812     if (readAhead != EndOfFile) {
11813       moveType = readAhead;
11814     } else {
11815       if (gameFileFP == NULL)
11816           return FALSE;
11817       moveType = (ChessMove) Myylex();
11818     }
11819
11820     done = FALSE;
11821     switch (moveType) {
11822       case Comment:
11823         if (appData.debugMode)
11824           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11825         p = yy_text;
11826
11827         /* append the comment but don't display it */
11828         AppendComment(currentMove, p, FALSE);
11829         return TRUE;
11830
11831       case WhiteCapturesEnPassant:
11832       case BlackCapturesEnPassant:
11833       case WhitePromotion:
11834       case BlackPromotion:
11835       case WhiteNonPromotion:
11836       case BlackNonPromotion:
11837       case NormalMove:
11838       case FirstLeg:
11839       case WhiteKingSideCastle:
11840       case WhiteQueenSideCastle:
11841       case BlackKingSideCastle:
11842       case BlackQueenSideCastle:
11843       case WhiteKingSideCastleWild:
11844       case WhiteQueenSideCastleWild:
11845       case BlackKingSideCastleWild:
11846       case BlackQueenSideCastleWild:
11847       /* PUSH Fabien */
11848       case WhiteHSideCastleFR:
11849       case WhiteASideCastleFR:
11850       case BlackHSideCastleFR:
11851       case BlackASideCastleFR:
11852       /* POP Fabien */
11853         if (appData.debugMode)
11854           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11855         fromX = currentMoveString[0] - AAA;
11856         fromY = currentMoveString[1] - ONE;
11857         toX = currentMoveString[2] - AAA;
11858         toY = currentMoveString[3] - ONE;
11859         promoChar = currentMoveString[4];
11860         if(promoChar == ';') promoChar = NULLCHAR;
11861         break;
11862
11863       case WhiteDrop:
11864       case BlackDrop:
11865         if (appData.debugMode)
11866           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11867         fromX = moveType == WhiteDrop ?
11868           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11869         (int) CharToPiece(ToLower(currentMoveString[0]));
11870         fromY = DROP_RANK;
11871         toX = currentMoveString[2] - AAA;
11872         toY = currentMoveString[3] - ONE;
11873         break;
11874
11875       case WhiteWins:
11876       case BlackWins:
11877       case GameIsDrawn:
11878       case GameUnfinished:
11879         if (appData.debugMode)
11880           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11881         p = strchr(yy_text, '{');
11882         if (p == NULL) p = strchr(yy_text, '(');
11883         if (p == NULL) {
11884             p = yy_text;
11885             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11886         } else {
11887             q = strchr(p, *p == '{' ? '}' : ')');
11888             if (q != NULL) *q = NULLCHAR;
11889             p++;
11890         }
11891         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11892         GameEnds(moveType, p, GE_FILE);
11893         done = TRUE;
11894         if (cmailMsgLoaded) {
11895             ClearHighlights();
11896             flipView = WhiteOnMove(currentMove);
11897             if (moveType == GameUnfinished) flipView = !flipView;
11898             if (appData.debugMode)
11899               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11900         }
11901         break;
11902
11903       case EndOfFile:
11904         if (appData.debugMode)
11905           fprintf(debugFP, "Parser hit end of file\n");
11906         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11907           case MT_NONE:
11908           case MT_CHECK:
11909             break;
11910           case MT_CHECKMATE:
11911           case MT_STAINMATE:
11912             if (WhiteOnMove(currentMove)) {
11913                 GameEnds(BlackWins, "Black mates", GE_FILE);
11914             } else {
11915                 GameEnds(WhiteWins, "White mates", GE_FILE);
11916             }
11917             break;
11918           case MT_STALEMATE:
11919             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11920             break;
11921         }
11922         done = TRUE;
11923         break;
11924
11925       case MoveNumberOne:
11926         if (lastLoadGameStart == GNUChessGame) {
11927             /* GNUChessGames have numbers, but they aren't move numbers */
11928             if (appData.debugMode)
11929               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11930                       yy_text, (int) moveType);
11931             return LoadGameOneMove(EndOfFile); /* tail recursion */
11932         }
11933         /* else fall thru */
11934
11935       case XBoardGame:
11936       case GNUChessGame:
11937       case PGNTag:
11938         /* Reached start of next game in file */
11939         if (appData.debugMode)
11940           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11941         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11942           case MT_NONE:
11943           case MT_CHECK:
11944             break;
11945           case MT_CHECKMATE:
11946           case MT_STAINMATE:
11947             if (WhiteOnMove(currentMove)) {
11948                 GameEnds(BlackWins, "Black mates", GE_FILE);
11949             } else {
11950                 GameEnds(WhiteWins, "White mates", GE_FILE);
11951             }
11952             break;
11953           case MT_STALEMATE:
11954             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11955             break;
11956         }
11957         done = TRUE;
11958         break;
11959
11960       case PositionDiagram:     /* should not happen; ignore */
11961       case ElapsedTime:         /* ignore */
11962       case NAG:                 /* ignore */
11963         if (appData.debugMode)
11964           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11965                   yy_text, (int) moveType);
11966         return LoadGameOneMove(EndOfFile); /* tail recursion */
11967
11968       case IllegalMove:
11969         if (appData.testLegality) {
11970             if (appData.debugMode)
11971               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11972             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11973                     (forwardMostMove / 2) + 1,
11974                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11975             DisplayError(move, 0);
11976             done = TRUE;
11977         } else {
11978             if (appData.debugMode)
11979               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11980                       yy_text, currentMoveString);
11981             fromX = currentMoveString[0] - AAA;
11982             fromY = currentMoveString[1] - ONE;
11983             toX = currentMoveString[2] - AAA;
11984             toY = currentMoveString[3] - ONE;
11985             promoChar = currentMoveString[4];
11986         }
11987         break;
11988
11989       case AmbiguousMove:
11990         if (appData.debugMode)
11991           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11992         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11993                 (forwardMostMove / 2) + 1,
11994                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11995         DisplayError(move, 0);
11996         done = TRUE;
11997         break;
11998
11999       default:
12000       case ImpossibleMove:
12001         if (appData.debugMode)
12002           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12003         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12004                 (forwardMostMove / 2) + 1,
12005                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12006         DisplayError(move, 0);
12007         done = TRUE;
12008         break;
12009     }
12010
12011     if (done) {
12012         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12013             DrawPosition(FALSE, boards[currentMove]);
12014             DisplayBothClocks();
12015             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12016               DisplayComment(currentMove - 1, commentList[currentMove]);
12017         }
12018         (void) StopLoadGameTimer();
12019         gameFileFP = NULL;
12020         cmailOldMove = forwardMostMove;
12021         return FALSE;
12022     } else {
12023         /* currentMoveString is set as a side-effect of yylex */
12024
12025         thinkOutput[0] = NULLCHAR;
12026         MakeMove(fromX, fromY, toX, toY, promoChar);
12027         killX = killY = -1; // [HGM] lion: used up
12028         currentMove = forwardMostMove;
12029         return TRUE;
12030     }
12031 }
12032
12033 /* Load the nth game from the given file */
12034 int
12035 LoadGameFromFile (char *filename, int n, char *title, int useList)
12036 {
12037     FILE *f;
12038     char buf[MSG_SIZ];
12039
12040     if (strcmp(filename, "-") == 0) {
12041         f = stdin;
12042         title = "stdin";
12043     } else {
12044         f = fopen(filename, "rb");
12045         if (f == NULL) {
12046           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12047             DisplayError(buf, errno);
12048             return FALSE;
12049         }
12050     }
12051     if (fseek(f, 0, 0) == -1) {
12052         /* f is not seekable; probably a pipe */
12053         useList = FALSE;
12054     }
12055     if (useList && n == 0) {
12056         int error = GameListBuild(f);
12057         if (error) {
12058             DisplayError(_("Cannot build game list"), error);
12059         } else if (!ListEmpty(&gameList) &&
12060                    ((ListGame *) gameList.tailPred)->number > 1) {
12061             GameListPopUp(f, title);
12062             return TRUE;
12063         }
12064         GameListDestroy();
12065         n = 1;
12066     }
12067     if (n == 0) n = 1;
12068     return LoadGame(f, n, title, FALSE);
12069 }
12070
12071
12072 void
12073 MakeRegisteredMove ()
12074 {
12075     int fromX, fromY, toX, toY;
12076     char promoChar;
12077     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12078         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12079           case CMAIL_MOVE:
12080           case CMAIL_DRAW:
12081             if (appData.debugMode)
12082               fprintf(debugFP, "Restoring %s for game %d\n",
12083                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12084
12085             thinkOutput[0] = NULLCHAR;
12086             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12087             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12088             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12089             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12090             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12091             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12092             MakeMove(fromX, fromY, toX, toY, promoChar);
12093             ShowMove(fromX, fromY, toX, toY);
12094
12095             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12096               case MT_NONE:
12097               case MT_CHECK:
12098                 break;
12099
12100               case MT_CHECKMATE:
12101               case MT_STAINMATE:
12102                 if (WhiteOnMove(currentMove)) {
12103                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12104                 } else {
12105                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12106                 }
12107                 break;
12108
12109               case MT_STALEMATE:
12110                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12111                 break;
12112             }
12113
12114             break;
12115
12116           case CMAIL_RESIGN:
12117             if (WhiteOnMove(currentMove)) {
12118                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12119             } else {
12120                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12121             }
12122             break;
12123
12124           case CMAIL_ACCEPT:
12125             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12126             break;
12127
12128           default:
12129             break;
12130         }
12131     }
12132
12133     return;
12134 }
12135
12136 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12137 int
12138 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12139 {
12140     int retVal;
12141
12142     if (gameNumber > nCmailGames) {
12143         DisplayError(_("No more games in this message"), 0);
12144         return FALSE;
12145     }
12146     if (f == lastLoadGameFP) {
12147         int offset = gameNumber - lastLoadGameNumber;
12148         if (offset == 0) {
12149             cmailMsg[0] = NULLCHAR;
12150             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12151                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12152                 nCmailMovesRegistered--;
12153             }
12154             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12155             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12156                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12157             }
12158         } else {
12159             if (! RegisterMove()) return FALSE;
12160         }
12161     }
12162
12163     retVal = LoadGame(f, gameNumber, title, useList);
12164
12165     /* Make move registered during previous look at this game, if any */
12166     MakeRegisteredMove();
12167
12168     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12169         commentList[currentMove]
12170           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12171         DisplayComment(currentMove - 1, commentList[currentMove]);
12172     }
12173
12174     return retVal;
12175 }
12176
12177 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12178 int
12179 ReloadGame (int offset)
12180 {
12181     int gameNumber = lastLoadGameNumber + offset;
12182     if (lastLoadGameFP == NULL) {
12183         DisplayError(_("No game has been loaded yet"), 0);
12184         return FALSE;
12185     }
12186     if (gameNumber <= 0) {
12187         DisplayError(_("Can't back up any further"), 0);
12188         return FALSE;
12189     }
12190     if (cmailMsgLoaded) {
12191         return CmailLoadGame(lastLoadGameFP, gameNumber,
12192                              lastLoadGameTitle, lastLoadGameUseList);
12193     } else {
12194         return LoadGame(lastLoadGameFP, gameNumber,
12195                         lastLoadGameTitle, lastLoadGameUseList);
12196     }
12197 }
12198
12199 int keys[EmptySquare+1];
12200
12201 int
12202 PositionMatches (Board b1, Board b2)
12203 {
12204     int r, f, sum=0;
12205     switch(appData.searchMode) {
12206         case 1: return CompareWithRights(b1, b2);
12207         case 2:
12208             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12209                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12210             }
12211             return TRUE;
12212         case 3:
12213             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12214               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12215                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12216             }
12217             return sum==0;
12218         case 4:
12219             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12220                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12221             }
12222             return sum==0;
12223     }
12224     return TRUE;
12225 }
12226
12227 #define Q_PROMO  4
12228 #define Q_EP     3
12229 #define Q_BCASTL 2
12230 #define Q_WCASTL 1
12231
12232 int pieceList[256], quickBoard[256];
12233 ChessSquare pieceType[256] = { EmptySquare };
12234 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12235 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12236 int soughtTotal, turn;
12237 Boolean epOK, flipSearch;
12238
12239 typedef struct {
12240     unsigned char piece, to;
12241 } Move;
12242
12243 #define DSIZE (250000)
12244
12245 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12246 Move *moveDatabase = initialSpace;
12247 unsigned int movePtr, dataSize = DSIZE;
12248
12249 int
12250 MakePieceList (Board board, int *counts)
12251 {
12252     int r, f, n=Q_PROMO, total=0;
12253     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12254     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12255         int sq = f + (r<<4);
12256         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12257             quickBoard[sq] = ++n;
12258             pieceList[n] = sq;
12259             pieceType[n] = board[r][f];
12260             counts[board[r][f]]++;
12261             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12262             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12263             total++;
12264         }
12265     }
12266     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12267     return total;
12268 }
12269
12270 void
12271 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12272 {
12273     int sq = fromX + (fromY<<4);
12274     int piece = quickBoard[sq], rook;
12275     quickBoard[sq] = 0;
12276     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12277     if(piece == pieceList[1] && fromY == toY) {
12278       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12279         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12280         moveDatabase[movePtr++].piece = Q_WCASTL;
12281         quickBoard[sq] = piece;
12282         piece = quickBoard[from]; quickBoard[from] = 0;
12283         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12284       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12285         quickBoard[sq] = 0; // remove Rook
12286         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12287         moveDatabase[movePtr++].piece = Q_WCASTL;
12288         quickBoard[sq] = pieceList[1]; // put King
12289         piece = rook;
12290         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12291       }
12292     } else
12293     if(piece == pieceList[2] && fromY == toY) {
12294       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12295         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12296         moveDatabase[movePtr++].piece = Q_BCASTL;
12297         quickBoard[sq] = piece;
12298         piece = quickBoard[from]; quickBoard[from] = 0;
12299         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12300       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12301         quickBoard[sq] = 0; // remove Rook
12302         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12303         moveDatabase[movePtr++].piece = Q_BCASTL;
12304         quickBoard[sq] = pieceList[2]; // put King
12305         piece = rook;
12306         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12307       }
12308     } else
12309     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12310         quickBoard[(fromY<<4)+toX] = 0;
12311         moveDatabase[movePtr].piece = Q_EP;
12312         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12313         moveDatabase[movePtr].to = sq;
12314     } else
12315     if(promoPiece != pieceType[piece]) {
12316         moveDatabase[movePtr++].piece = Q_PROMO;
12317         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12318     }
12319     moveDatabase[movePtr].piece = piece;
12320     quickBoard[sq] = piece;
12321     movePtr++;
12322 }
12323
12324 int
12325 PackGame (Board board)
12326 {
12327     Move *newSpace = NULL;
12328     moveDatabase[movePtr].piece = 0; // terminate previous game
12329     if(movePtr > dataSize) {
12330         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12331         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12332         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12333         if(newSpace) {
12334             int i;
12335             Move *p = moveDatabase, *q = newSpace;
12336             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12337             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12338             moveDatabase = newSpace;
12339         } else { // calloc failed, we must be out of memory. Too bad...
12340             dataSize = 0; // prevent calloc events for all subsequent games
12341             return 0;     // and signal this one isn't cached
12342         }
12343     }
12344     movePtr++;
12345     MakePieceList(board, counts);
12346     return movePtr;
12347 }
12348
12349 int
12350 QuickCompare (Board board, int *minCounts, int *maxCounts)
12351 {   // compare according to search mode
12352     int r, f;
12353     switch(appData.searchMode)
12354     {
12355       case 1: // exact position match
12356         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12357         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12358             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12359         }
12360         break;
12361       case 2: // can have extra material on empty squares
12362         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12363             if(board[r][f] == EmptySquare) continue;
12364             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12365         }
12366         break;
12367       case 3: // material with exact Pawn structure
12368         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12369             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12370             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12371         } // fall through to material comparison
12372       case 4: // exact material
12373         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12374         break;
12375       case 6: // material range with given imbalance
12376         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12377         // fall through to range comparison
12378       case 5: // material range
12379         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12380     }
12381     return TRUE;
12382 }
12383
12384 int
12385 QuickScan (Board board, Move *move)
12386 {   // reconstruct game,and compare all positions in it
12387     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12388     do {
12389         int piece = move->piece;
12390         int to = move->to, from = pieceList[piece];
12391         if(found < 0) { // if already found just scan to game end for final piece count
12392           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12393            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12394            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12395                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12396             ) {
12397             static int lastCounts[EmptySquare+1];
12398             int i;
12399             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12400             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12401           } else stretch = 0;
12402           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12403           if(found >= 0 && !appData.minPieces) return found;
12404         }
12405         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12406           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12407           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12408             piece = (++move)->piece;
12409             from = pieceList[piece];
12410             counts[pieceType[piece]]--;
12411             pieceType[piece] = (ChessSquare) move->to;
12412             counts[move->to]++;
12413           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12414             counts[pieceType[quickBoard[to]]]--;
12415             quickBoard[to] = 0; total--;
12416             move++;
12417             continue;
12418           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12419             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12420             from  = pieceList[piece]; // so this must be King
12421             quickBoard[from] = 0;
12422             pieceList[piece] = to;
12423             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12424             quickBoard[from] = 0; // rook
12425             quickBoard[to] = piece;
12426             to = move->to; piece = move->piece;
12427             goto aftercastle;
12428           }
12429         }
12430         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12431         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12432         quickBoard[from] = 0;
12433       aftercastle:
12434         quickBoard[to] = piece;
12435         pieceList[piece] = to;
12436         cnt++; turn ^= 3;
12437         move++;
12438     } while(1);
12439 }
12440
12441 void
12442 InitSearch ()
12443 {
12444     int r, f;
12445     flipSearch = FALSE;
12446     CopyBoard(soughtBoard, boards[currentMove]);
12447     soughtTotal = MakePieceList(soughtBoard, maxSought);
12448     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12449     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12450     CopyBoard(reverseBoard, boards[currentMove]);
12451     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12452         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12453         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12454         reverseBoard[r][f] = piece;
12455     }
12456     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12457     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12458     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12459                  || (boards[currentMove][CASTLING][2] == NoRights ||
12460                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12461                  && (boards[currentMove][CASTLING][5] == NoRights ||
12462                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12463       ) {
12464         flipSearch = TRUE;
12465         CopyBoard(flipBoard, soughtBoard);
12466         CopyBoard(rotateBoard, reverseBoard);
12467         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12468             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12469             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12470         }
12471     }
12472     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12473     if(appData.searchMode >= 5) {
12474         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12475         MakePieceList(soughtBoard, minSought);
12476         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12477     }
12478     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12479         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12480 }
12481
12482 GameInfo dummyInfo;
12483 static int creatingBook;
12484
12485 int
12486 GameContainsPosition (FILE *f, ListGame *lg)
12487 {
12488     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12489     int fromX, fromY, toX, toY;
12490     char promoChar;
12491     static int initDone=FALSE;
12492
12493     // weed out games based on numerical tag comparison
12494     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12495     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12496     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12497     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12498     if(!initDone) {
12499         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12500         initDone = TRUE;
12501     }
12502     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12503     else CopyBoard(boards[scratch], initialPosition); // default start position
12504     if(lg->moves) {
12505         turn = btm + 1;
12506         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12507         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12508     }
12509     if(btm) plyNr++;
12510     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12511     fseek(f, lg->offset, 0);
12512     yynewfile(f);
12513     while(1) {
12514         yyboardindex = scratch;
12515         quickFlag = plyNr+1;
12516         next = Myylex();
12517         quickFlag = 0;
12518         switch(next) {
12519             case PGNTag:
12520                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12521             default:
12522                 continue;
12523
12524             case XBoardGame:
12525             case GNUChessGame:
12526                 if(plyNr) return -1; // after we have seen moves, this is for new game
12527               continue;
12528
12529             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12530             case ImpossibleMove:
12531             case WhiteWins: // game ends here with these four
12532             case BlackWins:
12533             case GameIsDrawn:
12534             case GameUnfinished:
12535                 return -1;
12536
12537             case IllegalMove:
12538                 if(appData.testLegality) return -1;
12539             case WhiteCapturesEnPassant:
12540             case BlackCapturesEnPassant:
12541             case WhitePromotion:
12542             case BlackPromotion:
12543             case WhiteNonPromotion:
12544             case BlackNonPromotion:
12545             case NormalMove:
12546             case FirstLeg:
12547             case WhiteKingSideCastle:
12548             case WhiteQueenSideCastle:
12549             case BlackKingSideCastle:
12550             case BlackQueenSideCastle:
12551             case WhiteKingSideCastleWild:
12552             case WhiteQueenSideCastleWild:
12553             case BlackKingSideCastleWild:
12554             case BlackQueenSideCastleWild:
12555             case WhiteHSideCastleFR:
12556             case WhiteASideCastleFR:
12557             case BlackHSideCastleFR:
12558             case BlackASideCastleFR:
12559                 fromX = currentMoveString[0] - AAA;
12560                 fromY = currentMoveString[1] - ONE;
12561                 toX = currentMoveString[2] - AAA;
12562                 toY = currentMoveString[3] - ONE;
12563                 promoChar = currentMoveString[4];
12564                 break;
12565             case WhiteDrop:
12566             case BlackDrop:
12567                 fromX = next == WhiteDrop ?
12568                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12569                   (int) CharToPiece(ToLower(currentMoveString[0]));
12570                 fromY = DROP_RANK;
12571                 toX = currentMoveString[2] - AAA;
12572                 toY = currentMoveString[3] - ONE;
12573                 promoChar = 0;
12574                 break;
12575         }
12576         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12577         plyNr++;
12578         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12579         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12580         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12581         if(appData.findMirror) {
12582             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12583             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12584         }
12585     }
12586 }
12587
12588 /* Load the nth game from open file f */
12589 int
12590 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12591 {
12592     ChessMove cm;
12593     char buf[MSG_SIZ];
12594     int gn = gameNumber;
12595     ListGame *lg = NULL;
12596     int numPGNTags = 0;
12597     int err, pos = -1;
12598     GameMode oldGameMode;
12599     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12600
12601     if (appData.debugMode)
12602         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12603
12604     if (gameMode == Training )
12605         SetTrainingModeOff();
12606
12607     oldGameMode = gameMode;
12608     if (gameMode != BeginningOfGame) {
12609       Reset(FALSE, TRUE);
12610     }
12611     killX = killY = -1; // [HGM] lion: in case we did not Reset
12612
12613     gameFileFP = f;
12614     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12615         fclose(lastLoadGameFP);
12616     }
12617
12618     if (useList) {
12619         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12620
12621         if (lg) {
12622             fseek(f, lg->offset, 0);
12623             GameListHighlight(gameNumber);
12624             pos = lg->position;
12625             gn = 1;
12626         }
12627         else {
12628             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12629               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12630             else
12631             DisplayError(_("Game number out of range"), 0);
12632             return FALSE;
12633         }
12634     } else {
12635         GameListDestroy();
12636         if (fseek(f, 0, 0) == -1) {
12637             if (f == lastLoadGameFP ?
12638                 gameNumber == lastLoadGameNumber + 1 :
12639                 gameNumber == 1) {
12640                 gn = 1;
12641             } else {
12642                 DisplayError(_("Can't seek on game file"), 0);
12643                 return FALSE;
12644             }
12645         }
12646     }
12647     lastLoadGameFP = f;
12648     lastLoadGameNumber = gameNumber;
12649     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12650     lastLoadGameUseList = useList;
12651
12652     yynewfile(f);
12653
12654     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12655       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12656                 lg->gameInfo.black);
12657             DisplayTitle(buf);
12658     } else if (*title != NULLCHAR) {
12659         if (gameNumber > 1) {
12660           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12661             DisplayTitle(buf);
12662         } else {
12663             DisplayTitle(title);
12664         }
12665     }
12666
12667     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12668         gameMode = PlayFromGameFile;
12669         ModeHighlight();
12670     }
12671
12672     currentMove = forwardMostMove = backwardMostMove = 0;
12673     CopyBoard(boards[0], initialPosition);
12674     StopClocks();
12675
12676     /*
12677      * Skip the first gn-1 games in the file.
12678      * Also skip over anything that precedes an identifiable
12679      * start of game marker, to avoid being confused by
12680      * garbage at the start of the file.  Currently
12681      * recognized start of game markers are the move number "1",
12682      * the pattern "gnuchess .* game", the pattern
12683      * "^[#;%] [^ ]* game file", and a PGN tag block.
12684      * A game that starts with one of the latter two patterns
12685      * will also have a move number 1, possibly
12686      * following a position diagram.
12687      * 5-4-02: Let's try being more lenient and allowing a game to
12688      * start with an unnumbered move.  Does that break anything?
12689      */
12690     cm = lastLoadGameStart = EndOfFile;
12691     while (gn > 0) {
12692         yyboardindex = forwardMostMove;
12693         cm = (ChessMove) Myylex();
12694         switch (cm) {
12695           case EndOfFile:
12696             if (cmailMsgLoaded) {
12697                 nCmailGames = CMAIL_MAX_GAMES - gn;
12698             } else {
12699                 Reset(TRUE, TRUE);
12700                 DisplayError(_("Game not found in file"), 0);
12701             }
12702             return FALSE;
12703
12704           case GNUChessGame:
12705           case XBoardGame:
12706             gn--;
12707             lastLoadGameStart = cm;
12708             break;
12709
12710           case MoveNumberOne:
12711             switch (lastLoadGameStart) {
12712               case GNUChessGame:
12713               case XBoardGame:
12714               case PGNTag:
12715                 break;
12716               case MoveNumberOne:
12717               case EndOfFile:
12718                 gn--;           /* count this game */
12719                 lastLoadGameStart = cm;
12720                 break;
12721               default:
12722                 /* impossible */
12723                 break;
12724             }
12725             break;
12726
12727           case PGNTag:
12728             switch (lastLoadGameStart) {
12729               case GNUChessGame:
12730               case PGNTag:
12731               case MoveNumberOne:
12732               case EndOfFile:
12733                 gn--;           /* count this game */
12734                 lastLoadGameStart = cm;
12735                 break;
12736               case XBoardGame:
12737                 lastLoadGameStart = cm; /* game counted already */
12738                 break;
12739               default:
12740                 /* impossible */
12741                 break;
12742             }
12743             if (gn > 0) {
12744                 do {
12745                     yyboardindex = forwardMostMove;
12746                     cm = (ChessMove) Myylex();
12747                 } while (cm == PGNTag || cm == Comment);
12748             }
12749             break;
12750
12751           case WhiteWins:
12752           case BlackWins:
12753           case GameIsDrawn:
12754             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12755                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12756                     != CMAIL_OLD_RESULT) {
12757                     nCmailResults ++ ;
12758                     cmailResult[  CMAIL_MAX_GAMES
12759                                 - gn - 1] = CMAIL_OLD_RESULT;
12760                 }
12761             }
12762             break;
12763
12764           case NormalMove:
12765           case FirstLeg:
12766             /* Only a NormalMove can be at the start of a game
12767              * without a position diagram. */
12768             if (lastLoadGameStart == EndOfFile ) {
12769               gn--;
12770               lastLoadGameStart = MoveNumberOne;
12771             }
12772             break;
12773
12774           default:
12775             break;
12776         }
12777     }
12778
12779     if (appData.debugMode)
12780       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12781
12782     if (cm == XBoardGame) {
12783         /* Skip any header junk before position diagram and/or move 1 */
12784         for (;;) {
12785             yyboardindex = forwardMostMove;
12786             cm = (ChessMove) Myylex();
12787
12788             if (cm == EndOfFile ||
12789                 cm == GNUChessGame || cm == XBoardGame) {
12790                 /* Empty game; pretend end-of-file and handle later */
12791                 cm = EndOfFile;
12792                 break;
12793             }
12794
12795             if (cm == MoveNumberOne || cm == PositionDiagram ||
12796                 cm == PGNTag || cm == Comment)
12797               break;
12798         }
12799     } else if (cm == GNUChessGame) {
12800         if (gameInfo.event != NULL) {
12801             free(gameInfo.event);
12802         }
12803         gameInfo.event = StrSave(yy_text);
12804     }
12805
12806     startedFromSetupPosition = FALSE;
12807     while (cm == PGNTag) {
12808         if (appData.debugMode)
12809           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12810         err = ParsePGNTag(yy_text, &gameInfo);
12811         if (!err) numPGNTags++;
12812
12813         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12814         if(gameInfo.variant != oldVariant) {
12815             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12816             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12817             InitPosition(TRUE);
12818             oldVariant = gameInfo.variant;
12819             if (appData.debugMode)
12820               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12821         }
12822
12823
12824         if (gameInfo.fen != NULL) {
12825           Board initial_position;
12826           startedFromSetupPosition = TRUE;
12827           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12828             Reset(TRUE, TRUE);
12829             DisplayError(_("Bad FEN position in file"), 0);
12830             return FALSE;
12831           }
12832           CopyBoard(boards[0], initial_position);
12833           if (blackPlaysFirst) {
12834             currentMove = forwardMostMove = backwardMostMove = 1;
12835             CopyBoard(boards[1], initial_position);
12836             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12837             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12838             timeRemaining[0][1] = whiteTimeRemaining;
12839             timeRemaining[1][1] = blackTimeRemaining;
12840             if (commentList[0] != NULL) {
12841               commentList[1] = commentList[0];
12842               commentList[0] = NULL;
12843             }
12844           } else {
12845             currentMove = forwardMostMove = backwardMostMove = 0;
12846           }
12847           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12848           {   int i;
12849               initialRulePlies = FENrulePlies;
12850               for( i=0; i< nrCastlingRights; i++ )
12851                   initialRights[i] = initial_position[CASTLING][i];
12852           }
12853           yyboardindex = forwardMostMove;
12854           free(gameInfo.fen);
12855           gameInfo.fen = NULL;
12856         }
12857
12858         yyboardindex = forwardMostMove;
12859         cm = (ChessMove) Myylex();
12860
12861         /* Handle comments interspersed among the tags */
12862         while (cm == Comment) {
12863             char *p;
12864             if (appData.debugMode)
12865               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12866             p = yy_text;
12867             AppendComment(currentMove, p, FALSE);
12868             yyboardindex = forwardMostMove;
12869             cm = (ChessMove) Myylex();
12870         }
12871     }
12872
12873     /* don't rely on existence of Event tag since if game was
12874      * pasted from clipboard the Event tag may not exist
12875      */
12876     if (numPGNTags > 0){
12877         char *tags;
12878         if (gameInfo.variant == VariantNormal) {
12879           VariantClass v = StringToVariant(gameInfo.event);
12880           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12881           if(v < VariantShogi) gameInfo.variant = v;
12882         }
12883         if (!matchMode) {
12884           if( appData.autoDisplayTags ) {
12885             tags = PGNTags(&gameInfo);
12886             TagsPopUp(tags, CmailMsg());
12887             free(tags);
12888           }
12889         }
12890     } else {
12891         /* Make something up, but don't display it now */
12892         SetGameInfo();
12893         TagsPopDown();
12894     }
12895
12896     if (cm == PositionDiagram) {
12897         int i, j;
12898         char *p;
12899         Board initial_position;
12900
12901         if (appData.debugMode)
12902           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12903
12904         if (!startedFromSetupPosition) {
12905             p = yy_text;
12906             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12907               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12908                 switch (*p) {
12909                   case '{':
12910                   case '[':
12911                   case '-':
12912                   case ' ':
12913                   case '\t':
12914                   case '\n':
12915                   case '\r':
12916                     break;
12917                   default:
12918                     initial_position[i][j++] = CharToPiece(*p);
12919                     break;
12920                 }
12921             while (*p == ' ' || *p == '\t' ||
12922                    *p == '\n' || *p == '\r') p++;
12923
12924             if (strncmp(p, "black", strlen("black"))==0)
12925               blackPlaysFirst = TRUE;
12926             else
12927               blackPlaysFirst = FALSE;
12928             startedFromSetupPosition = TRUE;
12929
12930             CopyBoard(boards[0], initial_position);
12931             if (blackPlaysFirst) {
12932                 currentMove = forwardMostMove = backwardMostMove = 1;
12933                 CopyBoard(boards[1], initial_position);
12934                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12935                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12936                 timeRemaining[0][1] = whiteTimeRemaining;
12937                 timeRemaining[1][1] = blackTimeRemaining;
12938                 if (commentList[0] != NULL) {
12939                     commentList[1] = commentList[0];
12940                     commentList[0] = NULL;
12941                 }
12942             } else {
12943                 currentMove = forwardMostMove = backwardMostMove = 0;
12944             }
12945         }
12946         yyboardindex = forwardMostMove;
12947         cm = (ChessMove) Myylex();
12948     }
12949
12950   if(!creatingBook) {
12951     if (first.pr == NoProc) {
12952         StartChessProgram(&first);
12953     }
12954     InitChessProgram(&first, FALSE);
12955     SendToProgram("force\n", &first);
12956     if (startedFromSetupPosition) {
12957         SendBoard(&first, forwardMostMove);
12958     if (appData.debugMode) {
12959         fprintf(debugFP, "Load Game\n");
12960     }
12961         DisplayBothClocks();
12962     }
12963   }
12964
12965     /* [HGM] server: flag to write setup moves in broadcast file as one */
12966     loadFlag = appData.suppressLoadMoves;
12967
12968     while (cm == Comment) {
12969         char *p;
12970         if (appData.debugMode)
12971           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12972         p = yy_text;
12973         AppendComment(currentMove, p, FALSE);
12974         yyboardindex = forwardMostMove;
12975         cm = (ChessMove) Myylex();
12976     }
12977
12978     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12979         cm == WhiteWins || cm == BlackWins ||
12980         cm == GameIsDrawn || cm == GameUnfinished) {
12981         DisplayMessage("", _("No moves in game"));
12982         if (cmailMsgLoaded) {
12983             if (appData.debugMode)
12984               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12985             ClearHighlights();
12986             flipView = FALSE;
12987         }
12988         DrawPosition(FALSE, boards[currentMove]);
12989         DisplayBothClocks();
12990         gameMode = EditGame;
12991         ModeHighlight();
12992         gameFileFP = NULL;
12993         cmailOldMove = 0;
12994         return TRUE;
12995     }
12996
12997     // [HGM] PV info: routine tests if comment empty
12998     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12999         DisplayComment(currentMove - 1, commentList[currentMove]);
13000     }
13001     if (!matchMode && appData.timeDelay != 0)
13002       DrawPosition(FALSE, boards[currentMove]);
13003
13004     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13005       programStats.ok_to_send = 1;
13006     }
13007
13008     /* if the first token after the PGN tags is a move
13009      * and not move number 1, retrieve it from the parser
13010      */
13011     if (cm != MoveNumberOne)
13012         LoadGameOneMove(cm);
13013
13014     /* load the remaining moves from the file */
13015     while (LoadGameOneMove(EndOfFile)) {
13016       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13017       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13018     }
13019
13020     /* rewind to the start of the game */
13021     currentMove = backwardMostMove;
13022
13023     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13024
13025     if (oldGameMode == AnalyzeFile) {
13026       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13027       AnalyzeFileEvent();
13028     } else
13029     if (oldGameMode == AnalyzeMode) {
13030       AnalyzeFileEvent();
13031     }
13032
13033     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13034         long int w, b; // [HGM] adjourn: restore saved clock times
13035         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13036         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13037             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13038             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13039         }
13040     }
13041
13042     if(creatingBook) return TRUE;
13043     if (!matchMode && pos > 0) {
13044         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13045     } else
13046     if (matchMode || appData.timeDelay == 0) {
13047       ToEndEvent();
13048     } else if (appData.timeDelay > 0) {
13049       AutoPlayGameLoop();
13050     }
13051
13052     if (appData.debugMode)
13053         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13054
13055     loadFlag = 0; /* [HGM] true game starts */
13056     return TRUE;
13057 }
13058
13059 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13060 int
13061 ReloadPosition (int offset)
13062 {
13063     int positionNumber = lastLoadPositionNumber + offset;
13064     if (lastLoadPositionFP == NULL) {
13065         DisplayError(_("No position has been loaded yet"), 0);
13066         return FALSE;
13067     }
13068     if (positionNumber <= 0) {
13069         DisplayError(_("Can't back up any further"), 0);
13070         return FALSE;
13071     }
13072     return LoadPosition(lastLoadPositionFP, positionNumber,
13073                         lastLoadPositionTitle);
13074 }
13075
13076 /* Load the nth position from the given file */
13077 int
13078 LoadPositionFromFile (char *filename, int n, char *title)
13079 {
13080     FILE *f;
13081     char buf[MSG_SIZ];
13082
13083     if (strcmp(filename, "-") == 0) {
13084         return LoadPosition(stdin, n, "stdin");
13085     } else {
13086         f = fopen(filename, "rb");
13087         if (f == NULL) {
13088             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13089             DisplayError(buf, errno);
13090             return FALSE;
13091         } else {
13092             return LoadPosition(f, n, title);
13093         }
13094     }
13095 }
13096
13097 /* Load the nth position from the given open file, and close it */
13098 int
13099 LoadPosition (FILE *f, int positionNumber, char *title)
13100 {
13101     char *p, line[MSG_SIZ];
13102     Board initial_position;
13103     int i, j, fenMode, pn;
13104
13105     if (gameMode == Training )
13106         SetTrainingModeOff();
13107
13108     if (gameMode != BeginningOfGame) {
13109         Reset(FALSE, TRUE);
13110     }
13111     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13112         fclose(lastLoadPositionFP);
13113     }
13114     if (positionNumber == 0) positionNumber = 1;
13115     lastLoadPositionFP = f;
13116     lastLoadPositionNumber = positionNumber;
13117     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13118     if (first.pr == NoProc && !appData.noChessProgram) {
13119       StartChessProgram(&first);
13120       InitChessProgram(&first, FALSE);
13121     }
13122     pn = positionNumber;
13123     if (positionNumber < 0) {
13124         /* Negative position number means to seek to that byte offset */
13125         if (fseek(f, -positionNumber, 0) == -1) {
13126             DisplayError(_("Can't seek on position file"), 0);
13127             return FALSE;
13128         };
13129         pn = 1;
13130     } else {
13131         if (fseek(f, 0, 0) == -1) {
13132             if (f == lastLoadPositionFP ?
13133                 positionNumber == lastLoadPositionNumber + 1 :
13134                 positionNumber == 1) {
13135                 pn = 1;
13136             } else {
13137                 DisplayError(_("Can't seek on position file"), 0);
13138                 return FALSE;
13139             }
13140         }
13141     }
13142     /* See if this file is FEN or old-style xboard */
13143     if (fgets(line, MSG_SIZ, f) == NULL) {
13144         DisplayError(_("Position not found in file"), 0);
13145         return FALSE;
13146     }
13147     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13148     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13149
13150     if (pn >= 2) {
13151         if (fenMode || line[0] == '#') pn--;
13152         while (pn > 0) {
13153             /* skip positions before number pn */
13154             if (fgets(line, MSG_SIZ, f) == NULL) {
13155                 Reset(TRUE, TRUE);
13156                 DisplayError(_("Position not found in file"), 0);
13157                 return FALSE;
13158             }
13159             if (fenMode || line[0] == '#') pn--;
13160         }
13161     }
13162
13163     if (fenMode) {
13164         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13165             DisplayError(_("Bad FEN position in file"), 0);
13166             return FALSE;
13167         }
13168     } else {
13169         (void) fgets(line, MSG_SIZ, f);
13170         (void) fgets(line, MSG_SIZ, f);
13171
13172         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13173             (void) fgets(line, MSG_SIZ, f);
13174             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13175                 if (*p == ' ')
13176                   continue;
13177                 initial_position[i][j++] = CharToPiece(*p);
13178             }
13179         }
13180
13181         blackPlaysFirst = FALSE;
13182         if (!feof(f)) {
13183             (void) fgets(line, MSG_SIZ, f);
13184             if (strncmp(line, "black", strlen("black"))==0)
13185               blackPlaysFirst = TRUE;
13186         }
13187     }
13188     startedFromSetupPosition = TRUE;
13189
13190     CopyBoard(boards[0], initial_position);
13191     if (blackPlaysFirst) {
13192         currentMove = forwardMostMove = backwardMostMove = 1;
13193         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13194         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13195         CopyBoard(boards[1], initial_position);
13196         DisplayMessage("", _("Black to play"));
13197     } else {
13198         currentMove = forwardMostMove = backwardMostMove = 0;
13199         DisplayMessage("", _("White to play"));
13200     }
13201     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13202     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13203         SendToProgram("force\n", &first);
13204         SendBoard(&first, forwardMostMove);
13205     }
13206     if (appData.debugMode) {
13207 int i, j;
13208   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13209   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13210         fprintf(debugFP, "Load Position\n");
13211     }
13212
13213     if (positionNumber > 1) {
13214       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13215         DisplayTitle(line);
13216     } else {
13217         DisplayTitle(title);
13218     }
13219     gameMode = EditGame;
13220     ModeHighlight();
13221     ResetClocks();
13222     timeRemaining[0][1] = whiteTimeRemaining;
13223     timeRemaining[1][1] = blackTimeRemaining;
13224     DrawPosition(FALSE, boards[currentMove]);
13225
13226     return TRUE;
13227 }
13228
13229
13230 void
13231 CopyPlayerNameIntoFileName (char **dest, char *src)
13232 {
13233     while (*src != NULLCHAR && *src != ',') {
13234         if (*src == ' ') {
13235             *(*dest)++ = '_';
13236             src++;
13237         } else {
13238             *(*dest)++ = *src++;
13239         }
13240     }
13241 }
13242
13243 char *
13244 DefaultFileName (char *ext)
13245 {
13246     static char def[MSG_SIZ];
13247     char *p;
13248
13249     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13250         p = def;
13251         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13252         *p++ = '-';
13253         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13254         *p++ = '.';
13255         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13256     } else {
13257         def[0] = NULLCHAR;
13258     }
13259     return def;
13260 }
13261
13262 /* Save the current game to the given file */
13263 int
13264 SaveGameToFile (char *filename, int append)
13265 {
13266     FILE *f;
13267     char buf[MSG_SIZ];
13268     int result, i, t,tot=0;
13269
13270     if (strcmp(filename, "-") == 0) {
13271         return SaveGame(stdout, 0, NULL);
13272     } else {
13273         for(i=0; i<10; i++) { // upto 10 tries
13274              f = fopen(filename, append ? "a" : "w");
13275              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13276              if(f || errno != 13) break;
13277              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13278              tot += t;
13279         }
13280         if (f == NULL) {
13281             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13282             DisplayError(buf, errno);
13283             return FALSE;
13284         } else {
13285             safeStrCpy(buf, lastMsg, MSG_SIZ);
13286             DisplayMessage(_("Waiting for access to save file"), "");
13287             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13288             DisplayMessage(_("Saving game"), "");
13289             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13290             result = SaveGame(f, 0, NULL);
13291             DisplayMessage(buf, "");
13292             return result;
13293         }
13294     }
13295 }
13296
13297 char *
13298 SavePart (char *str)
13299 {
13300     static char buf[MSG_SIZ];
13301     char *p;
13302
13303     p = strchr(str, ' ');
13304     if (p == NULL) return str;
13305     strncpy(buf, str, p - str);
13306     buf[p - str] = NULLCHAR;
13307     return buf;
13308 }
13309
13310 #define PGN_MAX_LINE 75
13311
13312 #define PGN_SIDE_WHITE  0
13313 #define PGN_SIDE_BLACK  1
13314
13315 static int
13316 FindFirstMoveOutOfBook (int side)
13317 {
13318     int result = -1;
13319
13320     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13321         int index = backwardMostMove;
13322         int has_book_hit = 0;
13323
13324         if( (index % 2) != side ) {
13325             index++;
13326         }
13327
13328         while( index < forwardMostMove ) {
13329             /* Check to see if engine is in book */
13330             int depth = pvInfoList[index].depth;
13331             int score = pvInfoList[index].score;
13332             int in_book = 0;
13333
13334             if( depth <= 2 ) {
13335                 in_book = 1;
13336             }
13337             else if( score == 0 && depth == 63 ) {
13338                 in_book = 1; /* Zappa */
13339             }
13340             else if( score == 2 && depth == 99 ) {
13341                 in_book = 1; /* Abrok */
13342             }
13343
13344             has_book_hit += in_book;
13345
13346             if( ! in_book ) {
13347                 result = index;
13348
13349                 break;
13350             }
13351
13352             index += 2;
13353         }
13354     }
13355
13356     return result;
13357 }
13358
13359 void
13360 GetOutOfBookInfo (char * buf)
13361 {
13362     int oob[2];
13363     int i;
13364     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13365
13366     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13367     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13368
13369     *buf = '\0';
13370
13371     if( oob[0] >= 0 || oob[1] >= 0 ) {
13372         for( i=0; i<2; i++ ) {
13373             int idx = oob[i];
13374
13375             if( idx >= 0 ) {
13376                 if( i > 0 && oob[0] >= 0 ) {
13377                     strcat( buf, "   " );
13378                 }
13379
13380                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13381                 sprintf( buf+strlen(buf), "%s%.2f",
13382                     pvInfoList[idx].score >= 0 ? "+" : "",
13383                     pvInfoList[idx].score / 100.0 );
13384             }
13385         }
13386     }
13387 }
13388
13389 /* Save game in PGN style */
13390 static void
13391 SaveGamePGN2 (FILE *f)
13392 {
13393     int i, offset, linelen, newblock;
13394 //    char *movetext;
13395     char numtext[32];
13396     int movelen, numlen, blank;
13397     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13398
13399     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13400
13401     PrintPGNTags(f, &gameInfo);
13402
13403     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13404
13405     if (backwardMostMove > 0 || startedFromSetupPosition) {
13406         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13407         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13408         fprintf(f, "\n{--------------\n");
13409         PrintPosition(f, backwardMostMove);
13410         fprintf(f, "--------------}\n");
13411         free(fen);
13412     }
13413     else {
13414         /* [AS] Out of book annotation */
13415         if( appData.saveOutOfBookInfo ) {
13416             char buf[64];
13417
13418             GetOutOfBookInfo( buf );
13419
13420             if( buf[0] != '\0' ) {
13421                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13422             }
13423         }
13424
13425         fprintf(f, "\n");
13426     }
13427
13428     i = backwardMostMove;
13429     linelen = 0;
13430     newblock = TRUE;
13431
13432     while (i < forwardMostMove) {
13433         /* Print comments preceding this move */
13434         if (commentList[i] != NULL) {
13435             if (linelen > 0) fprintf(f, "\n");
13436             fprintf(f, "%s", commentList[i]);
13437             linelen = 0;
13438             newblock = TRUE;
13439         }
13440
13441         /* Format move number */
13442         if ((i % 2) == 0)
13443           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13444         else
13445           if (newblock)
13446             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13447           else
13448             numtext[0] = NULLCHAR;
13449
13450         numlen = strlen(numtext);
13451         newblock = FALSE;
13452
13453         /* Print move number */
13454         blank = linelen > 0 && numlen > 0;
13455         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13456             fprintf(f, "\n");
13457             linelen = 0;
13458             blank = 0;
13459         }
13460         if (blank) {
13461             fprintf(f, " ");
13462             linelen++;
13463         }
13464         fprintf(f, "%s", numtext);
13465         linelen += numlen;
13466
13467         /* Get move */
13468         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13469         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13470
13471         /* Print move */
13472         blank = linelen > 0 && movelen > 0;
13473         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13474             fprintf(f, "\n");
13475             linelen = 0;
13476             blank = 0;
13477         }
13478         if (blank) {
13479             fprintf(f, " ");
13480             linelen++;
13481         }
13482         fprintf(f, "%s", move_buffer);
13483         linelen += movelen;
13484
13485         /* [AS] Add PV info if present */
13486         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13487             /* [HGM] add time */
13488             char buf[MSG_SIZ]; int seconds;
13489
13490             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13491
13492             if( seconds <= 0)
13493               buf[0] = 0;
13494             else
13495               if( seconds < 30 )
13496                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13497               else
13498                 {
13499                   seconds = (seconds + 4)/10; // round to full seconds
13500                   if( seconds < 60 )
13501                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13502                   else
13503                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13504                 }
13505
13506             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13507                       pvInfoList[i].score >= 0 ? "+" : "",
13508                       pvInfoList[i].score / 100.0,
13509                       pvInfoList[i].depth,
13510                       buf );
13511
13512             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13513
13514             /* Print score/depth */
13515             blank = linelen > 0 && movelen > 0;
13516             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13517                 fprintf(f, "\n");
13518                 linelen = 0;
13519                 blank = 0;
13520             }
13521             if (blank) {
13522                 fprintf(f, " ");
13523                 linelen++;
13524             }
13525             fprintf(f, "%s", move_buffer);
13526             linelen += movelen;
13527         }
13528
13529         i++;
13530     }
13531
13532     /* Start a new line */
13533     if (linelen > 0) fprintf(f, "\n");
13534
13535     /* Print comments after last move */
13536     if (commentList[i] != NULL) {
13537         fprintf(f, "%s\n", commentList[i]);
13538     }
13539
13540     /* Print result */
13541     if (gameInfo.resultDetails != NULL &&
13542         gameInfo.resultDetails[0] != NULLCHAR) {
13543         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13544         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13545            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13546             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13547         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13548     } else {
13549         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13550     }
13551 }
13552
13553 /* Save game in PGN style and close the file */
13554 int
13555 SaveGamePGN (FILE *f)
13556 {
13557     SaveGamePGN2(f);
13558     fclose(f);
13559     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13560     return TRUE;
13561 }
13562
13563 /* Save game in old style and close the file */
13564 int
13565 SaveGameOldStyle (FILE *f)
13566 {
13567     int i, offset;
13568     time_t tm;
13569
13570     tm = time((time_t *) NULL);
13571
13572     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13573     PrintOpponents(f);
13574
13575     if (backwardMostMove > 0 || startedFromSetupPosition) {
13576         fprintf(f, "\n[--------------\n");
13577         PrintPosition(f, backwardMostMove);
13578         fprintf(f, "--------------]\n");
13579     } else {
13580         fprintf(f, "\n");
13581     }
13582
13583     i = backwardMostMove;
13584     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13585
13586     while (i < forwardMostMove) {
13587         if (commentList[i] != NULL) {
13588             fprintf(f, "[%s]\n", commentList[i]);
13589         }
13590
13591         if ((i % 2) == 1) {
13592             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13593             i++;
13594         } else {
13595             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13596             i++;
13597             if (commentList[i] != NULL) {
13598                 fprintf(f, "\n");
13599                 continue;
13600             }
13601             if (i >= forwardMostMove) {
13602                 fprintf(f, "\n");
13603                 break;
13604             }
13605             fprintf(f, "%s\n", parseList[i]);
13606             i++;
13607         }
13608     }
13609
13610     if (commentList[i] != NULL) {
13611         fprintf(f, "[%s]\n", commentList[i]);
13612     }
13613
13614     /* This isn't really the old style, but it's close enough */
13615     if (gameInfo.resultDetails != NULL &&
13616         gameInfo.resultDetails[0] != NULLCHAR) {
13617         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13618                 gameInfo.resultDetails);
13619     } else {
13620         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13621     }
13622
13623     fclose(f);
13624     return TRUE;
13625 }
13626
13627 /* Save the current game to open file f and close the file */
13628 int
13629 SaveGame (FILE *f, int dummy, char *dummy2)
13630 {
13631     if (gameMode == EditPosition) EditPositionDone(TRUE);
13632     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13633     if (appData.oldSaveStyle)
13634       return SaveGameOldStyle(f);
13635     else
13636       return SaveGamePGN(f);
13637 }
13638
13639 /* Save the current position to the given file */
13640 int
13641 SavePositionToFile (char *filename)
13642 {
13643     FILE *f;
13644     char buf[MSG_SIZ];
13645
13646     if (strcmp(filename, "-") == 0) {
13647         return SavePosition(stdout, 0, NULL);
13648     } else {
13649         f = fopen(filename, "a");
13650         if (f == NULL) {
13651             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13652             DisplayError(buf, errno);
13653             return FALSE;
13654         } else {
13655             safeStrCpy(buf, lastMsg, MSG_SIZ);
13656             DisplayMessage(_("Waiting for access to save file"), "");
13657             flock(fileno(f), LOCK_EX); // [HGM] lock
13658             DisplayMessage(_("Saving position"), "");
13659             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13660             SavePosition(f, 0, NULL);
13661             DisplayMessage(buf, "");
13662             return TRUE;
13663         }
13664     }
13665 }
13666
13667 /* Save the current position to the given open file and close the file */
13668 int
13669 SavePosition (FILE *f, int dummy, char *dummy2)
13670 {
13671     time_t tm;
13672     char *fen;
13673
13674     if (gameMode == EditPosition) EditPositionDone(TRUE);
13675     if (appData.oldSaveStyle) {
13676         tm = time((time_t *) NULL);
13677
13678         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13679         PrintOpponents(f);
13680         fprintf(f, "[--------------\n");
13681         PrintPosition(f, currentMove);
13682         fprintf(f, "--------------]\n");
13683     } else {
13684         fen = PositionToFEN(currentMove, NULL, 1);
13685         fprintf(f, "%s\n", fen);
13686         free(fen);
13687     }
13688     fclose(f);
13689     return TRUE;
13690 }
13691
13692 void
13693 ReloadCmailMsgEvent (int unregister)
13694 {
13695 #if !WIN32
13696     static char *inFilename = NULL;
13697     static char *outFilename;
13698     int i;
13699     struct stat inbuf, outbuf;
13700     int status;
13701
13702     /* Any registered moves are unregistered if unregister is set, */
13703     /* i.e. invoked by the signal handler */
13704     if (unregister) {
13705         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13706             cmailMoveRegistered[i] = FALSE;
13707             if (cmailCommentList[i] != NULL) {
13708                 free(cmailCommentList[i]);
13709                 cmailCommentList[i] = NULL;
13710             }
13711         }
13712         nCmailMovesRegistered = 0;
13713     }
13714
13715     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13716         cmailResult[i] = CMAIL_NOT_RESULT;
13717     }
13718     nCmailResults = 0;
13719
13720     if (inFilename == NULL) {
13721         /* Because the filenames are static they only get malloced once  */
13722         /* and they never get freed                                      */
13723         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13724         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13725
13726         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13727         sprintf(outFilename, "%s.out", appData.cmailGameName);
13728     }
13729
13730     status = stat(outFilename, &outbuf);
13731     if (status < 0) {
13732         cmailMailedMove = FALSE;
13733     } else {
13734         status = stat(inFilename, &inbuf);
13735         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13736     }
13737
13738     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13739        counts the games, notes how each one terminated, etc.
13740
13741        It would be nice to remove this kludge and instead gather all
13742        the information while building the game list.  (And to keep it
13743        in the game list nodes instead of having a bunch of fixed-size
13744        parallel arrays.)  Note this will require getting each game's
13745        termination from the PGN tags, as the game list builder does
13746        not process the game moves.  --mann
13747        */
13748     cmailMsgLoaded = TRUE;
13749     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13750
13751     /* Load first game in the file or popup game menu */
13752     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13753
13754 #endif /* !WIN32 */
13755     return;
13756 }
13757
13758 int
13759 RegisterMove ()
13760 {
13761     FILE *f;
13762     char string[MSG_SIZ];
13763
13764     if (   cmailMailedMove
13765         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13766         return TRUE;            /* Allow free viewing  */
13767     }
13768
13769     /* Unregister move to ensure that we don't leave RegisterMove        */
13770     /* with the move registered when the conditions for registering no   */
13771     /* longer hold                                                       */
13772     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13773         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13774         nCmailMovesRegistered --;
13775
13776         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13777           {
13778               free(cmailCommentList[lastLoadGameNumber - 1]);
13779               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13780           }
13781     }
13782
13783     if (cmailOldMove == -1) {
13784         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13785         return FALSE;
13786     }
13787
13788     if (currentMove > cmailOldMove + 1) {
13789         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13790         return FALSE;
13791     }
13792
13793     if (currentMove < cmailOldMove) {
13794         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13795         return FALSE;
13796     }
13797
13798     if (forwardMostMove > currentMove) {
13799         /* Silently truncate extra moves */
13800         TruncateGame();
13801     }
13802
13803     if (   (currentMove == cmailOldMove + 1)
13804         || (   (currentMove == cmailOldMove)
13805             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13806                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13807         if (gameInfo.result != GameUnfinished) {
13808             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13809         }
13810
13811         if (commentList[currentMove] != NULL) {
13812             cmailCommentList[lastLoadGameNumber - 1]
13813               = StrSave(commentList[currentMove]);
13814         }
13815         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13816
13817         if (appData.debugMode)
13818           fprintf(debugFP, "Saving %s for game %d\n",
13819                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13820
13821         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13822
13823         f = fopen(string, "w");
13824         if (appData.oldSaveStyle) {
13825             SaveGameOldStyle(f); /* also closes the file */
13826
13827             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13828             f = fopen(string, "w");
13829             SavePosition(f, 0, NULL); /* also closes the file */
13830         } else {
13831             fprintf(f, "{--------------\n");
13832             PrintPosition(f, currentMove);
13833             fprintf(f, "--------------}\n\n");
13834
13835             SaveGame(f, 0, NULL); /* also closes the file*/
13836         }
13837
13838         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13839         nCmailMovesRegistered ++;
13840     } else if (nCmailGames == 1) {
13841         DisplayError(_("You have not made a move yet"), 0);
13842         return FALSE;
13843     }
13844
13845     return TRUE;
13846 }
13847
13848 void
13849 MailMoveEvent ()
13850 {
13851 #if !WIN32
13852     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13853     FILE *commandOutput;
13854     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13855     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13856     int nBuffers;
13857     int i;
13858     int archived;
13859     char *arcDir;
13860
13861     if (! cmailMsgLoaded) {
13862         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13863         return;
13864     }
13865
13866     if (nCmailGames == nCmailResults) {
13867         DisplayError(_("No unfinished games"), 0);
13868         return;
13869     }
13870
13871 #if CMAIL_PROHIBIT_REMAIL
13872     if (cmailMailedMove) {
13873       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);
13874         DisplayError(msg, 0);
13875         return;
13876     }
13877 #endif
13878
13879     if (! (cmailMailedMove || RegisterMove())) return;
13880
13881     if (   cmailMailedMove
13882         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13883       snprintf(string, MSG_SIZ, partCommandString,
13884                appData.debugMode ? " -v" : "", appData.cmailGameName);
13885         commandOutput = popen(string, "r");
13886
13887         if (commandOutput == NULL) {
13888             DisplayError(_("Failed to invoke cmail"), 0);
13889         } else {
13890             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13891                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13892             }
13893             if (nBuffers > 1) {
13894                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13895                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13896                 nBytes = MSG_SIZ - 1;
13897             } else {
13898                 (void) memcpy(msg, buffer, nBytes);
13899             }
13900             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13901
13902             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13903                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13904
13905                 archived = TRUE;
13906                 for (i = 0; i < nCmailGames; i ++) {
13907                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13908                         archived = FALSE;
13909                     }
13910                 }
13911                 if (   archived
13912                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13913                         != NULL)) {
13914                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13915                            arcDir,
13916                            appData.cmailGameName,
13917                            gameInfo.date);
13918                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13919                     cmailMsgLoaded = FALSE;
13920                 }
13921             }
13922
13923             DisplayInformation(msg);
13924             pclose(commandOutput);
13925         }
13926     } else {
13927         if ((*cmailMsg) != '\0') {
13928             DisplayInformation(cmailMsg);
13929         }
13930     }
13931
13932     return;
13933 #endif /* !WIN32 */
13934 }
13935
13936 char *
13937 CmailMsg ()
13938 {
13939 #if WIN32
13940     return NULL;
13941 #else
13942     int  prependComma = 0;
13943     char number[5];
13944     char string[MSG_SIZ];       /* Space for game-list */
13945     int  i;
13946
13947     if (!cmailMsgLoaded) return "";
13948
13949     if (cmailMailedMove) {
13950       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13951     } else {
13952         /* Create a list of games left */
13953       snprintf(string, MSG_SIZ, "[");
13954         for (i = 0; i < nCmailGames; i ++) {
13955             if (! (   cmailMoveRegistered[i]
13956                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13957                 if (prependComma) {
13958                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13959                 } else {
13960                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13961                     prependComma = 1;
13962                 }
13963
13964                 strcat(string, number);
13965             }
13966         }
13967         strcat(string, "]");
13968
13969         if (nCmailMovesRegistered + nCmailResults == 0) {
13970             switch (nCmailGames) {
13971               case 1:
13972                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13973                 break;
13974
13975               case 2:
13976                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13977                 break;
13978
13979               default:
13980                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13981                          nCmailGames);
13982                 break;
13983             }
13984         } else {
13985             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13986               case 1:
13987                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13988                          string);
13989                 break;
13990
13991               case 0:
13992                 if (nCmailResults == nCmailGames) {
13993                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13994                 } else {
13995                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13996                 }
13997                 break;
13998
13999               default:
14000                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14001                          string);
14002             }
14003         }
14004     }
14005     return cmailMsg;
14006 #endif /* WIN32 */
14007 }
14008
14009 void
14010 ResetGameEvent ()
14011 {
14012     if (gameMode == Training)
14013       SetTrainingModeOff();
14014
14015     Reset(TRUE, TRUE);
14016     cmailMsgLoaded = FALSE;
14017     if (appData.icsActive) {
14018       SendToICS(ics_prefix);
14019       SendToICS("refresh\n");
14020     }
14021 }
14022
14023 void
14024 ExitEvent (int status)
14025 {
14026     exiting++;
14027     if (exiting > 2) {
14028       /* Give up on clean exit */
14029       exit(status);
14030     }
14031     if (exiting > 1) {
14032       /* Keep trying for clean exit */
14033       return;
14034     }
14035
14036     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14037     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14038
14039     if (telnetISR != NULL) {
14040       RemoveInputSource(telnetISR);
14041     }
14042     if (icsPR != NoProc) {
14043       DestroyChildProcess(icsPR, TRUE);
14044     }
14045
14046     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14047     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14048
14049     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14050     /* make sure this other one finishes before killing it!                  */
14051     if(endingGame) { int count = 0;
14052         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14053         while(endingGame && count++ < 10) DoSleep(1);
14054         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14055     }
14056
14057     /* Kill off chess programs */
14058     if (first.pr != NoProc) {
14059         ExitAnalyzeMode();
14060
14061         DoSleep( appData.delayBeforeQuit );
14062         SendToProgram("quit\n", &first);
14063         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14064     }
14065     if (second.pr != NoProc) {
14066         DoSleep( appData.delayBeforeQuit );
14067         SendToProgram("quit\n", &second);
14068         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14069     }
14070     if (first.isr != NULL) {
14071         RemoveInputSource(first.isr);
14072     }
14073     if (second.isr != NULL) {
14074         RemoveInputSource(second.isr);
14075     }
14076
14077     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14078     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14079
14080     ShutDownFrontEnd();
14081     exit(status);
14082 }
14083
14084 void
14085 PauseEngine (ChessProgramState *cps)
14086 {
14087     SendToProgram("pause\n", cps);
14088     cps->pause = 2;
14089 }
14090
14091 void
14092 UnPauseEngine (ChessProgramState *cps)
14093 {
14094     SendToProgram("resume\n", cps);
14095     cps->pause = 1;
14096 }
14097
14098 void
14099 PauseEvent ()
14100 {
14101     if (appData.debugMode)
14102         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14103     if (pausing) {
14104         pausing = FALSE;
14105         ModeHighlight();
14106         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14107             StartClocks();
14108             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14109                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14110                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14111             }
14112             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14113             HandleMachineMove(stashedInputMove, stalledEngine);
14114             stalledEngine = NULL;
14115             return;
14116         }
14117         if (gameMode == MachinePlaysWhite ||
14118             gameMode == TwoMachinesPlay   ||
14119             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14120             if(first.pause)  UnPauseEngine(&first);
14121             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14122             if(second.pause) UnPauseEngine(&second);
14123             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14124             StartClocks();
14125         } else {
14126             DisplayBothClocks();
14127         }
14128         if (gameMode == PlayFromGameFile) {
14129             if (appData.timeDelay >= 0)
14130                 AutoPlayGameLoop();
14131         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14132             Reset(FALSE, TRUE);
14133             SendToICS(ics_prefix);
14134             SendToICS("refresh\n");
14135         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14136             ForwardInner(forwardMostMove);
14137         }
14138         pauseExamInvalid = FALSE;
14139     } else {
14140         switch (gameMode) {
14141           default:
14142             return;
14143           case IcsExamining:
14144             pauseExamForwardMostMove = forwardMostMove;
14145             pauseExamInvalid = FALSE;
14146             /* fall through */
14147           case IcsObserving:
14148           case IcsPlayingWhite:
14149           case IcsPlayingBlack:
14150             pausing = TRUE;
14151             ModeHighlight();
14152             return;
14153           case PlayFromGameFile:
14154             (void) StopLoadGameTimer();
14155             pausing = TRUE;
14156             ModeHighlight();
14157             break;
14158           case BeginningOfGame:
14159             if (appData.icsActive) return;
14160             /* else fall through */
14161           case MachinePlaysWhite:
14162           case MachinePlaysBlack:
14163           case TwoMachinesPlay:
14164             if (forwardMostMove == 0)
14165               return;           /* don't pause if no one has moved */
14166             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14167                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14168                 if(onMove->pause) {           // thinking engine can be paused
14169                     PauseEngine(onMove);      // do it
14170                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14171                         PauseEngine(onMove->other);
14172                     else
14173                         SendToProgram("easy\n", onMove->other);
14174                     StopClocks();
14175                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14176             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14177                 if(first.pause) {
14178                     PauseEngine(&first);
14179                     StopClocks();
14180                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14181             } else { // human on move, pause pondering by either method
14182                 if(first.pause)
14183                     PauseEngine(&first);
14184                 else if(appData.ponderNextMove)
14185                     SendToProgram("easy\n", &first);
14186                 StopClocks();
14187             }
14188             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14189           case AnalyzeMode:
14190             pausing = TRUE;
14191             ModeHighlight();
14192             break;
14193         }
14194     }
14195 }
14196
14197 void
14198 EditCommentEvent ()
14199 {
14200     char title[MSG_SIZ];
14201
14202     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14203       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14204     } else {
14205       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14206                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14207                parseList[currentMove - 1]);
14208     }
14209
14210     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14211 }
14212
14213
14214 void
14215 EditTagsEvent ()
14216 {
14217     char *tags = PGNTags(&gameInfo);
14218     bookUp = FALSE;
14219     EditTagsPopUp(tags, NULL);
14220     free(tags);
14221 }
14222
14223 void
14224 ToggleSecond ()
14225 {
14226   if(second.analyzing) {
14227     SendToProgram("exit\n", &second);
14228     second.analyzing = FALSE;
14229   } else {
14230     if (second.pr == NoProc) StartChessProgram(&second);
14231     InitChessProgram(&second, FALSE);
14232     FeedMovesToProgram(&second, currentMove);
14233
14234     SendToProgram("analyze\n", &second);
14235     second.analyzing = TRUE;
14236   }
14237 }
14238
14239 /* Toggle ShowThinking */
14240 void
14241 ToggleShowThinking()
14242 {
14243   appData.showThinking = !appData.showThinking;
14244   ShowThinkingEvent();
14245 }
14246
14247 int
14248 AnalyzeModeEvent ()
14249 {
14250     char buf[MSG_SIZ];
14251
14252     if (!first.analysisSupport) {
14253       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14254       DisplayError(buf, 0);
14255       return 0;
14256     }
14257     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14258     if (appData.icsActive) {
14259         if (gameMode != IcsObserving) {
14260           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14261             DisplayError(buf, 0);
14262             /* secure check */
14263             if (appData.icsEngineAnalyze) {
14264                 if (appData.debugMode)
14265                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14266                 ExitAnalyzeMode();
14267                 ModeHighlight();
14268             }
14269             return 0;
14270         }
14271         /* if enable, user wants to disable icsEngineAnalyze */
14272         if (appData.icsEngineAnalyze) {
14273                 ExitAnalyzeMode();
14274                 ModeHighlight();
14275                 return 0;
14276         }
14277         appData.icsEngineAnalyze = TRUE;
14278         if (appData.debugMode)
14279             fprintf(debugFP, "ICS engine analyze starting... \n");
14280     }
14281
14282     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14283     if (appData.noChessProgram || gameMode == AnalyzeMode)
14284       return 0;
14285
14286     if (gameMode != AnalyzeFile) {
14287         if (!appData.icsEngineAnalyze) {
14288                EditGameEvent();
14289                if (gameMode != EditGame) return 0;
14290         }
14291         if (!appData.showThinking) ToggleShowThinking();
14292         ResurrectChessProgram();
14293         SendToProgram("analyze\n", &first);
14294         first.analyzing = TRUE;
14295         /*first.maybeThinking = TRUE;*/
14296         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14297         EngineOutputPopUp();
14298     }
14299     if (!appData.icsEngineAnalyze) {
14300         gameMode = AnalyzeMode;
14301         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14302     }
14303     pausing = FALSE;
14304     ModeHighlight();
14305     SetGameInfo();
14306
14307     StartAnalysisClock();
14308     GetTimeMark(&lastNodeCountTime);
14309     lastNodeCount = 0;
14310     return 1;
14311 }
14312
14313 void
14314 AnalyzeFileEvent ()
14315 {
14316     if (appData.noChessProgram || gameMode == AnalyzeFile)
14317       return;
14318
14319     if (!first.analysisSupport) {
14320       char buf[MSG_SIZ];
14321       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14322       DisplayError(buf, 0);
14323       return;
14324     }
14325
14326     if (gameMode != AnalyzeMode) {
14327         keepInfo = 1; // mere annotating should not alter PGN tags
14328         EditGameEvent();
14329         keepInfo = 0;
14330         if (gameMode != EditGame) return;
14331         if (!appData.showThinking) ToggleShowThinking();
14332         ResurrectChessProgram();
14333         SendToProgram("analyze\n", &first);
14334         first.analyzing = TRUE;
14335         /*first.maybeThinking = TRUE;*/
14336         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14337         EngineOutputPopUp();
14338     }
14339     gameMode = AnalyzeFile;
14340     pausing = FALSE;
14341     ModeHighlight();
14342
14343     StartAnalysisClock();
14344     GetTimeMark(&lastNodeCountTime);
14345     lastNodeCount = 0;
14346     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14347     AnalysisPeriodicEvent(1);
14348 }
14349
14350 void
14351 MachineWhiteEvent ()
14352 {
14353     char buf[MSG_SIZ];
14354     char *bookHit = NULL;
14355
14356     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14357       return;
14358
14359
14360     if (gameMode == PlayFromGameFile ||
14361         gameMode == TwoMachinesPlay  ||
14362         gameMode == Training         ||
14363         gameMode == AnalyzeMode      ||
14364         gameMode == EndOfGame)
14365         EditGameEvent();
14366
14367     if (gameMode == EditPosition)
14368         EditPositionDone(TRUE);
14369
14370     if (!WhiteOnMove(currentMove)) {
14371         DisplayError(_("It is not White's turn"), 0);
14372         return;
14373     }
14374
14375     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14376       ExitAnalyzeMode();
14377
14378     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14379         gameMode == AnalyzeFile)
14380         TruncateGame();
14381
14382     ResurrectChessProgram();    /* in case it isn't running */
14383     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14384         gameMode = MachinePlaysWhite;
14385         ResetClocks();
14386     } else
14387     gameMode = MachinePlaysWhite;
14388     pausing = FALSE;
14389     ModeHighlight();
14390     SetGameInfo();
14391     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14392     DisplayTitle(buf);
14393     if (first.sendName) {
14394       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14395       SendToProgram(buf, &first);
14396     }
14397     if (first.sendTime) {
14398       if (first.useColors) {
14399         SendToProgram("black\n", &first); /*gnu kludge*/
14400       }
14401       SendTimeRemaining(&first, TRUE);
14402     }
14403     if (first.useColors) {
14404       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14405     }
14406     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14407     SetMachineThinkingEnables();
14408     first.maybeThinking = TRUE;
14409     StartClocks();
14410     firstMove = FALSE;
14411
14412     if (appData.autoFlipView && !flipView) {
14413       flipView = !flipView;
14414       DrawPosition(FALSE, NULL);
14415       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14416     }
14417
14418     if(bookHit) { // [HGM] book: simulate book reply
14419         static char bookMove[MSG_SIZ]; // a bit generous?
14420
14421         programStats.nodes = programStats.depth = programStats.time =
14422         programStats.score = programStats.got_only_move = 0;
14423         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14424
14425         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14426         strcat(bookMove, bookHit);
14427         HandleMachineMove(bookMove, &first);
14428     }
14429 }
14430
14431 void
14432 MachineBlackEvent ()
14433 {
14434   char buf[MSG_SIZ];
14435   char *bookHit = NULL;
14436
14437     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14438         return;
14439
14440
14441     if (gameMode == PlayFromGameFile ||
14442         gameMode == TwoMachinesPlay  ||
14443         gameMode == Training         ||
14444         gameMode == AnalyzeMode      ||
14445         gameMode == EndOfGame)
14446         EditGameEvent();
14447
14448     if (gameMode == EditPosition)
14449         EditPositionDone(TRUE);
14450
14451     if (WhiteOnMove(currentMove)) {
14452         DisplayError(_("It is not Black's turn"), 0);
14453         return;
14454     }
14455
14456     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14457       ExitAnalyzeMode();
14458
14459     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14460         gameMode == AnalyzeFile)
14461         TruncateGame();
14462
14463     ResurrectChessProgram();    /* in case it isn't running */
14464     gameMode = MachinePlaysBlack;
14465     pausing = FALSE;
14466     ModeHighlight();
14467     SetGameInfo();
14468     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14469     DisplayTitle(buf);
14470     if (first.sendName) {
14471       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14472       SendToProgram(buf, &first);
14473     }
14474     if (first.sendTime) {
14475       if (first.useColors) {
14476         SendToProgram("white\n", &first); /*gnu kludge*/
14477       }
14478       SendTimeRemaining(&first, FALSE);
14479     }
14480     if (first.useColors) {
14481       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14482     }
14483     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14484     SetMachineThinkingEnables();
14485     first.maybeThinking = TRUE;
14486     StartClocks();
14487
14488     if (appData.autoFlipView && flipView) {
14489       flipView = !flipView;
14490       DrawPosition(FALSE, NULL);
14491       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14492     }
14493     if(bookHit) { // [HGM] book: simulate book reply
14494         static char bookMove[MSG_SIZ]; // a bit generous?
14495
14496         programStats.nodes = programStats.depth = programStats.time =
14497         programStats.score = programStats.got_only_move = 0;
14498         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14499
14500         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14501         strcat(bookMove, bookHit);
14502         HandleMachineMove(bookMove, &first);
14503     }
14504 }
14505
14506
14507 void
14508 DisplayTwoMachinesTitle ()
14509 {
14510     char buf[MSG_SIZ];
14511     if (appData.matchGames > 0) {
14512         if(appData.tourneyFile[0]) {
14513           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14514                    gameInfo.white, _("vs."), gameInfo.black,
14515                    nextGame+1, appData.matchGames+1,
14516                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14517         } else
14518         if (first.twoMachinesColor[0] == 'w') {
14519           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14520                    gameInfo.white, _("vs."),  gameInfo.black,
14521                    first.matchWins, second.matchWins,
14522                    matchGame - 1 - (first.matchWins + second.matchWins));
14523         } else {
14524           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14525                    gameInfo.white, _("vs."), gameInfo.black,
14526                    second.matchWins, first.matchWins,
14527                    matchGame - 1 - (first.matchWins + second.matchWins));
14528         }
14529     } else {
14530       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14531     }
14532     DisplayTitle(buf);
14533 }
14534
14535 void
14536 SettingsMenuIfReady ()
14537 {
14538   if (second.lastPing != second.lastPong) {
14539     DisplayMessage("", _("Waiting for second chess program"));
14540     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14541     return;
14542   }
14543   ThawUI();
14544   DisplayMessage("", "");
14545   SettingsPopUp(&second);
14546 }
14547
14548 int
14549 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14550 {
14551     char buf[MSG_SIZ];
14552     if (cps->pr == NoProc) {
14553         StartChessProgram(cps);
14554         if (cps->protocolVersion == 1) {
14555           retry();
14556           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14557         } else {
14558           /* kludge: allow timeout for initial "feature" command */
14559           if(retry != TwoMachinesEventIfReady) FreezeUI();
14560           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14561           DisplayMessage("", buf);
14562           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14563         }
14564         return 1;
14565     }
14566     return 0;
14567 }
14568
14569 void
14570 TwoMachinesEvent P((void))
14571 {
14572     int i;
14573     char buf[MSG_SIZ];
14574     ChessProgramState *onmove;
14575     char *bookHit = NULL;
14576     static int stalling = 0;
14577     TimeMark now;
14578     long wait;
14579
14580     if (appData.noChessProgram) return;
14581
14582     switch (gameMode) {
14583       case TwoMachinesPlay:
14584         return;
14585       case MachinePlaysWhite:
14586       case MachinePlaysBlack:
14587         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14588             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14589             return;
14590         }
14591         /* fall through */
14592       case BeginningOfGame:
14593       case PlayFromGameFile:
14594       case EndOfGame:
14595         EditGameEvent();
14596         if (gameMode != EditGame) return;
14597         break;
14598       case EditPosition:
14599         EditPositionDone(TRUE);
14600         break;
14601       case AnalyzeMode:
14602       case AnalyzeFile:
14603         ExitAnalyzeMode();
14604         break;
14605       case EditGame:
14606       default:
14607         break;
14608     }
14609
14610 //    forwardMostMove = currentMove;
14611     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14612     startingEngine = TRUE;
14613
14614     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14615
14616     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14617     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14618       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14619       return;
14620     }
14621     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14622
14623     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14624                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14625         startingEngine = FALSE;
14626         DisplayError("second engine does not play this", 0);
14627         return;
14628     }
14629
14630     if(!stalling) {
14631       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14632       SendToProgram("force\n", &second);
14633       stalling = 1;
14634       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14635       return;
14636     }
14637     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14638     if(appData.matchPause>10000 || appData.matchPause<10)
14639                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14640     wait = SubtractTimeMarks(&now, &pauseStart);
14641     if(wait < appData.matchPause) {
14642         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14643         return;
14644     }
14645     // we are now committed to starting the game
14646     stalling = 0;
14647     DisplayMessage("", "");
14648     if (startedFromSetupPosition) {
14649         SendBoard(&second, backwardMostMove);
14650     if (appData.debugMode) {
14651         fprintf(debugFP, "Two Machines\n");
14652     }
14653     }
14654     for (i = backwardMostMove; i < forwardMostMove; i++) {
14655         SendMoveToProgram(i, &second);
14656     }
14657
14658     gameMode = TwoMachinesPlay;
14659     pausing = startingEngine = FALSE;
14660     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14661     SetGameInfo();
14662     DisplayTwoMachinesTitle();
14663     firstMove = TRUE;
14664     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14665         onmove = &first;
14666     } else {
14667         onmove = &second;
14668     }
14669     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14670     SendToProgram(first.computerString, &first);
14671     if (first.sendName) {
14672       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14673       SendToProgram(buf, &first);
14674     }
14675     SendToProgram(second.computerString, &second);
14676     if (second.sendName) {
14677       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14678       SendToProgram(buf, &second);
14679     }
14680
14681     ResetClocks();
14682     if (!first.sendTime || !second.sendTime) {
14683         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14684         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14685     }
14686     if (onmove->sendTime) {
14687       if (onmove->useColors) {
14688         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14689       }
14690       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14691     }
14692     if (onmove->useColors) {
14693       SendToProgram(onmove->twoMachinesColor, onmove);
14694     }
14695     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14696 //    SendToProgram("go\n", onmove);
14697     onmove->maybeThinking = TRUE;
14698     SetMachineThinkingEnables();
14699
14700     StartClocks();
14701
14702     if(bookHit) { // [HGM] book: simulate book reply
14703         static char bookMove[MSG_SIZ]; // a bit generous?
14704
14705         programStats.nodes = programStats.depth = programStats.time =
14706         programStats.score = programStats.got_only_move = 0;
14707         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14708
14709         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14710         strcat(bookMove, bookHit);
14711         savedMessage = bookMove; // args for deferred call
14712         savedState = onmove;
14713         ScheduleDelayedEvent(DeferredBookMove, 1);
14714     }
14715 }
14716
14717 void
14718 TrainingEvent ()
14719 {
14720     if (gameMode == Training) {
14721       SetTrainingModeOff();
14722       gameMode = PlayFromGameFile;
14723       DisplayMessage("", _("Training mode off"));
14724     } else {
14725       gameMode = Training;
14726       animateTraining = appData.animate;
14727
14728       /* make sure we are not already at the end of the game */
14729       if (currentMove < forwardMostMove) {
14730         SetTrainingModeOn();
14731         DisplayMessage("", _("Training mode on"));
14732       } else {
14733         gameMode = PlayFromGameFile;
14734         DisplayError(_("Already at end of game"), 0);
14735       }
14736     }
14737     ModeHighlight();
14738 }
14739
14740 void
14741 IcsClientEvent ()
14742 {
14743     if (!appData.icsActive) return;
14744     switch (gameMode) {
14745       case IcsPlayingWhite:
14746       case IcsPlayingBlack:
14747       case IcsObserving:
14748       case IcsIdle:
14749       case BeginningOfGame:
14750       case IcsExamining:
14751         return;
14752
14753       case EditGame:
14754         break;
14755
14756       case EditPosition:
14757         EditPositionDone(TRUE);
14758         break;
14759
14760       case AnalyzeMode:
14761       case AnalyzeFile:
14762         ExitAnalyzeMode();
14763         break;
14764
14765       default:
14766         EditGameEvent();
14767         break;
14768     }
14769
14770     gameMode = IcsIdle;
14771     ModeHighlight();
14772     return;
14773 }
14774
14775 void
14776 EditGameEvent ()
14777 {
14778     int i;
14779
14780     switch (gameMode) {
14781       case Training:
14782         SetTrainingModeOff();
14783         break;
14784       case MachinePlaysWhite:
14785       case MachinePlaysBlack:
14786       case BeginningOfGame:
14787         SendToProgram("force\n", &first);
14788         SetUserThinkingEnables();
14789         break;
14790       case PlayFromGameFile:
14791         (void) StopLoadGameTimer();
14792         if (gameFileFP != NULL) {
14793             gameFileFP = NULL;
14794         }
14795         break;
14796       case EditPosition:
14797         EditPositionDone(TRUE);
14798         break;
14799       case AnalyzeMode:
14800       case AnalyzeFile:
14801         ExitAnalyzeMode();
14802         SendToProgram("force\n", &first);
14803         break;
14804       case TwoMachinesPlay:
14805         GameEnds(EndOfFile, NULL, GE_PLAYER);
14806         ResurrectChessProgram();
14807         SetUserThinkingEnables();
14808         break;
14809       case EndOfGame:
14810         ResurrectChessProgram();
14811         break;
14812       case IcsPlayingBlack:
14813       case IcsPlayingWhite:
14814         DisplayError(_("Warning: You are still playing a game"), 0);
14815         break;
14816       case IcsObserving:
14817         DisplayError(_("Warning: You are still observing a game"), 0);
14818         break;
14819       case IcsExamining:
14820         DisplayError(_("Warning: You are still examining a game"), 0);
14821         break;
14822       case IcsIdle:
14823         break;
14824       case EditGame:
14825       default:
14826         return;
14827     }
14828
14829     pausing = FALSE;
14830     StopClocks();
14831     first.offeredDraw = second.offeredDraw = 0;
14832
14833     if (gameMode == PlayFromGameFile) {
14834         whiteTimeRemaining = timeRemaining[0][currentMove];
14835         blackTimeRemaining = timeRemaining[1][currentMove];
14836         DisplayTitle("");
14837     }
14838
14839     if (gameMode == MachinePlaysWhite ||
14840         gameMode == MachinePlaysBlack ||
14841         gameMode == TwoMachinesPlay ||
14842         gameMode == EndOfGame) {
14843         i = forwardMostMove;
14844         while (i > currentMove) {
14845             SendToProgram("undo\n", &first);
14846             i--;
14847         }
14848         if(!adjustedClock) {
14849         whiteTimeRemaining = timeRemaining[0][currentMove];
14850         blackTimeRemaining = timeRemaining[1][currentMove];
14851         DisplayBothClocks();
14852         }
14853         if (whiteFlag || blackFlag) {
14854             whiteFlag = blackFlag = 0;
14855         }
14856         DisplayTitle("");
14857     }
14858
14859     gameMode = EditGame;
14860     ModeHighlight();
14861     SetGameInfo();
14862 }
14863
14864
14865 void
14866 EditPositionEvent ()
14867 {
14868     if (gameMode == EditPosition) {
14869         EditGameEvent();
14870         return;
14871     }
14872
14873     EditGameEvent();
14874     if (gameMode != EditGame) return;
14875
14876     gameMode = EditPosition;
14877     ModeHighlight();
14878     SetGameInfo();
14879     if (currentMove > 0)
14880       CopyBoard(boards[0], boards[currentMove]);
14881
14882     blackPlaysFirst = !WhiteOnMove(currentMove);
14883     ResetClocks();
14884     currentMove = forwardMostMove = backwardMostMove = 0;
14885     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14886     DisplayMove(-1);
14887     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14888 }
14889
14890 void
14891 ExitAnalyzeMode ()
14892 {
14893     /* [DM] icsEngineAnalyze - possible call from other functions */
14894     if (appData.icsEngineAnalyze) {
14895         appData.icsEngineAnalyze = FALSE;
14896
14897         DisplayMessage("",_("Close ICS engine analyze..."));
14898     }
14899     if (first.analysisSupport && first.analyzing) {
14900       SendToBoth("exit\n");
14901       first.analyzing = second.analyzing = FALSE;
14902     }
14903     thinkOutput[0] = NULLCHAR;
14904 }
14905
14906 void
14907 EditPositionDone (Boolean fakeRights)
14908 {
14909     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14910
14911     startedFromSetupPosition = TRUE;
14912     InitChessProgram(&first, FALSE);
14913     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14914       boards[0][EP_STATUS] = EP_NONE;
14915       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14916       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14917         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14918         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14919       } else boards[0][CASTLING][2] = NoRights;
14920       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14921         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14922         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14923       } else boards[0][CASTLING][5] = NoRights;
14924       if(gameInfo.variant == VariantSChess) {
14925         int i;
14926         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14927           boards[0][VIRGIN][i] = 0;
14928           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14929           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14930         }
14931       }
14932     }
14933     SendToProgram("force\n", &first);
14934     if (blackPlaysFirst) {
14935         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14936         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14937         currentMove = forwardMostMove = backwardMostMove = 1;
14938         CopyBoard(boards[1], boards[0]);
14939     } else {
14940         currentMove = forwardMostMove = backwardMostMove = 0;
14941     }
14942     SendBoard(&first, forwardMostMove);
14943     if (appData.debugMode) {
14944         fprintf(debugFP, "EditPosDone\n");
14945     }
14946     DisplayTitle("");
14947     DisplayMessage("", "");
14948     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14949     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14950     gameMode = EditGame;
14951     ModeHighlight();
14952     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14953     ClearHighlights(); /* [AS] */
14954 }
14955
14956 /* Pause for `ms' milliseconds */
14957 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14958 void
14959 TimeDelay (long ms)
14960 {
14961     TimeMark m1, m2;
14962
14963     GetTimeMark(&m1);
14964     do {
14965         GetTimeMark(&m2);
14966     } while (SubtractTimeMarks(&m2, &m1) < ms);
14967 }
14968
14969 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14970 void
14971 SendMultiLineToICS (char *buf)
14972 {
14973     char temp[MSG_SIZ+1], *p;
14974     int len;
14975
14976     len = strlen(buf);
14977     if (len > MSG_SIZ)
14978       len = MSG_SIZ;
14979
14980     strncpy(temp, buf, len);
14981     temp[len] = 0;
14982
14983     p = temp;
14984     while (*p) {
14985         if (*p == '\n' || *p == '\r')
14986           *p = ' ';
14987         ++p;
14988     }
14989
14990     strcat(temp, "\n");
14991     SendToICS(temp);
14992     SendToPlayer(temp, strlen(temp));
14993 }
14994
14995 void
14996 SetWhiteToPlayEvent ()
14997 {
14998     if (gameMode == EditPosition) {
14999         blackPlaysFirst = FALSE;
15000         DisplayBothClocks();    /* works because currentMove is 0 */
15001     } else if (gameMode == IcsExamining) {
15002         SendToICS(ics_prefix);
15003         SendToICS("tomove white\n");
15004     }
15005 }
15006
15007 void
15008 SetBlackToPlayEvent ()
15009 {
15010     if (gameMode == EditPosition) {
15011         blackPlaysFirst = TRUE;
15012         currentMove = 1;        /* kludge */
15013         DisplayBothClocks();
15014         currentMove = 0;
15015     } else if (gameMode == IcsExamining) {
15016         SendToICS(ics_prefix);
15017         SendToICS("tomove black\n");
15018     }
15019 }
15020
15021 void
15022 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15023 {
15024     char buf[MSG_SIZ];
15025     ChessSquare piece = boards[0][y][x];
15026     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15027     static int lastVariant;
15028
15029     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15030
15031     switch (selection) {
15032       case ClearBoard:
15033         CopyBoard(currentBoard, boards[0]);
15034         CopyBoard(menuBoard, initialPosition);
15035         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15036             SendToICS(ics_prefix);
15037             SendToICS("bsetup clear\n");
15038         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15039             SendToICS(ics_prefix);
15040             SendToICS("clearboard\n");
15041         } else {
15042             int nonEmpty = 0;
15043             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15044                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15045                 for (y = 0; y < BOARD_HEIGHT; y++) {
15046                     if (gameMode == IcsExamining) {
15047                         if (boards[currentMove][y][x] != EmptySquare) {
15048                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15049                                     AAA + x, ONE + y);
15050                             SendToICS(buf);
15051                         }
15052                     } else {
15053                         if(boards[0][y][x] != p) nonEmpty++;
15054                         boards[0][y][x] = p;
15055                     }
15056                 }
15057             }
15058             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15059                 int r;
15060                 for(r = 0; r < BOARD_HEIGHT; r++) {
15061                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15062                     ChessSquare p = menuBoard[r][x];
15063                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15064                   }
15065                 }
15066                 DisplayMessage("Clicking clock again restores position", "");
15067                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15068                 if(!nonEmpty) { // asked to clear an empty board
15069                     CopyBoard(boards[0], menuBoard);
15070                 } else
15071                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15072                     CopyBoard(boards[0], initialPosition);
15073                 } else
15074                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15075                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15076                     CopyBoard(boards[0], erasedBoard);
15077                 } else
15078                     CopyBoard(erasedBoard, currentBoard);
15079
15080             }
15081         }
15082         if (gameMode == EditPosition) {
15083             DrawPosition(FALSE, boards[0]);
15084         }
15085         break;
15086
15087       case WhitePlay:
15088         SetWhiteToPlayEvent();
15089         break;
15090
15091       case BlackPlay:
15092         SetBlackToPlayEvent();
15093         break;
15094
15095       case EmptySquare:
15096         if (gameMode == IcsExamining) {
15097             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15098             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15099             SendToICS(buf);
15100         } else {
15101             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15102                 if(x == BOARD_LEFT-2) {
15103                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15104                     boards[0][y][1] = 0;
15105                 } else
15106                 if(x == BOARD_RGHT+1) {
15107                     if(y >= gameInfo.holdingsSize) break;
15108                     boards[0][y][BOARD_WIDTH-2] = 0;
15109                 } else break;
15110             }
15111             boards[0][y][x] = EmptySquare;
15112             DrawPosition(FALSE, boards[0]);
15113         }
15114         break;
15115
15116       case PromotePiece:
15117         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15118            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15119             selection = (ChessSquare) (PROMOTED piece);
15120         } else if(piece == EmptySquare) selection = WhiteSilver;
15121         else selection = (ChessSquare)((int)piece - 1);
15122         goto defaultlabel;
15123
15124       case DemotePiece:
15125         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15126            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15127             selection = (ChessSquare) (DEMOTED piece);
15128         } else if(piece == EmptySquare) selection = BlackSilver;
15129         else selection = (ChessSquare)((int)piece + 1);
15130         goto defaultlabel;
15131
15132       case WhiteQueen:
15133       case BlackQueen:
15134         if(gameInfo.variant == VariantShatranj ||
15135            gameInfo.variant == VariantXiangqi  ||
15136            gameInfo.variant == VariantCourier  ||
15137            gameInfo.variant == VariantASEAN    ||
15138            gameInfo.variant == VariantMakruk     )
15139             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15140         goto defaultlabel;
15141
15142       case WhiteKing:
15143       case BlackKing:
15144         if(gameInfo.variant == VariantXiangqi)
15145             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15146         if(gameInfo.variant == VariantKnightmate)
15147             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15148       default:
15149         defaultlabel:
15150         if (gameMode == IcsExamining) {
15151             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15152             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15153                      PieceToChar(selection), AAA + x, ONE + y);
15154             SendToICS(buf);
15155         } else {
15156             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15157                 int n;
15158                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15159                     n = PieceToNumber(selection - BlackPawn);
15160                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15161                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15162                     boards[0][BOARD_HEIGHT-1-n][1]++;
15163                 } else
15164                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15165                     n = PieceToNumber(selection);
15166                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15167                     boards[0][n][BOARD_WIDTH-1] = selection;
15168                     boards[0][n][BOARD_WIDTH-2]++;
15169                 }
15170             } else
15171             boards[0][y][x] = selection;
15172             DrawPosition(TRUE, boards[0]);
15173             ClearHighlights();
15174             fromX = fromY = -1;
15175         }
15176         break;
15177     }
15178 }
15179
15180
15181 void
15182 DropMenuEvent (ChessSquare selection, int x, int y)
15183 {
15184     ChessMove moveType;
15185
15186     switch (gameMode) {
15187       case IcsPlayingWhite:
15188       case MachinePlaysBlack:
15189         if (!WhiteOnMove(currentMove)) {
15190             DisplayMoveError(_("It is Black's turn"));
15191             return;
15192         }
15193         moveType = WhiteDrop;
15194         break;
15195       case IcsPlayingBlack:
15196       case MachinePlaysWhite:
15197         if (WhiteOnMove(currentMove)) {
15198             DisplayMoveError(_("It is White's turn"));
15199             return;
15200         }
15201         moveType = BlackDrop;
15202         break;
15203       case EditGame:
15204         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15205         break;
15206       default:
15207         return;
15208     }
15209
15210     if (moveType == BlackDrop && selection < BlackPawn) {
15211       selection = (ChessSquare) ((int) selection
15212                                  + (int) BlackPawn - (int) WhitePawn);
15213     }
15214     if (boards[currentMove][y][x] != EmptySquare) {
15215         DisplayMoveError(_("That square is occupied"));
15216         return;
15217     }
15218
15219     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15220 }
15221
15222 void
15223 AcceptEvent ()
15224 {
15225     /* Accept a pending offer of any kind from opponent */
15226
15227     if (appData.icsActive) {
15228         SendToICS(ics_prefix);
15229         SendToICS("accept\n");
15230     } else if (cmailMsgLoaded) {
15231         if (currentMove == cmailOldMove &&
15232             commentList[cmailOldMove] != NULL &&
15233             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15234                    "Black offers a draw" : "White offers a draw")) {
15235             TruncateGame();
15236             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15237             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15238         } else {
15239             DisplayError(_("There is no pending offer on this move"), 0);
15240             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15241         }
15242     } else {
15243         /* Not used for offers from chess program */
15244     }
15245 }
15246
15247 void
15248 DeclineEvent ()
15249 {
15250     /* Decline a pending offer of any kind from opponent */
15251
15252     if (appData.icsActive) {
15253         SendToICS(ics_prefix);
15254         SendToICS("decline\n");
15255     } else if (cmailMsgLoaded) {
15256         if (currentMove == cmailOldMove &&
15257             commentList[cmailOldMove] != NULL &&
15258             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15259                    "Black offers a draw" : "White offers a draw")) {
15260 #ifdef NOTDEF
15261             AppendComment(cmailOldMove, "Draw declined", TRUE);
15262             DisplayComment(cmailOldMove - 1, "Draw declined");
15263 #endif /*NOTDEF*/
15264         } else {
15265             DisplayError(_("There is no pending offer on this move"), 0);
15266         }
15267     } else {
15268         /* Not used for offers from chess program */
15269     }
15270 }
15271
15272 void
15273 RematchEvent ()
15274 {
15275     /* Issue ICS rematch command */
15276     if (appData.icsActive) {
15277         SendToICS(ics_prefix);
15278         SendToICS("rematch\n");
15279     }
15280 }
15281
15282 void
15283 CallFlagEvent ()
15284 {
15285     /* Call your opponent's flag (claim a win on time) */
15286     if (appData.icsActive) {
15287         SendToICS(ics_prefix);
15288         SendToICS("flag\n");
15289     } else {
15290         switch (gameMode) {
15291           default:
15292             return;
15293           case MachinePlaysWhite:
15294             if (whiteFlag) {
15295                 if (blackFlag)
15296                   GameEnds(GameIsDrawn, "Both players ran out of time",
15297                            GE_PLAYER);
15298                 else
15299                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15300             } else {
15301                 DisplayError(_("Your opponent is not out of time"), 0);
15302             }
15303             break;
15304           case MachinePlaysBlack:
15305             if (blackFlag) {
15306                 if (whiteFlag)
15307                   GameEnds(GameIsDrawn, "Both players ran out of time",
15308                            GE_PLAYER);
15309                 else
15310                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15311             } else {
15312                 DisplayError(_("Your opponent is not out of time"), 0);
15313             }
15314             break;
15315         }
15316     }
15317 }
15318
15319 void
15320 ClockClick (int which)
15321 {       // [HGM] code moved to back-end from winboard.c
15322         if(which) { // black clock
15323           if (gameMode == EditPosition || gameMode == IcsExamining) {
15324             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15325             SetBlackToPlayEvent();
15326           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15327                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15328           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15329           } else if (shiftKey) {
15330             AdjustClock(which, -1);
15331           } else if (gameMode == IcsPlayingWhite ||
15332                      gameMode == MachinePlaysBlack) {
15333             CallFlagEvent();
15334           }
15335         } else { // white clock
15336           if (gameMode == EditPosition || gameMode == IcsExamining) {
15337             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15338             SetWhiteToPlayEvent();
15339           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15340                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15341           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15342           } else if (shiftKey) {
15343             AdjustClock(which, -1);
15344           } else if (gameMode == IcsPlayingBlack ||
15345                    gameMode == MachinePlaysWhite) {
15346             CallFlagEvent();
15347           }
15348         }
15349 }
15350
15351 void
15352 DrawEvent ()
15353 {
15354     /* Offer draw or accept pending draw offer from opponent */
15355
15356     if (appData.icsActive) {
15357         /* Note: tournament rules require draw offers to be
15358            made after you make your move but before you punch
15359            your clock.  Currently ICS doesn't let you do that;
15360            instead, you immediately punch your clock after making
15361            a move, but you can offer a draw at any time. */
15362
15363         SendToICS(ics_prefix);
15364         SendToICS("draw\n");
15365         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15366     } else if (cmailMsgLoaded) {
15367         if (currentMove == cmailOldMove &&
15368             commentList[cmailOldMove] != NULL &&
15369             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15370                    "Black offers a draw" : "White offers a draw")) {
15371             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15372             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15373         } else if (currentMove == cmailOldMove + 1) {
15374             char *offer = WhiteOnMove(cmailOldMove) ?
15375               "White offers a draw" : "Black offers a draw";
15376             AppendComment(currentMove, offer, TRUE);
15377             DisplayComment(currentMove - 1, offer);
15378             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15379         } else {
15380             DisplayError(_("You must make your move before offering a draw"), 0);
15381             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15382         }
15383     } else if (first.offeredDraw) {
15384         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15385     } else {
15386         if (first.sendDrawOffers) {
15387             SendToProgram("draw\n", &first);
15388             userOfferedDraw = TRUE;
15389         }
15390     }
15391 }
15392
15393 void
15394 AdjournEvent ()
15395 {
15396     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15397
15398     if (appData.icsActive) {
15399         SendToICS(ics_prefix);
15400         SendToICS("adjourn\n");
15401     } else {
15402         /* Currently GNU Chess doesn't offer or accept Adjourns */
15403     }
15404 }
15405
15406
15407 void
15408 AbortEvent ()
15409 {
15410     /* Offer Abort or accept pending Abort offer from opponent */
15411
15412     if (appData.icsActive) {
15413         SendToICS(ics_prefix);
15414         SendToICS("abort\n");
15415     } else {
15416         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15417     }
15418 }
15419
15420 void
15421 ResignEvent ()
15422 {
15423     /* Resign.  You can do this even if it's not your turn. */
15424
15425     if (appData.icsActive) {
15426         SendToICS(ics_prefix);
15427         SendToICS("resign\n");
15428     } else {
15429         switch (gameMode) {
15430           case MachinePlaysWhite:
15431             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15432             break;
15433           case MachinePlaysBlack:
15434             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15435             break;
15436           case EditGame:
15437             if (cmailMsgLoaded) {
15438                 TruncateGame();
15439                 if (WhiteOnMove(cmailOldMove)) {
15440                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15441                 } else {
15442                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15443                 }
15444                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15445             }
15446             break;
15447           default:
15448             break;
15449         }
15450     }
15451 }
15452
15453
15454 void
15455 StopObservingEvent ()
15456 {
15457     /* Stop observing current games */
15458     SendToICS(ics_prefix);
15459     SendToICS("unobserve\n");
15460 }
15461
15462 void
15463 StopExaminingEvent ()
15464 {
15465     /* Stop observing current game */
15466     SendToICS(ics_prefix);
15467     SendToICS("unexamine\n");
15468 }
15469
15470 void
15471 ForwardInner (int target)
15472 {
15473     int limit; int oldSeekGraphUp = seekGraphUp;
15474
15475     if (appData.debugMode)
15476         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15477                 target, currentMove, forwardMostMove);
15478
15479     if (gameMode == EditPosition)
15480       return;
15481
15482     seekGraphUp = FALSE;
15483     MarkTargetSquares(1);
15484
15485     if (gameMode == PlayFromGameFile && !pausing)
15486       PauseEvent();
15487
15488     if (gameMode == IcsExamining && pausing)
15489       limit = pauseExamForwardMostMove;
15490     else
15491       limit = forwardMostMove;
15492
15493     if (target > limit) target = limit;
15494
15495     if (target > 0 && moveList[target - 1][0]) {
15496         int fromX, fromY, toX, toY;
15497         toX = moveList[target - 1][2] - AAA;
15498         toY = moveList[target - 1][3] - ONE;
15499         if (moveList[target - 1][1] == '@') {
15500             if (appData.highlightLastMove) {
15501                 SetHighlights(-1, -1, toX, toY);
15502             }
15503         } else {
15504             int viaX = moveList[target - 1][5] - AAA;
15505             int viaY = moveList[target - 1][6] - ONE;
15506             fromX = moveList[target - 1][0] - AAA;
15507             fromY = moveList[target - 1][1] - ONE;
15508             if (target == currentMove + 1) {
15509                 if(moveList[target - 1][4] == ';') { // multi-leg
15510                     ChessSquare piece = boards[currentMove][viaY][viaX];
15511                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15512                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15513                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15514                     boards[currentMove][viaY][viaX] = piece;
15515                 } else
15516                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15517             }
15518             if (appData.highlightLastMove) {
15519                 SetHighlights(fromX, fromY, toX, toY);
15520             }
15521         }
15522     }
15523     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15524         gameMode == Training || gameMode == PlayFromGameFile ||
15525         gameMode == AnalyzeFile) {
15526         while (currentMove < target) {
15527             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15528             SendMoveToProgram(currentMove++, &first);
15529         }
15530     } else {
15531         currentMove = target;
15532     }
15533
15534     if (gameMode == EditGame || gameMode == EndOfGame) {
15535         whiteTimeRemaining = timeRemaining[0][currentMove];
15536         blackTimeRemaining = timeRemaining[1][currentMove];
15537     }
15538     DisplayBothClocks();
15539     DisplayMove(currentMove - 1);
15540     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15541     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15542     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15543         DisplayComment(currentMove - 1, commentList[currentMove]);
15544     }
15545     ClearMap(); // [HGM] exclude: invalidate map
15546 }
15547
15548
15549 void
15550 ForwardEvent ()
15551 {
15552     if (gameMode == IcsExamining && !pausing) {
15553         SendToICS(ics_prefix);
15554         SendToICS("forward\n");
15555     } else {
15556         ForwardInner(currentMove + 1);
15557     }
15558 }
15559
15560 void
15561 ToEndEvent ()
15562 {
15563     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15564         /* to optimze, we temporarily turn off analysis mode while we feed
15565          * the remaining moves to the engine. Otherwise we get analysis output
15566          * after each move.
15567          */
15568         if (first.analysisSupport) {
15569           SendToProgram("exit\nforce\n", &first);
15570           first.analyzing = FALSE;
15571         }
15572     }
15573
15574     if (gameMode == IcsExamining && !pausing) {
15575         SendToICS(ics_prefix);
15576         SendToICS("forward 999999\n");
15577     } else {
15578         ForwardInner(forwardMostMove);
15579     }
15580
15581     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15582         /* we have fed all the moves, so reactivate analysis mode */
15583         SendToProgram("analyze\n", &first);
15584         first.analyzing = TRUE;
15585         /*first.maybeThinking = TRUE;*/
15586         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15587     }
15588 }
15589
15590 void
15591 BackwardInner (int target)
15592 {
15593     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15594
15595     if (appData.debugMode)
15596         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15597                 target, currentMove, forwardMostMove);
15598
15599     if (gameMode == EditPosition) return;
15600     seekGraphUp = FALSE;
15601     MarkTargetSquares(1);
15602     if (currentMove <= backwardMostMove) {
15603         ClearHighlights();
15604         DrawPosition(full_redraw, boards[currentMove]);
15605         return;
15606     }
15607     if (gameMode == PlayFromGameFile && !pausing)
15608       PauseEvent();
15609
15610     if (moveList[target][0]) {
15611         int fromX, fromY, toX, toY;
15612         toX = moveList[target][2] - AAA;
15613         toY = moveList[target][3] - ONE;
15614         if (moveList[target][1] == '@') {
15615             if (appData.highlightLastMove) {
15616                 SetHighlights(-1, -1, toX, toY);
15617             }
15618         } else {
15619             fromX = moveList[target][0] - AAA;
15620             fromY = moveList[target][1] - ONE;
15621             if (target == currentMove - 1) {
15622                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15623             }
15624             if (appData.highlightLastMove) {
15625                 SetHighlights(fromX, fromY, toX, toY);
15626             }
15627         }
15628     }
15629     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15630         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15631         while (currentMove > target) {
15632             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15633                 // null move cannot be undone. Reload program with move history before it.
15634                 int i;
15635                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15636                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15637                 }
15638                 SendBoard(&first, i);
15639               if(second.analyzing) SendBoard(&second, i);
15640                 for(currentMove=i; currentMove<target; currentMove++) {
15641                     SendMoveToProgram(currentMove, &first);
15642                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15643                 }
15644                 break;
15645             }
15646             SendToBoth("undo\n");
15647             currentMove--;
15648         }
15649     } else {
15650         currentMove = target;
15651     }
15652
15653     if (gameMode == EditGame || gameMode == EndOfGame) {
15654         whiteTimeRemaining = timeRemaining[0][currentMove];
15655         blackTimeRemaining = timeRemaining[1][currentMove];
15656     }
15657     DisplayBothClocks();
15658     DisplayMove(currentMove - 1);
15659     DrawPosition(full_redraw, boards[currentMove]);
15660     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15661     // [HGM] PV info: routine tests if comment empty
15662     DisplayComment(currentMove - 1, commentList[currentMove]);
15663     ClearMap(); // [HGM] exclude: invalidate map
15664 }
15665
15666 void
15667 BackwardEvent ()
15668 {
15669     if (gameMode == IcsExamining && !pausing) {
15670         SendToICS(ics_prefix);
15671         SendToICS("backward\n");
15672     } else {
15673         BackwardInner(currentMove - 1);
15674     }
15675 }
15676
15677 void
15678 ToStartEvent ()
15679 {
15680     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15681         /* to optimize, we temporarily turn off analysis mode while we undo
15682          * all the moves. Otherwise we get analysis output after each undo.
15683          */
15684         if (first.analysisSupport) {
15685           SendToProgram("exit\nforce\n", &first);
15686           first.analyzing = FALSE;
15687         }
15688     }
15689
15690     if (gameMode == IcsExamining && !pausing) {
15691         SendToICS(ics_prefix);
15692         SendToICS("backward 999999\n");
15693     } else {
15694         BackwardInner(backwardMostMove);
15695     }
15696
15697     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15698         /* we have fed all the moves, so reactivate analysis mode */
15699         SendToProgram("analyze\n", &first);
15700         first.analyzing = TRUE;
15701         /*first.maybeThinking = TRUE;*/
15702         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15703     }
15704 }
15705
15706 void
15707 ToNrEvent (int to)
15708 {
15709   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15710   if (to >= forwardMostMove) to = forwardMostMove;
15711   if (to <= backwardMostMove) to = backwardMostMove;
15712   if (to < currentMove) {
15713     BackwardInner(to);
15714   } else {
15715     ForwardInner(to);
15716   }
15717 }
15718
15719 void
15720 RevertEvent (Boolean annotate)
15721 {
15722     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15723         return;
15724     }
15725     if (gameMode != IcsExamining) {
15726         DisplayError(_("You are not examining a game"), 0);
15727         return;
15728     }
15729     if (pausing) {
15730         DisplayError(_("You can't revert while pausing"), 0);
15731         return;
15732     }
15733     SendToICS(ics_prefix);
15734     SendToICS("revert\n");
15735 }
15736
15737 void
15738 RetractMoveEvent ()
15739 {
15740     switch (gameMode) {
15741       case MachinePlaysWhite:
15742       case MachinePlaysBlack:
15743         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15744             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15745             return;
15746         }
15747         if (forwardMostMove < 2) return;
15748         currentMove = forwardMostMove = forwardMostMove - 2;
15749         whiteTimeRemaining = timeRemaining[0][currentMove];
15750         blackTimeRemaining = timeRemaining[1][currentMove];
15751         DisplayBothClocks();
15752         DisplayMove(currentMove - 1);
15753         ClearHighlights();/*!! could figure this out*/
15754         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15755         SendToProgram("remove\n", &first);
15756         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15757         break;
15758
15759       case BeginningOfGame:
15760       default:
15761         break;
15762
15763       case IcsPlayingWhite:
15764       case IcsPlayingBlack:
15765         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15766             SendToICS(ics_prefix);
15767             SendToICS("takeback 2\n");
15768         } else {
15769             SendToICS(ics_prefix);
15770             SendToICS("takeback 1\n");
15771         }
15772         break;
15773     }
15774 }
15775
15776 void
15777 MoveNowEvent ()
15778 {
15779     ChessProgramState *cps;
15780
15781     switch (gameMode) {
15782       case MachinePlaysWhite:
15783         if (!WhiteOnMove(forwardMostMove)) {
15784             DisplayError(_("It is your turn"), 0);
15785             return;
15786         }
15787         cps = &first;
15788         break;
15789       case MachinePlaysBlack:
15790         if (WhiteOnMove(forwardMostMove)) {
15791             DisplayError(_("It is your turn"), 0);
15792             return;
15793         }
15794         cps = &first;
15795         break;
15796       case TwoMachinesPlay:
15797         if (WhiteOnMove(forwardMostMove) ==
15798             (first.twoMachinesColor[0] == 'w')) {
15799             cps = &first;
15800         } else {
15801             cps = &second;
15802         }
15803         break;
15804       case BeginningOfGame:
15805       default:
15806         return;
15807     }
15808     SendToProgram("?\n", cps);
15809 }
15810
15811 void
15812 TruncateGameEvent ()
15813 {
15814     EditGameEvent();
15815     if (gameMode != EditGame) return;
15816     TruncateGame();
15817 }
15818
15819 void
15820 TruncateGame ()
15821 {
15822     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15823     if (forwardMostMove > currentMove) {
15824         if (gameInfo.resultDetails != NULL) {
15825             free(gameInfo.resultDetails);
15826             gameInfo.resultDetails = NULL;
15827             gameInfo.result = GameUnfinished;
15828         }
15829         forwardMostMove = currentMove;
15830         HistorySet(parseList, backwardMostMove, forwardMostMove,
15831                    currentMove-1);
15832     }
15833 }
15834
15835 void
15836 HintEvent ()
15837 {
15838     if (appData.noChessProgram) return;
15839     switch (gameMode) {
15840       case MachinePlaysWhite:
15841         if (WhiteOnMove(forwardMostMove)) {
15842             DisplayError(_("Wait until your turn."), 0);
15843             return;
15844         }
15845         break;
15846       case BeginningOfGame:
15847       case MachinePlaysBlack:
15848         if (!WhiteOnMove(forwardMostMove)) {
15849             DisplayError(_("Wait until your turn."), 0);
15850             return;
15851         }
15852         break;
15853       default:
15854         DisplayError(_("No hint available"), 0);
15855         return;
15856     }
15857     SendToProgram("hint\n", &first);
15858     hintRequested = TRUE;
15859 }
15860
15861 int
15862 SaveSelected (FILE *g, int dummy, char *dummy2)
15863 {
15864     ListGame * lg = (ListGame *) gameList.head;
15865     int nItem, cnt=0;
15866     FILE *f;
15867
15868     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15869         DisplayError(_("Game list not loaded or empty"), 0);
15870         return 0;
15871     }
15872
15873     creatingBook = TRUE; // suppresses stuff during load game
15874
15875     /* Get list size */
15876     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15877         if(lg->position >= 0) { // selected?
15878             LoadGame(f, nItem, "", TRUE);
15879             SaveGamePGN2(g); // leaves g open
15880             cnt++; DoEvents();
15881         }
15882         lg = (ListGame *) lg->node.succ;
15883     }
15884
15885     fclose(g);
15886     creatingBook = FALSE;
15887
15888     return cnt;
15889 }
15890
15891 void
15892 CreateBookEvent ()
15893 {
15894     ListGame * lg = (ListGame *) gameList.head;
15895     FILE *f, *g;
15896     int nItem;
15897     static int secondTime = FALSE;
15898
15899     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15900         DisplayError(_("Game list not loaded or empty"), 0);
15901         return;
15902     }
15903
15904     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15905         fclose(g);
15906         secondTime++;
15907         DisplayNote(_("Book file exists! Try again for overwrite."));
15908         return;
15909     }
15910
15911     creatingBook = TRUE;
15912     secondTime = FALSE;
15913
15914     /* Get list size */
15915     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15916         if(lg->position >= 0) {
15917             LoadGame(f, nItem, "", TRUE);
15918             AddGameToBook(TRUE);
15919             DoEvents();
15920         }
15921         lg = (ListGame *) lg->node.succ;
15922     }
15923
15924     creatingBook = FALSE;
15925     FlushBook();
15926 }
15927
15928 void
15929 BookEvent ()
15930 {
15931     if (appData.noChessProgram) return;
15932     switch (gameMode) {
15933       case MachinePlaysWhite:
15934         if (WhiteOnMove(forwardMostMove)) {
15935             DisplayError(_("Wait until your turn."), 0);
15936             return;
15937         }
15938         break;
15939       case BeginningOfGame:
15940       case MachinePlaysBlack:
15941         if (!WhiteOnMove(forwardMostMove)) {
15942             DisplayError(_("Wait until your turn."), 0);
15943             return;
15944         }
15945         break;
15946       case EditPosition:
15947         EditPositionDone(TRUE);
15948         break;
15949       case TwoMachinesPlay:
15950         return;
15951       default:
15952         break;
15953     }
15954     SendToProgram("bk\n", &first);
15955     bookOutput[0] = NULLCHAR;
15956     bookRequested = TRUE;
15957 }
15958
15959 void
15960 AboutGameEvent ()
15961 {
15962     char *tags = PGNTags(&gameInfo);
15963     TagsPopUp(tags, CmailMsg());
15964     free(tags);
15965 }
15966
15967 /* end button procedures */
15968
15969 void
15970 PrintPosition (FILE *fp, int move)
15971 {
15972     int i, j;
15973
15974     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15975         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15976             char c = PieceToChar(boards[move][i][j]);
15977             fputc(c == 'x' ? '.' : c, fp);
15978             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15979         }
15980     }
15981     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15982       fprintf(fp, "white to play\n");
15983     else
15984       fprintf(fp, "black to play\n");
15985 }
15986
15987 void
15988 PrintOpponents (FILE *fp)
15989 {
15990     if (gameInfo.white != NULL) {
15991         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15992     } else {
15993         fprintf(fp, "\n");
15994     }
15995 }
15996
15997 /* Find last component of program's own name, using some heuristics */
15998 void
15999 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16000 {
16001     char *p, *q, c;
16002     int local = (strcmp(host, "localhost") == 0);
16003     while (!local && (p = strchr(prog, ';')) != NULL) {
16004         p++;
16005         while (*p == ' ') p++;
16006         prog = p;
16007     }
16008     if (*prog == '"' || *prog == '\'') {
16009         q = strchr(prog + 1, *prog);
16010     } else {
16011         q = strchr(prog, ' ');
16012     }
16013     if (q == NULL) q = prog + strlen(prog);
16014     p = q;
16015     while (p >= prog && *p != '/' && *p != '\\') p--;
16016     p++;
16017     if(p == prog && *p == '"') p++;
16018     c = *q; *q = 0;
16019     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16020     memcpy(buf, p, q - p);
16021     buf[q - p] = NULLCHAR;
16022     if (!local) {
16023         strcat(buf, "@");
16024         strcat(buf, host);
16025     }
16026 }
16027
16028 char *
16029 TimeControlTagValue ()
16030 {
16031     char buf[MSG_SIZ];
16032     if (!appData.clockMode) {
16033       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16034     } else if (movesPerSession > 0) {
16035       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16036     } else if (timeIncrement == 0) {
16037       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16038     } else {
16039       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16040     }
16041     return StrSave(buf);
16042 }
16043
16044 void
16045 SetGameInfo ()
16046 {
16047     /* This routine is used only for certain modes */
16048     VariantClass v = gameInfo.variant;
16049     ChessMove r = GameUnfinished;
16050     char *p = NULL;
16051
16052     if(keepInfo) return;
16053
16054     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16055         r = gameInfo.result;
16056         p = gameInfo.resultDetails;
16057         gameInfo.resultDetails = NULL;
16058     }
16059     ClearGameInfo(&gameInfo);
16060     gameInfo.variant = v;
16061
16062     switch (gameMode) {
16063       case MachinePlaysWhite:
16064         gameInfo.event = StrSave( appData.pgnEventHeader );
16065         gameInfo.site = StrSave(HostName());
16066         gameInfo.date = PGNDate();
16067         gameInfo.round = StrSave("-");
16068         gameInfo.white = StrSave(first.tidy);
16069         gameInfo.black = StrSave(UserName());
16070         gameInfo.timeControl = TimeControlTagValue();
16071         break;
16072
16073       case MachinePlaysBlack:
16074         gameInfo.event = StrSave( appData.pgnEventHeader );
16075         gameInfo.site = StrSave(HostName());
16076         gameInfo.date = PGNDate();
16077         gameInfo.round = StrSave("-");
16078         gameInfo.white = StrSave(UserName());
16079         gameInfo.black = StrSave(first.tidy);
16080         gameInfo.timeControl = TimeControlTagValue();
16081         break;
16082
16083       case TwoMachinesPlay:
16084         gameInfo.event = StrSave( appData.pgnEventHeader );
16085         gameInfo.site = StrSave(HostName());
16086         gameInfo.date = PGNDate();
16087         if (roundNr > 0) {
16088             char buf[MSG_SIZ];
16089             snprintf(buf, MSG_SIZ, "%d", roundNr);
16090             gameInfo.round = StrSave(buf);
16091         } else {
16092             gameInfo.round = StrSave("-");
16093         }
16094         if (first.twoMachinesColor[0] == 'w') {
16095             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16096             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16097         } else {
16098             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16099             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16100         }
16101         gameInfo.timeControl = TimeControlTagValue();
16102         break;
16103
16104       case EditGame:
16105         gameInfo.event = StrSave("Edited game");
16106         gameInfo.site = StrSave(HostName());
16107         gameInfo.date = PGNDate();
16108         gameInfo.round = StrSave("-");
16109         gameInfo.white = StrSave("-");
16110         gameInfo.black = StrSave("-");
16111         gameInfo.result = r;
16112         gameInfo.resultDetails = p;
16113         break;
16114
16115       case EditPosition:
16116         gameInfo.event = StrSave("Edited position");
16117         gameInfo.site = StrSave(HostName());
16118         gameInfo.date = PGNDate();
16119         gameInfo.round = StrSave("-");
16120         gameInfo.white = StrSave("-");
16121         gameInfo.black = StrSave("-");
16122         break;
16123
16124       case IcsPlayingWhite:
16125       case IcsPlayingBlack:
16126       case IcsObserving:
16127       case IcsExamining:
16128         break;
16129
16130       case PlayFromGameFile:
16131         gameInfo.event = StrSave("Game from non-PGN file");
16132         gameInfo.site = StrSave(HostName());
16133         gameInfo.date = PGNDate();
16134         gameInfo.round = StrSave("-");
16135         gameInfo.white = StrSave("?");
16136         gameInfo.black = StrSave("?");
16137         break;
16138
16139       default:
16140         break;
16141     }
16142 }
16143
16144 void
16145 ReplaceComment (int index, char *text)
16146 {
16147     int len;
16148     char *p;
16149     float score;
16150
16151     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16152        pvInfoList[index-1].depth == len &&
16153        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16154        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16155     while (*text == '\n') text++;
16156     len = strlen(text);
16157     while (len > 0 && text[len - 1] == '\n') len--;
16158
16159     if (commentList[index] != NULL)
16160       free(commentList[index]);
16161
16162     if (len == 0) {
16163         commentList[index] = NULL;
16164         return;
16165     }
16166   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16167       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16168       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16169     commentList[index] = (char *) malloc(len + 2);
16170     strncpy(commentList[index], text, len);
16171     commentList[index][len] = '\n';
16172     commentList[index][len + 1] = NULLCHAR;
16173   } else {
16174     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16175     char *p;
16176     commentList[index] = (char *) malloc(len + 7);
16177     safeStrCpy(commentList[index], "{\n", 3);
16178     safeStrCpy(commentList[index]+2, text, len+1);
16179     commentList[index][len+2] = NULLCHAR;
16180     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16181     strcat(commentList[index], "\n}\n");
16182   }
16183 }
16184
16185 void
16186 CrushCRs (char *text)
16187 {
16188   char *p = text;
16189   char *q = text;
16190   char ch;
16191
16192   do {
16193     ch = *p++;
16194     if (ch == '\r') continue;
16195     *q++ = ch;
16196   } while (ch != '\0');
16197 }
16198
16199 void
16200 AppendComment (int index, char *text, Boolean addBraces)
16201 /* addBraces  tells if we should add {} */
16202 {
16203     int oldlen, len;
16204     char *old;
16205
16206 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16207     if(addBraces == 3) addBraces = 0; else // force appending literally
16208     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16209
16210     CrushCRs(text);
16211     while (*text == '\n') text++;
16212     len = strlen(text);
16213     while (len > 0 && text[len - 1] == '\n') len--;
16214     text[len] = NULLCHAR;
16215
16216     if (len == 0) return;
16217
16218     if (commentList[index] != NULL) {
16219       Boolean addClosingBrace = addBraces;
16220         old = commentList[index];
16221         oldlen = strlen(old);
16222         while(commentList[index][oldlen-1] ==  '\n')
16223           commentList[index][--oldlen] = NULLCHAR;
16224         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16225         safeStrCpy(commentList[index], old, oldlen + len + 6);
16226         free(old);
16227         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16228         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16229           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16230           while (*text == '\n') { text++; len--; }
16231           commentList[index][--oldlen] = NULLCHAR;
16232       }
16233         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16234         else          strcat(commentList[index], "\n");
16235         strcat(commentList[index], text);
16236         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16237         else          strcat(commentList[index], "\n");
16238     } else {
16239         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16240         if(addBraces)
16241           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16242         else commentList[index][0] = NULLCHAR;
16243         strcat(commentList[index], text);
16244         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16245         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16246     }
16247 }
16248
16249 static char *
16250 FindStr (char * text, char * sub_text)
16251 {
16252     char * result = strstr( text, sub_text );
16253
16254     if( result != NULL ) {
16255         result += strlen( sub_text );
16256     }
16257
16258     return result;
16259 }
16260
16261 /* [AS] Try to extract PV info from PGN comment */
16262 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16263 char *
16264 GetInfoFromComment (int index, char * text)
16265 {
16266     char * sep = text, *p;
16267
16268     if( text != NULL && index > 0 ) {
16269         int score = 0;
16270         int depth = 0;
16271         int time = -1, sec = 0, deci;
16272         char * s_eval = FindStr( text, "[%eval " );
16273         char * s_emt = FindStr( text, "[%emt " );
16274 #if 0
16275         if( s_eval != NULL || s_emt != NULL ) {
16276 #else
16277         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16278 #endif
16279             /* New style */
16280             char delim;
16281
16282             if( s_eval != NULL ) {
16283                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16284                     return text;
16285                 }
16286
16287                 if( delim != ']' ) {
16288                     return text;
16289                 }
16290             }
16291
16292             if( s_emt != NULL ) {
16293             }
16294                 return text;
16295         }
16296         else {
16297             /* We expect something like: [+|-]nnn.nn/dd */
16298             int score_lo = 0;
16299
16300             if(*text != '{') return text; // [HGM] braces: must be normal comment
16301
16302             sep = strchr( text, '/' );
16303             if( sep == NULL || sep < (text+4) ) {
16304                 return text;
16305             }
16306
16307             p = text;
16308             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16309             if(p[1] == '(') { // comment starts with PV
16310                p = strchr(p, ')'); // locate end of PV
16311                if(p == NULL || sep < p+5) return text;
16312                // at this point we have something like "{(.*) +0.23/6 ..."
16313                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16314                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16315                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16316             }
16317             time = -1; sec = -1; deci = -1;
16318             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16319                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16320                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16321                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16322                 return text;
16323             }
16324
16325             if( score_lo < 0 || score_lo >= 100 ) {
16326                 return text;
16327             }
16328
16329             if(sec >= 0) time = 600*time + 10*sec; else
16330             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16331
16332             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16333
16334             /* [HGM] PV time: now locate end of PV info */
16335             while( *++sep >= '0' && *sep <= '9'); // strip depth
16336             if(time >= 0)
16337             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16338             if(sec >= 0)
16339             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16340             if(deci >= 0)
16341             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16342             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16343         }
16344
16345         if( depth <= 0 ) {
16346             return text;
16347         }
16348
16349         if( time < 0 ) {
16350             time = -1;
16351         }
16352
16353         pvInfoList[index-1].depth = depth;
16354         pvInfoList[index-1].score = score;
16355         pvInfoList[index-1].time  = 10*time; // centi-sec
16356         if(*sep == '}') *sep = 0; else *--sep = '{';
16357         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16358     }
16359     return sep;
16360 }
16361
16362 void
16363 SendToProgram (char *message, ChessProgramState *cps)
16364 {
16365     int count, outCount, error;
16366     char buf[MSG_SIZ];
16367
16368     if (cps->pr == NoProc) return;
16369     Attention(cps);
16370
16371     if (appData.debugMode) {
16372         TimeMark now;
16373         GetTimeMark(&now);
16374         fprintf(debugFP, "%ld >%-6s: %s",
16375                 SubtractTimeMarks(&now, &programStartTime),
16376                 cps->which, message);
16377         if(serverFP)
16378             fprintf(serverFP, "%ld >%-6s: %s",
16379                 SubtractTimeMarks(&now, &programStartTime),
16380                 cps->which, message), fflush(serverFP);
16381     }
16382
16383     count = strlen(message);
16384     outCount = OutputToProcess(cps->pr, message, count, &error);
16385     if (outCount < count && !exiting
16386                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16387       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16388       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16389         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16390             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16391                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16392                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16393                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16394             } else {
16395                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16396                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16397                 gameInfo.result = res;
16398             }
16399             gameInfo.resultDetails = StrSave(buf);
16400         }
16401         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16402         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16403     }
16404 }
16405
16406 void
16407 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16408 {
16409     char *end_str;
16410     char buf[MSG_SIZ];
16411     ChessProgramState *cps = (ChessProgramState *)closure;
16412
16413     if (isr != cps->isr) return; /* Killed intentionally */
16414     if (count <= 0) {
16415         if (count == 0) {
16416             RemoveInputSource(cps->isr);
16417             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16418                     _(cps->which), cps->program);
16419             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16420             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16421                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16422                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16423                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16424                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16425                 } else {
16426                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16427                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16428                     gameInfo.result = res;
16429                 }
16430                 gameInfo.resultDetails = StrSave(buf);
16431             }
16432             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16433             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16434         } else {
16435             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16436                     _(cps->which), cps->program);
16437             RemoveInputSource(cps->isr);
16438
16439             /* [AS] Program is misbehaving badly... kill it */
16440             if( count == -2 ) {
16441                 DestroyChildProcess( cps->pr, 9 );
16442                 cps->pr = NoProc;
16443             }
16444
16445             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16446         }
16447         return;
16448     }
16449
16450     if ((end_str = strchr(message, '\r')) != NULL)
16451       *end_str = NULLCHAR;
16452     if ((end_str = strchr(message, '\n')) != NULL)
16453       *end_str = NULLCHAR;
16454
16455     if (appData.debugMode) {
16456         TimeMark now; int print = 1;
16457         char *quote = ""; char c; int i;
16458
16459         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16460                 char start = message[0];
16461                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16462                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16463                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16464                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16465                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16466                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16467                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16468                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16469                    sscanf(message, "hint: %c", &c)!=1 &&
16470                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16471                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16472                     print = (appData.engineComments >= 2);
16473                 }
16474                 message[0] = start; // restore original message
16475         }
16476         if(print) {
16477                 GetTimeMark(&now);
16478                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16479                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16480                         quote,
16481                         message);
16482                 if(serverFP)
16483                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16484                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16485                         quote,
16486                         message), fflush(serverFP);
16487         }
16488     }
16489
16490     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16491     if (appData.icsEngineAnalyze) {
16492         if (strstr(message, "whisper") != NULL ||
16493              strstr(message, "kibitz") != NULL ||
16494             strstr(message, "tellics") != NULL) return;
16495     }
16496
16497     HandleMachineMove(message, cps);
16498 }
16499
16500
16501 void
16502 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16503 {
16504     char buf[MSG_SIZ];
16505     int seconds;
16506
16507     if( timeControl_2 > 0 ) {
16508         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16509             tc = timeControl_2;
16510         }
16511     }
16512     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16513     inc /= cps->timeOdds;
16514     st  /= cps->timeOdds;
16515
16516     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16517
16518     if (st > 0) {
16519       /* Set exact time per move, normally using st command */
16520       if (cps->stKludge) {
16521         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16522         seconds = st % 60;
16523         if (seconds == 0) {
16524           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16525         } else {
16526           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16527         }
16528       } else {
16529         snprintf(buf, MSG_SIZ, "st %d\n", st);
16530       }
16531     } else {
16532       /* Set conventional or incremental time control, using level command */
16533       if (seconds == 0) {
16534         /* Note old gnuchess bug -- minutes:seconds used to not work.
16535            Fixed in later versions, but still avoid :seconds
16536            when seconds is 0. */
16537         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16538       } else {
16539         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16540                  seconds, inc/1000.);
16541       }
16542     }
16543     SendToProgram(buf, cps);
16544
16545     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16546     /* Orthogonally, limit search to given depth */
16547     if (sd > 0) {
16548       if (cps->sdKludge) {
16549         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16550       } else {
16551         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16552       }
16553       SendToProgram(buf, cps);
16554     }
16555
16556     if(cps->nps >= 0) { /* [HGM] nps */
16557         if(cps->supportsNPS == FALSE)
16558           cps->nps = -1; // don't use if engine explicitly says not supported!
16559         else {
16560           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16561           SendToProgram(buf, cps);
16562         }
16563     }
16564 }
16565
16566 ChessProgramState *
16567 WhitePlayer ()
16568 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16569 {
16570     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16571        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16572         return &second;
16573     return &first;
16574 }
16575
16576 void
16577 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16578 {
16579     char message[MSG_SIZ];
16580     long time, otime;
16581
16582     /* Note: this routine must be called when the clocks are stopped
16583        or when they have *just* been set or switched; otherwise
16584        it will be off by the time since the current tick started.
16585     */
16586     if (machineWhite) {
16587         time = whiteTimeRemaining / 10;
16588         otime = blackTimeRemaining / 10;
16589     } else {
16590         time = blackTimeRemaining / 10;
16591         otime = whiteTimeRemaining / 10;
16592     }
16593     /* [HGM] translate opponent's time by time-odds factor */
16594     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16595
16596     if (time <= 0) time = 1;
16597     if (otime <= 0) otime = 1;
16598
16599     snprintf(message, MSG_SIZ, "time %ld\n", time);
16600     SendToProgram(message, cps);
16601
16602     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16603     SendToProgram(message, cps);
16604 }
16605
16606 char *
16607 EngineDefinedVariant (ChessProgramState *cps, int n)
16608 {   // return name of n-th unknown variant that engine supports
16609     static char buf[MSG_SIZ];
16610     char *p, *s = cps->variants;
16611     if(!s) return NULL;
16612     do { // parse string from variants feature
16613       VariantClass v;
16614         p = strchr(s, ',');
16615         if(p) *p = NULLCHAR;
16616       v = StringToVariant(s);
16617       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16618         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16619             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16620                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16621                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16622                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16623             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16624         }
16625         if(p) *p++ = ',';
16626         if(n < 0) return buf;
16627     } while(s = p);
16628     return NULL;
16629 }
16630
16631 int
16632 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16633 {
16634   char buf[MSG_SIZ];
16635   int len = strlen(name);
16636   int val;
16637
16638   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16639     (*p) += len + 1;
16640     sscanf(*p, "%d", &val);
16641     *loc = (val != 0);
16642     while (**p && **p != ' ')
16643       (*p)++;
16644     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16645     SendToProgram(buf, cps);
16646     return TRUE;
16647   }
16648   return FALSE;
16649 }
16650
16651 int
16652 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16653 {
16654   char buf[MSG_SIZ];
16655   int len = strlen(name);
16656   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16657     (*p) += len + 1;
16658     sscanf(*p, "%d", loc);
16659     while (**p && **p != ' ') (*p)++;
16660     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16661     SendToProgram(buf, cps);
16662     return TRUE;
16663   }
16664   return FALSE;
16665 }
16666
16667 int
16668 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16669 {
16670   char buf[MSG_SIZ];
16671   int len = strlen(name);
16672   if (strncmp((*p), name, len) == 0
16673       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16674     (*p) += len + 2;
16675     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16676     sscanf(*p, "%[^\"]", *loc);
16677     while (**p && **p != '\"') (*p)++;
16678     if (**p == '\"') (*p)++;
16679     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16680     SendToProgram(buf, cps);
16681     return TRUE;
16682   }
16683   return FALSE;
16684 }
16685
16686 int
16687 ParseOption (Option *opt, ChessProgramState *cps)
16688 // [HGM] options: process the string that defines an engine option, and determine
16689 // name, type, default value, and allowed value range
16690 {
16691         char *p, *q, buf[MSG_SIZ];
16692         int n, min = (-1)<<31, max = 1<<31, def;
16693
16694         if(p = strstr(opt->name, " -spin ")) {
16695             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16696             if(max < min) max = min; // enforce consistency
16697             if(def < min) def = min;
16698             if(def > max) def = max;
16699             opt->value = def;
16700             opt->min = min;
16701             opt->max = max;
16702             opt->type = Spin;
16703         } else if((p = strstr(opt->name, " -slider "))) {
16704             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16705             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16706             if(max < min) max = min; // enforce consistency
16707             if(def < min) def = min;
16708             if(def > max) def = max;
16709             opt->value = def;
16710             opt->min = min;
16711             opt->max = max;
16712             opt->type = Spin; // Slider;
16713         } else if((p = strstr(opt->name, " -string "))) {
16714             opt->textValue = p+9;
16715             opt->type = TextBox;
16716         } else if((p = strstr(opt->name, " -file "))) {
16717             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16718             opt->textValue = p+7;
16719             opt->type = FileName; // FileName;
16720         } else if((p = strstr(opt->name, " -path "))) {
16721             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16722             opt->textValue = p+7;
16723             opt->type = PathName; // PathName;
16724         } else if(p = strstr(opt->name, " -check ")) {
16725             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16726             opt->value = (def != 0);
16727             opt->type = CheckBox;
16728         } else if(p = strstr(opt->name, " -combo ")) {
16729             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16730             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16731             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16732             opt->value = n = 0;
16733             while(q = StrStr(q, " /// ")) {
16734                 n++; *q = 0;    // count choices, and null-terminate each of them
16735                 q += 5;
16736                 if(*q == '*') { // remember default, which is marked with * prefix
16737                     q++;
16738                     opt->value = n;
16739                 }
16740                 cps->comboList[cps->comboCnt++] = q;
16741             }
16742             cps->comboList[cps->comboCnt++] = NULL;
16743             opt->max = n + 1;
16744             opt->type = ComboBox;
16745         } else if(p = strstr(opt->name, " -button")) {
16746             opt->type = Button;
16747         } else if(p = strstr(opt->name, " -save")) {
16748             opt->type = SaveButton;
16749         } else return FALSE;
16750         *p = 0; // terminate option name
16751         // now look if the command-line options define a setting for this engine option.
16752         if(cps->optionSettings && cps->optionSettings[0])
16753             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16754         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16755           snprintf(buf, MSG_SIZ, "option %s", p);
16756                 if(p = strstr(buf, ",")) *p = 0;
16757                 if(q = strchr(buf, '=')) switch(opt->type) {
16758                     case ComboBox:
16759                         for(n=0; n<opt->max; n++)
16760                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16761                         break;
16762                     case TextBox:
16763                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16764                         break;
16765                     case Spin:
16766                     case CheckBox:
16767                         opt->value = atoi(q+1);
16768                     default:
16769                         break;
16770                 }
16771                 strcat(buf, "\n");
16772                 SendToProgram(buf, cps);
16773         }
16774         return TRUE;
16775 }
16776
16777 void
16778 FeatureDone (ChessProgramState *cps, int val)
16779 {
16780   DelayedEventCallback cb = GetDelayedEvent();
16781   if ((cb == InitBackEnd3 && cps == &first) ||
16782       (cb == SettingsMenuIfReady && cps == &second) ||
16783       (cb == LoadEngine) ||
16784       (cb == TwoMachinesEventIfReady)) {
16785     CancelDelayedEvent();
16786     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16787   }
16788   cps->initDone = val;
16789   if(val) cps->reload = FALSE;
16790 }
16791
16792 /* Parse feature command from engine */
16793 void
16794 ParseFeatures (char *args, ChessProgramState *cps)
16795 {
16796   char *p = args;
16797   char *q = NULL;
16798   int val;
16799   char buf[MSG_SIZ];
16800
16801   for (;;) {
16802     while (*p == ' ') p++;
16803     if (*p == NULLCHAR) return;
16804
16805     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16806     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16807     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16808     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16809     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16810     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16811     if (BoolFeature(&p, "reuse", &val, cps)) {
16812       /* Engine can disable reuse, but can't enable it if user said no */
16813       if (!val) cps->reuse = FALSE;
16814       continue;
16815     }
16816     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16817     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16818       if (gameMode == TwoMachinesPlay) {
16819         DisplayTwoMachinesTitle();
16820       } else {
16821         DisplayTitle("");
16822       }
16823       continue;
16824     }
16825     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16826     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16827     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16828     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16829     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16830     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16831     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16832     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16833     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16834     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16835     if (IntFeature(&p, "done", &val, cps)) {
16836       FeatureDone(cps, val);
16837       continue;
16838     }
16839     /* Added by Tord: */
16840     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16841     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16842     /* End of additions by Tord */
16843
16844     /* [HGM] added features: */
16845     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16846     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16847     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16848     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16849     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16850     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16851     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16852     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16853         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16854         FREE(cps->option[cps->nrOptions].name);
16855         cps->option[cps->nrOptions].name = q; q = NULL;
16856         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16857           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16858             SendToProgram(buf, cps);
16859             continue;
16860         }
16861         if(cps->nrOptions >= MAX_OPTIONS) {
16862             cps->nrOptions--;
16863             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16864             DisplayError(buf, 0);
16865         }
16866         continue;
16867     }
16868     /* End of additions by HGM */
16869
16870     /* unknown feature: complain and skip */
16871     q = p;
16872     while (*q && *q != '=') q++;
16873     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16874     SendToProgram(buf, cps);
16875     p = q;
16876     if (*p == '=') {
16877       p++;
16878       if (*p == '\"') {
16879         p++;
16880         while (*p && *p != '\"') p++;
16881         if (*p == '\"') p++;
16882       } else {
16883         while (*p && *p != ' ') p++;
16884       }
16885     }
16886   }
16887
16888 }
16889
16890 void
16891 PeriodicUpdatesEvent (int newState)
16892 {
16893     if (newState == appData.periodicUpdates)
16894       return;
16895
16896     appData.periodicUpdates=newState;
16897
16898     /* Display type changes, so update it now */
16899 //    DisplayAnalysis();
16900
16901     /* Get the ball rolling again... */
16902     if (newState) {
16903         AnalysisPeriodicEvent(1);
16904         StartAnalysisClock();
16905     }
16906 }
16907
16908 void
16909 PonderNextMoveEvent (int newState)
16910 {
16911     if (newState == appData.ponderNextMove) return;
16912     if (gameMode == EditPosition) EditPositionDone(TRUE);
16913     if (newState) {
16914         SendToProgram("hard\n", &first);
16915         if (gameMode == TwoMachinesPlay) {
16916             SendToProgram("hard\n", &second);
16917         }
16918     } else {
16919         SendToProgram("easy\n", &first);
16920         thinkOutput[0] = NULLCHAR;
16921         if (gameMode == TwoMachinesPlay) {
16922             SendToProgram("easy\n", &second);
16923         }
16924     }
16925     appData.ponderNextMove = newState;
16926 }
16927
16928 void
16929 NewSettingEvent (int option, int *feature, char *command, int value)
16930 {
16931     char buf[MSG_SIZ];
16932
16933     if (gameMode == EditPosition) EditPositionDone(TRUE);
16934     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16935     if(feature == NULL || *feature) SendToProgram(buf, &first);
16936     if (gameMode == TwoMachinesPlay) {
16937         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16938     }
16939 }
16940
16941 void
16942 ShowThinkingEvent ()
16943 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16944 {
16945     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16946     int newState = appData.showThinking
16947         // [HGM] thinking: other features now need thinking output as well
16948         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16949
16950     if (oldState == newState) return;
16951     oldState = newState;
16952     if (gameMode == EditPosition) EditPositionDone(TRUE);
16953     if (oldState) {
16954         SendToProgram("post\n", &first);
16955         if (gameMode == TwoMachinesPlay) {
16956             SendToProgram("post\n", &second);
16957         }
16958     } else {
16959         SendToProgram("nopost\n", &first);
16960         thinkOutput[0] = NULLCHAR;
16961         if (gameMode == TwoMachinesPlay) {
16962             SendToProgram("nopost\n", &second);
16963         }
16964     }
16965 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16966 }
16967
16968 void
16969 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16970 {
16971   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16972   if (pr == NoProc) return;
16973   AskQuestion(title, question, replyPrefix, pr);
16974 }
16975
16976 void
16977 TypeInEvent (char firstChar)
16978 {
16979     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16980         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16981         gameMode == AnalyzeMode || gameMode == EditGame ||
16982         gameMode == EditPosition || gameMode == IcsExamining ||
16983         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16984         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16985                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16986                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16987         gameMode == Training) PopUpMoveDialog(firstChar);
16988 }
16989
16990 void
16991 TypeInDoneEvent (char *move)
16992 {
16993         Board board;
16994         int n, fromX, fromY, toX, toY;
16995         char promoChar;
16996         ChessMove moveType;
16997
16998         // [HGM] FENedit
16999         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17000                 EditPositionPasteFEN(move);
17001                 return;
17002         }
17003         // [HGM] movenum: allow move number to be typed in any mode
17004         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17005           ToNrEvent(2*n-1);
17006           return;
17007         }
17008         // undocumented kludge: allow command-line option to be typed in!
17009         // (potentially fatal, and does not implement the effect of the option.)
17010         // should only be used for options that are values on which future decisions will be made,
17011         // and definitely not on options that would be used during initialization.
17012         if(strstr(move, "!!! -") == move) {
17013             ParseArgsFromString(move+4);
17014             return;
17015         }
17016
17017       if (gameMode != EditGame && currentMove != forwardMostMove &&
17018         gameMode != Training) {
17019         DisplayMoveError(_("Displayed move is not current"));
17020       } else {
17021         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17022           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17023         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17024         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17025           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17026           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17027         } else {
17028           DisplayMoveError(_("Could not parse move"));
17029         }
17030       }
17031 }
17032
17033 void
17034 DisplayMove (int moveNumber)
17035 {
17036     char message[MSG_SIZ];
17037     char res[MSG_SIZ];
17038     char cpThinkOutput[MSG_SIZ];
17039
17040     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17041
17042     if (moveNumber == forwardMostMove - 1 ||
17043         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17044
17045         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17046
17047         if (strchr(cpThinkOutput, '\n')) {
17048             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17049         }
17050     } else {
17051         *cpThinkOutput = NULLCHAR;
17052     }
17053
17054     /* [AS] Hide thinking from human user */
17055     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17056         *cpThinkOutput = NULLCHAR;
17057         if( thinkOutput[0] != NULLCHAR ) {
17058             int i;
17059
17060             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17061                 cpThinkOutput[i] = '.';
17062             }
17063             cpThinkOutput[i] = NULLCHAR;
17064             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17065         }
17066     }
17067
17068     if (moveNumber == forwardMostMove - 1 &&
17069         gameInfo.resultDetails != NULL) {
17070         if (gameInfo.resultDetails[0] == NULLCHAR) {
17071           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17072         } else {
17073           snprintf(res, MSG_SIZ, " {%s} %s",
17074                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17075         }
17076     } else {
17077         res[0] = NULLCHAR;
17078     }
17079
17080     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17081         DisplayMessage(res, cpThinkOutput);
17082     } else {
17083       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17084                 WhiteOnMove(moveNumber) ? " " : ".. ",
17085                 parseList[moveNumber], res);
17086         DisplayMessage(message, cpThinkOutput);
17087     }
17088 }
17089
17090 void
17091 DisplayComment (int moveNumber, char *text)
17092 {
17093     char title[MSG_SIZ];
17094
17095     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17096       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17097     } else {
17098       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17099               WhiteOnMove(moveNumber) ? " " : ".. ",
17100               parseList[moveNumber]);
17101     }
17102     if (text != NULL && (appData.autoDisplayComment || commentUp))
17103         CommentPopUp(title, text);
17104 }
17105
17106 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17107  * might be busy thinking or pondering.  It can be omitted if your
17108  * gnuchess is configured to stop thinking immediately on any user
17109  * input.  However, that gnuchess feature depends on the FIONREAD
17110  * ioctl, which does not work properly on some flavors of Unix.
17111  */
17112 void
17113 Attention (ChessProgramState *cps)
17114 {
17115 #if ATTENTION
17116     if (!cps->useSigint) return;
17117     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17118     switch (gameMode) {
17119       case MachinePlaysWhite:
17120       case MachinePlaysBlack:
17121       case TwoMachinesPlay:
17122       case IcsPlayingWhite:
17123       case IcsPlayingBlack:
17124       case AnalyzeMode:
17125       case AnalyzeFile:
17126         /* Skip if we know it isn't thinking */
17127         if (!cps->maybeThinking) return;
17128         if (appData.debugMode)
17129           fprintf(debugFP, "Interrupting %s\n", cps->which);
17130         InterruptChildProcess(cps->pr);
17131         cps->maybeThinking = FALSE;
17132         break;
17133       default:
17134         break;
17135     }
17136 #endif /*ATTENTION*/
17137 }
17138
17139 int
17140 CheckFlags ()
17141 {
17142     if (whiteTimeRemaining <= 0) {
17143         if (!whiteFlag) {
17144             whiteFlag = TRUE;
17145             if (appData.icsActive) {
17146                 if (appData.autoCallFlag &&
17147                     gameMode == IcsPlayingBlack && !blackFlag) {
17148                   SendToICS(ics_prefix);
17149                   SendToICS("flag\n");
17150                 }
17151             } else {
17152                 if (blackFlag) {
17153                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17154                 } else {
17155                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17156                     if (appData.autoCallFlag) {
17157                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17158                         return TRUE;
17159                     }
17160                 }
17161             }
17162         }
17163     }
17164     if (blackTimeRemaining <= 0) {
17165         if (!blackFlag) {
17166             blackFlag = TRUE;
17167             if (appData.icsActive) {
17168                 if (appData.autoCallFlag &&
17169                     gameMode == IcsPlayingWhite && !whiteFlag) {
17170                   SendToICS(ics_prefix);
17171                   SendToICS("flag\n");
17172                 }
17173             } else {
17174                 if (whiteFlag) {
17175                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17176                 } else {
17177                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17178                     if (appData.autoCallFlag) {
17179                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17180                         return TRUE;
17181                     }
17182                 }
17183             }
17184         }
17185     }
17186     return FALSE;
17187 }
17188
17189 void
17190 CheckTimeControl ()
17191 {
17192     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17193         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17194
17195     /*
17196      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17197      */
17198     if ( !WhiteOnMove(forwardMostMove) ) {
17199         /* White made time control */
17200         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17201         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17202         /* [HGM] time odds: correct new time quota for time odds! */
17203                                             / WhitePlayer()->timeOdds;
17204         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17205     } else {
17206         lastBlack -= blackTimeRemaining;
17207         /* Black made time control */
17208         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17209                                             / WhitePlayer()->other->timeOdds;
17210         lastWhite = whiteTimeRemaining;
17211     }
17212 }
17213
17214 void
17215 DisplayBothClocks ()
17216 {
17217     int wom = gameMode == EditPosition ?
17218       !blackPlaysFirst : WhiteOnMove(currentMove);
17219     DisplayWhiteClock(whiteTimeRemaining, wom);
17220     DisplayBlackClock(blackTimeRemaining, !wom);
17221 }
17222
17223
17224 /* Timekeeping seems to be a portability nightmare.  I think everyone
17225    has ftime(), but I'm really not sure, so I'm including some ifdefs
17226    to use other calls if you don't.  Clocks will be less accurate if
17227    you have neither ftime nor gettimeofday.
17228 */
17229
17230 /* VS 2008 requires the #include outside of the function */
17231 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17232 #include <sys/timeb.h>
17233 #endif
17234
17235 /* Get the current time as a TimeMark */
17236 void
17237 GetTimeMark (TimeMark *tm)
17238 {
17239 #if HAVE_GETTIMEOFDAY
17240
17241     struct timeval timeVal;
17242     struct timezone timeZone;
17243
17244     gettimeofday(&timeVal, &timeZone);
17245     tm->sec = (long) timeVal.tv_sec;
17246     tm->ms = (int) (timeVal.tv_usec / 1000L);
17247
17248 #else /*!HAVE_GETTIMEOFDAY*/
17249 #if HAVE_FTIME
17250
17251 // include <sys/timeb.h> / moved to just above start of function
17252     struct timeb timeB;
17253
17254     ftime(&timeB);
17255     tm->sec = (long) timeB.time;
17256     tm->ms = (int) timeB.millitm;
17257
17258 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17259     tm->sec = (long) time(NULL);
17260     tm->ms = 0;
17261 #endif
17262 #endif
17263 }
17264
17265 /* Return the difference in milliseconds between two
17266    time marks.  We assume the difference will fit in a long!
17267 */
17268 long
17269 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17270 {
17271     return 1000L*(tm2->sec - tm1->sec) +
17272            (long) (tm2->ms - tm1->ms);
17273 }
17274
17275
17276 /*
17277  * Code to manage the game clocks.
17278  *
17279  * In tournament play, black starts the clock and then white makes a move.
17280  * We give the human user a slight advantage if he is playing white---the
17281  * clocks don't run until he makes his first move, so it takes zero time.
17282  * Also, we don't account for network lag, so we could get out of sync
17283  * with GNU Chess's clock -- but then, referees are always right.
17284  */
17285
17286 static TimeMark tickStartTM;
17287 static long intendedTickLength;
17288
17289 long
17290 NextTickLength (long timeRemaining)
17291 {
17292     long nominalTickLength, nextTickLength;
17293
17294     if (timeRemaining > 0L && timeRemaining <= 10000L)
17295       nominalTickLength = 100L;
17296     else
17297       nominalTickLength = 1000L;
17298     nextTickLength = timeRemaining % nominalTickLength;
17299     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17300
17301     return nextTickLength;
17302 }
17303
17304 /* Adjust clock one minute up or down */
17305 void
17306 AdjustClock (Boolean which, int dir)
17307 {
17308     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17309     if(which) blackTimeRemaining += 60000*dir;
17310     else      whiteTimeRemaining += 60000*dir;
17311     DisplayBothClocks();
17312     adjustedClock = TRUE;
17313 }
17314
17315 /* Stop clocks and reset to a fresh time control */
17316 void
17317 ResetClocks ()
17318 {
17319     (void) StopClockTimer();
17320     if (appData.icsActive) {
17321         whiteTimeRemaining = blackTimeRemaining = 0;
17322     } else if (searchTime) {
17323         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17324         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17325     } else { /* [HGM] correct new time quote for time odds */
17326         whiteTC = blackTC = fullTimeControlString;
17327         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17328         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17329     }
17330     if (whiteFlag || blackFlag) {
17331         DisplayTitle("");
17332         whiteFlag = blackFlag = FALSE;
17333     }
17334     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17335     DisplayBothClocks();
17336     adjustedClock = FALSE;
17337 }
17338
17339 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17340
17341 /* Decrement running clock by amount of time that has passed */
17342 void
17343 DecrementClocks ()
17344 {
17345     long timeRemaining;
17346     long lastTickLength, fudge;
17347     TimeMark now;
17348
17349     if (!appData.clockMode) return;
17350     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17351
17352     GetTimeMark(&now);
17353
17354     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17355
17356     /* Fudge if we woke up a little too soon */
17357     fudge = intendedTickLength - lastTickLength;
17358     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17359
17360     if (WhiteOnMove(forwardMostMove)) {
17361         if(whiteNPS >= 0) lastTickLength = 0;
17362         timeRemaining = whiteTimeRemaining -= lastTickLength;
17363         if(timeRemaining < 0 && !appData.icsActive) {
17364             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17365             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17366                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17367                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17368             }
17369         }
17370         DisplayWhiteClock(whiteTimeRemaining - fudge,
17371                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17372     } else {
17373         if(blackNPS >= 0) lastTickLength = 0;
17374         timeRemaining = blackTimeRemaining -= lastTickLength;
17375         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17376             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17377             if(suddenDeath) {
17378                 blackStartMove = forwardMostMove;
17379                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17380             }
17381         }
17382         DisplayBlackClock(blackTimeRemaining - fudge,
17383                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17384     }
17385     if (CheckFlags()) return;
17386
17387     if(twoBoards) { // count down secondary board's clocks as well
17388         activePartnerTime -= lastTickLength;
17389         partnerUp = 1;
17390         if(activePartner == 'W')
17391             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17392         else
17393             DisplayBlackClock(activePartnerTime, TRUE);
17394         partnerUp = 0;
17395     }
17396
17397     tickStartTM = now;
17398     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17399     StartClockTimer(intendedTickLength);
17400
17401     /* if the time remaining has fallen below the alarm threshold, sound the
17402      * alarm. if the alarm has sounded and (due to a takeback or time control
17403      * with increment) the time remaining has increased to a level above the
17404      * threshold, reset the alarm so it can sound again.
17405      */
17406
17407     if (appData.icsActive && appData.icsAlarm) {
17408
17409         /* make sure we are dealing with the user's clock */
17410         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17411                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17412            )) return;
17413
17414         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17415             alarmSounded = FALSE;
17416         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17417             PlayAlarmSound();
17418             alarmSounded = TRUE;
17419         }
17420     }
17421 }
17422
17423
17424 /* A player has just moved, so stop the previously running
17425    clock and (if in clock mode) start the other one.
17426    We redisplay both clocks in case we're in ICS mode, because
17427    ICS gives us an update to both clocks after every move.
17428    Note that this routine is called *after* forwardMostMove
17429    is updated, so the last fractional tick must be subtracted
17430    from the color that is *not* on move now.
17431 */
17432 void
17433 SwitchClocks (int newMoveNr)
17434 {
17435     long lastTickLength;
17436     TimeMark now;
17437     int flagged = FALSE;
17438
17439     GetTimeMark(&now);
17440
17441     if (StopClockTimer() && appData.clockMode) {
17442         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17443         if (!WhiteOnMove(forwardMostMove)) {
17444             if(blackNPS >= 0) lastTickLength = 0;
17445             blackTimeRemaining -= lastTickLength;
17446            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17447 //         if(pvInfoList[forwardMostMove].time == -1)
17448                  pvInfoList[forwardMostMove].time =               // use GUI time
17449                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17450         } else {
17451            if(whiteNPS >= 0) lastTickLength = 0;
17452            whiteTimeRemaining -= lastTickLength;
17453            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17454 //         if(pvInfoList[forwardMostMove].time == -1)
17455                  pvInfoList[forwardMostMove].time =
17456                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17457         }
17458         flagged = CheckFlags();
17459     }
17460     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17461     CheckTimeControl();
17462
17463     if (flagged || !appData.clockMode) return;
17464
17465     switch (gameMode) {
17466       case MachinePlaysBlack:
17467       case MachinePlaysWhite:
17468       case BeginningOfGame:
17469         if (pausing) return;
17470         break;
17471
17472       case EditGame:
17473       case PlayFromGameFile:
17474       case IcsExamining:
17475         return;
17476
17477       default:
17478         break;
17479     }
17480
17481     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17482         if(WhiteOnMove(forwardMostMove))
17483              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17484         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17485     }
17486
17487     tickStartTM = now;
17488     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17489       whiteTimeRemaining : blackTimeRemaining);
17490     StartClockTimer(intendedTickLength);
17491 }
17492
17493
17494 /* Stop both clocks */
17495 void
17496 StopClocks ()
17497 {
17498     long lastTickLength;
17499     TimeMark now;
17500
17501     if (!StopClockTimer()) return;
17502     if (!appData.clockMode) return;
17503
17504     GetTimeMark(&now);
17505
17506     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17507     if (WhiteOnMove(forwardMostMove)) {
17508         if(whiteNPS >= 0) lastTickLength = 0;
17509         whiteTimeRemaining -= lastTickLength;
17510         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17511     } else {
17512         if(blackNPS >= 0) lastTickLength = 0;
17513         blackTimeRemaining -= lastTickLength;
17514         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17515     }
17516     CheckFlags();
17517 }
17518
17519 /* Start clock of player on move.  Time may have been reset, so
17520    if clock is already running, stop and restart it. */
17521 void
17522 StartClocks ()
17523 {
17524     (void) StopClockTimer(); /* in case it was running already */
17525     DisplayBothClocks();
17526     if (CheckFlags()) return;
17527
17528     if (!appData.clockMode) return;
17529     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17530
17531     GetTimeMark(&tickStartTM);
17532     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17533       whiteTimeRemaining : blackTimeRemaining);
17534
17535    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17536     whiteNPS = blackNPS = -1;
17537     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17538        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17539         whiteNPS = first.nps;
17540     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17541        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17542         blackNPS = first.nps;
17543     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17544         whiteNPS = second.nps;
17545     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17546         blackNPS = second.nps;
17547     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17548
17549     StartClockTimer(intendedTickLength);
17550 }
17551
17552 char *
17553 TimeString (long ms)
17554 {
17555     long second, minute, hour, day;
17556     char *sign = "";
17557     static char buf[32];
17558
17559     if (ms > 0 && ms <= 9900) {
17560       /* convert milliseconds to tenths, rounding up */
17561       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17562
17563       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17564       return buf;
17565     }
17566
17567     /* convert milliseconds to seconds, rounding up */
17568     /* use floating point to avoid strangeness of integer division
17569        with negative dividends on many machines */
17570     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17571
17572     if (second < 0) {
17573         sign = "-";
17574         second = -second;
17575     }
17576
17577     day = second / (60 * 60 * 24);
17578     second = second % (60 * 60 * 24);
17579     hour = second / (60 * 60);
17580     second = second % (60 * 60);
17581     minute = second / 60;
17582     second = second % 60;
17583
17584     if (day > 0)
17585       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17586               sign, day, hour, minute, second);
17587     else if (hour > 0)
17588       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17589     else
17590       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17591
17592     return buf;
17593 }
17594
17595
17596 /*
17597  * This is necessary because some C libraries aren't ANSI C compliant yet.
17598  */
17599 char *
17600 StrStr (char *string, char *match)
17601 {
17602     int i, length;
17603
17604     length = strlen(match);
17605
17606     for (i = strlen(string) - length; i >= 0; i--, string++)
17607       if (!strncmp(match, string, length))
17608         return string;
17609
17610     return NULL;
17611 }
17612
17613 char *
17614 StrCaseStr (char *string, char *match)
17615 {
17616     int i, j, length;
17617
17618     length = strlen(match);
17619
17620     for (i = strlen(string) - length; i >= 0; i--, string++) {
17621         for (j = 0; j < length; j++) {
17622             if (ToLower(match[j]) != ToLower(string[j]))
17623               break;
17624         }
17625         if (j == length) return string;
17626     }
17627
17628     return NULL;
17629 }
17630
17631 #ifndef _amigados
17632 int
17633 StrCaseCmp (char *s1, char *s2)
17634 {
17635     char c1, c2;
17636
17637     for (;;) {
17638         c1 = ToLower(*s1++);
17639         c2 = ToLower(*s2++);
17640         if (c1 > c2) return 1;
17641         if (c1 < c2) return -1;
17642         if (c1 == NULLCHAR) return 0;
17643     }
17644 }
17645
17646
17647 int
17648 ToLower (int c)
17649 {
17650     return isupper(c) ? tolower(c) : c;
17651 }
17652
17653
17654 int
17655 ToUpper (int c)
17656 {
17657     return islower(c) ? toupper(c) : c;
17658 }
17659 #endif /* !_amigados    */
17660
17661 char *
17662 StrSave (char *s)
17663 {
17664   char *ret;
17665
17666   if ((ret = (char *) malloc(strlen(s) + 1)))
17667     {
17668       safeStrCpy(ret, s, strlen(s)+1);
17669     }
17670   return ret;
17671 }
17672
17673 char *
17674 StrSavePtr (char *s, char **savePtr)
17675 {
17676     if (*savePtr) {
17677         free(*savePtr);
17678     }
17679     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17680       safeStrCpy(*savePtr, s, strlen(s)+1);
17681     }
17682     return(*savePtr);
17683 }
17684
17685 char *
17686 PGNDate ()
17687 {
17688     time_t clock;
17689     struct tm *tm;
17690     char buf[MSG_SIZ];
17691
17692     clock = time((time_t *)NULL);
17693     tm = localtime(&clock);
17694     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17695             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17696     return StrSave(buf);
17697 }
17698
17699
17700 char *
17701 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17702 {
17703     int i, j, fromX, fromY, toX, toY;
17704     int whiteToPlay;
17705     char buf[MSG_SIZ];
17706     char *p, *q;
17707     int emptycount;
17708     ChessSquare piece;
17709
17710     whiteToPlay = (gameMode == EditPosition) ?
17711       !blackPlaysFirst : (move % 2 == 0);
17712     p = buf;
17713
17714     /* Piece placement data */
17715     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17716         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17717         emptycount = 0;
17718         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17719             if (boards[move][i][j] == EmptySquare) {
17720                 emptycount++;
17721             } else { ChessSquare piece = boards[move][i][j];
17722                 if (emptycount > 0) {
17723                     if(emptycount<10) /* [HGM] can be >= 10 */
17724                         *p++ = '0' + emptycount;
17725                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17726                     emptycount = 0;
17727                 }
17728                 if(PieceToChar(piece) == '+') {
17729                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17730                     *p++ = '+';
17731                     piece = (ChessSquare)(CHUDEMOTED piece);
17732                 }
17733                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17734                 if(p[-1] == '~') {
17735                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17736                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17737                     *p++ = '~';
17738                 }
17739             }
17740         }
17741         if (emptycount > 0) {
17742             if(emptycount<10) /* [HGM] can be >= 10 */
17743                 *p++ = '0' + emptycount;
17744             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17745             emptycount = 0;
17746         }
17747         *p++ = '/';
17748     }
17749     *(p - 1) = ' ';
17750
17751     /* [HGM] print Crazyhouse or Shogi holdings */
17752     if( gameInfo.holdingsWidth ) {
17753         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17754         q = p;
17755         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17756             piece = boards[move][i][BOARD_WIDTH-1];
17757             if( piece != EmptySquare )
17758               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17759                   *p++ = PieceToChar(piece);
17760         }
17761         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17762             piece = boards[move][BOARD_HEIGHT-i-1][0];
17763             if( piece != EmptySquare )
17764               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17765                   *p++ = PieceToChar(piece);
17766         }
17767
17768         if( q == p ) *p++ = '-';
17769         *p++ = ']';
17770         *p++ = ' ';
17771     }
17772
17773     /* Active color */
17774     *p++ = whiteToPlay ? 'w' : 'b';
17775     *p++ = ' ';
17776
17777   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17778     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17779   } else {
17780   if(nrCastlingRights) {
17781      q = p;
17782      if(appData.fischerCastling) {
17783        /* [HGM] write directly from rights */
17784            if(boards[move][CASTLING][2] != NoRights &&
17785               boards[move][CASTLING][0] != NoRights   )
17786                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17787            if(boards[move][CASTLING][2] != NoRights &&
17788               boards[move][CASTLING][1] != NoRights   )
17789                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17790            if(boards[move][CASTLING][5] != NoRights &&
17791               boards[move][CASTLING][3] != NoRights   )
17792                 *p++ = boards[move][CASTLING][3] + AAA;
17793            if(boards[move][CASTLING][5] != NoRights &&
17794               boards[move][CASTLING][4] != NoRights   )
17795                 *p++ = boards[move][CASTLING][4] + AAA;
17796      } else {
17797
17798         /* [HGM] write true castling rights */
17799         if( nrCastlingRights == 6 ) {
17800             int q, k=0;
17801             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17802                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17803             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17804                  boards[move][CASTLING][2] != NoRights  );
17805             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17806                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17807                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17808                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17809                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17810             }
17811             if(q) *p++ = 'Q';
17812             k = 0;
17813             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17814                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17815             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17816                  boards[move][CASTLING][5] != NoRights  );
17817             if(gameInfo.variant == VariantSChess) {
17818                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17819                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17820                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17821                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17822             }
17823             if(q) *p++ = 'q';
17824         }
17825      }
17826      if (q == p) *p++ = '-'; /* No castling rights */
17827      *p++ = ' ';
17828   }
17829
17830   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17831      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17832      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17833     /* En passant target square */
17834     if (move > backwardMostMove) {
17835         fromX = moveList[move - 1][0] - AAA;
17836         fromY = moveList[move - 1][1] - ONE;
17837         toX = moveList[move - 1][2] - AAA;
17838         toY = moveList[move - 1][3] - ONE;
17839         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17840             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17841             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17842             fromX == toX) {
17843             /* 2-square pawn move just happened */
17844             *p++ = toX + AAA;
17845             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17846         } else {
17847             *p++ = '-';
17848         }
17849     } else if(move == backwardMostMove) {
17850         // [HGM] perhaps we should always do it like this, and forget the above?
17851         if((signed char)boards[move][EP_STATUS] >= 0) {
17852             *p++ = boards[move][EP_STATUS] + AAA;
17853             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17854         } else {
17855             *p++ = '-';
17856         }
17857     } else {
17858         *p++ = '-';
17859     }
17860     *p++ = ' ';
17861   }
17862   }
17863
17864     if(moveCounts)
17865     {   int i = 0, j=move;
17866
17867         /* [HGM] find reversible plies */
17868         if (appData.debugMode) { int k;
17869             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17870             for(k=backwardMostMove; k<=forwardMostMove; k++)
17871                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17872
17873         }
17874
17875         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17876         if( j == backwardMostMove ) i += initialRulePlies;
17877         sprintf(p, "%d ", i);
17878         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17879
17880         /* Fullmove number */
17881         sprintf(p, "%d", (move / 2) + 1);
17882     } else *--p = NULLCHAR;
17883
17884     return StrSave(buf);
17885 }
17886
17887 Boolean
17888 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17889 {
17890     int i, j, k, w=0, subst=0, shuffle=0;
17891     char *p, c;
17892     int emptycount, virgin[BOARD_FILES];
17893     ChessSquare piece;
17894
17895     p = fen;
17896
17897     /* Piece placement data */
17898     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17899         j = 0;
17900         for (;;) {
17901             if (*p == '/' || *p == ' ' || *p == '[' ) {
17902                 if(j > w) w = j;
17903                 emptycount = gameInfo.boardWidth - j;
17904                 while (emptycount--)
17905                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17906                 if (*p == '/') p++;
17907                 else if(autoSize) { // we stumbled unexpectedly into end of board
17908                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17909                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17910                     }
17911                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17912                 }
17913                 break;
17914 #if(BOARD_FILES >= 10)*0
17915             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17916                 p++; emptycount=10;
17917                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17918                 while (emptycount--)
17919                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17920 #endif
17921             } else if (*p == '*') {
17922                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17923             } else if (isdigit(*p)) {
17924                 emptycount = *p++ - '0';
17925                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17926                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17927                 while (emptycount--)
17928                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17929             } else if (*p == '<') {
17930                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17931                 else if (i != 0 || !shuffle) return FALSE;
17932                 p++;
17933             } else if (shuffle && *p == '>') {
17934                 p++; // for now ignore closing shuffle range, and assume rank-end
17935             } else if (*p == '?') {
17936                 if (j >= gameInfo.boardWidth) return FALSE;
17937                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17938                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17939             } else if (*p == '+' || isalpha(*p)) {
17940                 if (j >= gameInfo.boardWidth) return FALSE;
17941                 if(*p=='+') {
17942                     piece = CharToPiece(*++p);
17943                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17944                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17945                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17946                 } else piece = CharToPiece(*p++);
17947
17948                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17949                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17950                     piece = (ChessSquare) (PROMOTED piece);
17951                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17952                     p++;
17953                 }
17954                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17955             } else {
17956                 return FALSE;
17957             }
17958         }
17959     }
17960     while (*p == '/' || *p == ' ') p++;
17961
17962     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17963
17964     /* [HGM] by default clear Crazyhouse holdings, if present */
17965     if(gameInfo.holdingsWidth) {
17966        for(i=0; i<BOARD_HEIGHT; i++) {
17967            board[i][0]             = EmptySquare; /* black holdings */
17968            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17969            board[i][1]             = (ChessSquare) 0; /* black counts */
17970            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17971        }
17972     }
17973
17974     /* [HGM] look for Crazyhouse holdings here */
17975     while(*p==' ') p++;
17976     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17977         int swap=0, wcnt=0, bcnt=0;
17978         if(*p == '[') p++;
17979         if(*p == '<') swap++, p++;
17980         if(*p == '-' ) p++; /* empty holdings */ else {
17981             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17982             /* if we would allow FEN reading to set board size, we would   */
17983             /* have to add holdings and shift the board read so far here   */
17984             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17985                 p++;
17986                 if((int) piece >= (int) BlackPawn ) {
17987                     i = (int)piece - (int)BlackPawn;
17988                     i = PieceToNumber((ChessSquare)i);
17989                     if( i >= gameInfo.holdingsSize ) return FALSE;
17990                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17991                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17992                     bcnt++;
17993                 } else {
17994                     i = (int)piece - (int)WhitePawn;
17995                     i = PieceToNumber((ChessSquare)i);
17996                     if( i >= gameInfo.holdingsSize ) return FALSE;
17997                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17998                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17999                     wcnt++;
18000                 }
18001             }
18002             if(subst) { // substitute back-rank question marks by holdings pieces
18003                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18004                     int k, m, n = bcnt + 1;
18005                     if(board[0][j] == ClearBoard) {
18006                         if(!wcnt) return FALSE;
18007                         n = rand() % wcnt;
18008                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18009                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18010                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18011                             break;
18012                         }
18013                     }
18014                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18015                         if(!bcnt) return FALSE;
18016                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18017                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18018                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18019                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18020                             break;
18021                         }
18022                     }
18023                 }
18024                 subst = 0;
18025             }
18026         }
18027         if(*p == ']') p++;
18028     }
18029
18030     if(subst) return FALSE; // substitution requested, but no holdings
18031
18032     while(*p == ' ') p++;
18033
18034     /* Active color */
18035     c = *p++;
18036     if(appData.colorNickNames) {
18037       if( c == appData.colorNickNames[0] ) c = 'w'; else
18038       if( c == appData.colorNickNames[1] ) c = 'b';
18039     }
18040     switch (c) {
18041       case 'w':
18042         *blackPlaysFirst = FALSE;
18043         break;
18044       case 'b':
18045         *blackPlaysFirst = TRUE;
18046         break;
18047       default:
18048         return FALSE;
18049     }
18050
18051     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18052     /* return the extra info in global variiables             */
18053
18054     /* set defaults in case FEN is incomplete */
18055     board[EP_STATUS] = EP_UNKNOWN;
18056     for(i=0; i<nrCastlingRights; i++ ) {
18057         board[CASTLING][i] =
18058             appData.fischerCastling ? NoRights : initialRights[i];
18059     }   /* assume possible unless obviously impossible */
18060     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18061     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18062     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18063                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18064     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18065     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18066     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18067                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18068     FENrulePlies = 0;
18069
18070     while(*p==' ') p++;
18071     if(nrCastlingRights) {
18072       int fischer = 0;
18073       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18074       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18075           /* castling indicator present, so default becomes no castlings */
18076           for(i=0; i<nrCastlingRights; i++ ) {
18077                  board[CASTLING][i] = NoRights;
18078           }
18079       }
18080       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18081              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18082              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18083              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18084         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18085
18086         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18087             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18088             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18089         }
18090         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18091             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18092         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18093                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18094         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18095                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18096         switch(c) {
18097           case'K':
18098               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18099               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18100               board[CASTLING][2] = whiteKingFile;
18101               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18102               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18103               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18104               break;
18105           case'Q':
18106               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18107               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18108               board[CASTLING][2] = whiteKingFile;
18109               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18110               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18111               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18112               break;
18113           case'k':
18114               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18115               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18116               board[CASTLING][5] = blackKingFile;
18117               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18118               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18119               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18120               break;
18121           case'q':
18122               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18123               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18124               board[CASTLING][5] = blackKingFile;
18125               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18126               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18127               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18128           case '-':
18129               break;
18130           default: /* FRC castlings */
18131               if(c >= 'a') { /* black rights */
18132                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18133                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18134                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18135                   if(i == BOARD_RGHT) break;
18136                   board[CASTLING][5] = i;
18137                   c -= AAA;
18138                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18139                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18140                   if(c > i)
18141                       board[CASTLING][3] = c;
18142                   else
18143                       board[CASTLING][4] = c;
18144               } else { /* white rights */
18145                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18146                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18147                     if(board[0][i] == WhiteKing) break;
18148                   if(i == BOARD_RGHT) break;
18149                   board[CASTLING][2] = i;
18150                   c -= AAA - 'a' + 'A';
18151                   if(board[0][c] >= WhiteKing) break;
18152                   if(c > i)
18153                       board[CASTLING][0] = c;
18154                   else
18155                       board[CASTLING][1] = c;
18156               }
18157         }
18158       }
18159       for(i=0; i<nrCastlingRights; i++)
18160         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18161       if(gameInfo.variant == VariantSChess)
18162         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18163       if(fischer && shuffle) appData.fischerCastling = TRUE;
18164     if (appData.debugMode) {
18165         fprintf(debugFP, "FEN castling rights:");
18166         for(i=0; i<nrCastlingRights; i++)
18167         fprintf(debugFP, " %d", board[CASTLING][i]);
18168         fprintf(debugFP, "\n");
18169     }
18170
18171       while(*p==' ') p++;
18172     }
18173
18174     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18175
18176     /* read e.p. field in games that know e.p. capture */
18177     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18178        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18179        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18180       if(*p=='-') {
18181         p++; board[EP_STATUS] = EP_NONE;
18182       } else {
18183          char c = *p++ - AAA;
18184
18185          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18186          if(*p >= '0' && *p <='9') p++;
18187          board[EP_STATUS] = c;
18188       }
18189     }
18190
18191
18192     if(sscanf(p, "%d", &i) == 1) {
18193         FENrulePlies = i; /* 50-move ply counter */
18194         /* (The move number is still ignored)    */
18195     }
18196
18197     return TRUE;
18198 }
18199
18200 void
18201 EditPositionPasteFEN (char *fen)
18202 {
18203   if (fen != NULL) {
18204     Board initial_position;
18205
18206     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18207       DisplayError(_("Bad FEN position in clipboard"), 0);
18208       return ;
18209     } else {
18210       int savedBlackPlaysFirst = blackPlaysFirst;
18211       EditPositionEvent();
18212       blackPlaysFirst = savedBlackPlaysFirst;
18213       CopyBoard(boards[0], initial_position);
18214       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18215       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18216       DisplayBothClocks();
18217       DrawPosition(FALSE, boards[currentMove]);
18218     }
18219   }
18220 }
18221
18222 static char cseq[12] = "\\   ";
18223
18224 Boolean
18225 set_cont_sequence (char *new_seq)
18226 {
18227     int len;
18228     Boolean ret;
18229
18230     // handle bad attempts to set the sequence
18231         if (!new_seq)
18232                 return 0; // acceptable error - no debug
18233
18234     len = strlen(new_seq);
18235     ret = (len > 0) && (len < sizeof(cseq));
18236     if (ret)
18237       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18238     else if (appData.debugMode)
18239       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18240     return ret;
18241 }
18242
18243 /*
18244     reformat a source message so words don't cross the width boundary.  internal
18245     newlines are not removed.  returns the wrapped size (no null character unless
18246     included in source message).  If dest is NULL, only calculate the size required
18247     for the dest buffer.  lp argument indicats line position upon entry, and it's
18248     passed back upon exit.
18249 */
18250 int
18251 wrap (char *dest, char *src, int count, int width, int *lp)
18252 {
18253     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18254
18255     cseq_len = strlen(cseq);
18256     old_line = line = *lp;
18257     ansi = len = clen = 0;
18258
18259     for (i=0; i < count; i++)
18260     {
18261         if (src[i] == '\033')
18262             ansi = 1;
18263
18264         // if we hit the width, back up
18265         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18266         {
18267             // store i & len in case the word is too long
18268             old_i = i, old_len = len;
18269
18270             // find the end of the last word
18271             while (i && src[i] != ' ' && src[i] != '\n')
18272             {
18273                 i--;
18274                 len--;
18275             }
18276
18277             // word too long?  restore i & len before splitting it
18278             if ((old_i-i+clen) >= width)
18279             {
18280                 i = old_i;
18281                 len = old_len;
18282             }
18283
18284             // extra space?
18285             if (i && src[i-1] == ' ')
18286                 len--;
18287
18288             if (src[i] != ' ' && src[i] != '\n')
18289             {
18290                 i--;
18291                 if (len)
18292                     len--;
18293             }
18294
18295             // now append the newline and continuation sequence
18296             if (dest)
18297                 dest[len] = '\n';
18298             len++;
18299             if (dest)
18300                 strncpy(dest+len, cseq, cseq_len);
18301             len += cseq_len;
18302             line = cseq_len;
18303             clen = cseq_len;
18304             continue;
18305         }
18306
18307         if (dest)
18308             dest[len] = src[i];
18309         len++;
18310         if (!ansi)
18311             line++;
18312         if (src[i] == '\n')
18313             line = 0;
18314         if (src[i] == 'm')
18315             ansi = 0;
18316     }
18317     if (dest && appData.debugMode)
18318     {
18319         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18320             count, width, line, len, *lp);
18321         show_bytes(debugFP, src, count);
18322         fprintf(debugFP, "\ndest: ");
18323         show_bytes(debugFP, dest, len);
18324         fprintf(debugFP, "\n");
18325     }
18326     *lp = dest ? line : old_line;
18327
18328     return len;
18329 }
18330
18331 // [HGM] vari: routines for shelving variations
18332 Boolean modeRestore = FALSE;
18333
18334 void
18335 PushInner (int firstMove, int lastMove)
18336 {
18337         int i, j, nrMoves = lastMove - firstMove;
18338
18339         // push current tail of game on stack
18340         savedResult[storedGames] = gameInfo.result;
18341         savedDetails[storedGames] = gameInfo.resultDetails;
18342         gameInfo.resultDetails = NULL;
18343         savedFirst[storedGames] = firstMove;
18344         savedLast [storedGames] = lastMove;
18345         savedFramePtr[storedGames] = framePtr;
18346         framePtr -= nrMoves; // reserve space for the boards
18347         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18348             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18349             for(j=0; j<MOVE_LEN; j++)
18350                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18351             for(j=0; j<2*MOVE_LEN; j++)
18352                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18353             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18354             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18355             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18356             pvInfoList[firstMove+i-1].depth = 0;
18357             commentList[framePtr+i] = commentList[firstMove+i];
18358             commentList[firstMove+i] = NULL;
18359         }
18360
18361         storedGames++;
18362         forwardMostMove = firstMove; // truncate game so we can start variation
18363 }
18364
18365 void
18366 PushTail (int firstMove, int lastMove)
18367 {
18368         if(appData.icsActive) { // only in local mode
18369                 forwardMostMove = currentMove; // mimic old ICS behavior
18370                 return;
18371         }
18372         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18373
18374         PushInner(firstMove, lastMove);
18375         if(storedGames == 1) GreyRevert(FALSE);
18376         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18377 }
18378
18379 void
18380 PopInner (Boolean annotate)
18381 {
18382         int i, j, nrMoves;
18383         char buf[8000], moveBuf[20];
18384
18385         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18386         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18387         nrMoves = savedLast[storedGames] - currentMove;
18388         if(annotate) {
18389                 int cnt = 10;
18390                 if(!WhiteOnMove(currentMove))
18391                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18392                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18393                 for(i=currentMove; i<forwardMostMove; i++) {
18394                         if(WhiteOnMove(i))
18395                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18396                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18397                         strcat(buf, moveBuf);
18398                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18399                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18400                 }
18401                 strcat(buf, ")");
18402         }
18403         for(i=1; i<=nrMoves; i++) { // copy last variation back
18404             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18405             for(j=0; j<MOVE_LEN; j++)
18406                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18407             for(j=0; j<2*MOVE_LEN; j++)
18408                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18409             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18410             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18411             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18412             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18413             commentList[currentMove+i] = commentList[framePtr+i];
18414             commentList[framePtr+i] = NULL;
18415         }
18416         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18417         framePtr = savedFramePtr[storedGames];
18418         gameInfo.result = savedResult[storedGames];
18419         if(gameInfo.resultDetails != NULL) {
18420             free(gameInfo.resultDetails);
18421       }
18422         gameInfo.resultDetails = savedDetails[storedGames];
18423         forwardMostMove = currentMove + nrMoves;
18424 }
18425
18426 Boolean
18427 PopTail (Boolean annotate)
18428 {
18429         if(appData.icsActive) return FALSE; // only in local mode
18430         if(!storedGames) return FALSE; // sanity
18431         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18432
18433         PopInner(annotate);
18434         if(currentMove < forwardMostMove) ForwardEvent(); else
18435         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18436
18437         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18438         return TRUE;
18439 }
18440
18441 void
18442 CleanupTail ()
18443 {       // remove all shelved variations
18444         int i;
18445         for(i=0; i<storedGames; i++) {
18446             if(savedDetails[i])
18447                 free(savedDetails[i]);
18448             savedDetails[i] = NULL;
18449         }
18450         for(i=framePtr; i<MAX_MOVES; i++) {
18451                 if(commentList[i]) free(commentList[i]);
18452                 commentList[i] = NULL;
18453         }
18454         framePtr = MAX_MOVES-1;
18455         storedGames = 0;
18456 }
18457
18458 void
18459 LoadVariation (int index, char *text)
18460 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18461         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18462         int level = 0, move;
18463
18464         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18465         // first find outermost bracketing variation
18466         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18467             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18468                 if(*p == '{') wait = '}'; else
18469                 if(*p == '[') wait = ']'; else
18470                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18471                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18472             }
18473             if(*p == wait) wait = NULLCHAR; // closing ]} found
18474             p++;
18475         }
18476         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18477         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18478         end[1] = NULLCHAR; // clip off comment beyond variation
18479         ToNrEvent(currentMove-1);
18480         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18481         // kludge: use ParsePV() to append variation to game
18482         move = currentMove;
18483         ParsePV(start, TRUE, TRUE);
18484         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18485         ClearPremoveHighlights();
18486         CommentPopDown();
18487         ToNrEvent(currentMove+1);
18488 }
18489
18490 void
18491 LoadTheme ()
18492 {
18493     char *p, *q, buf[MSG_SIZ];
18494     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18495         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18496         ParseArgsFromString(buf);
18497         ActivateTheme(TRUE); // also redo colors
18498         return;
18499     }
18500     p = nickName;
18501     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18502     {
18503         int len;
18504         q = appData.themeNames;
18505         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18506       if(appData.useBitmaps) {
18507         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18508                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18509                 appData.liteBackTextureMode,
18510                 appData.darkBackTextureMode );
18511       } else {
18512         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18513                 Col2Text(2),   // lightSquareColor
18514                 Col2Text(3) ); // darkSquareColor
18515       }
18516       if(appData.useBorder) {
18517         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18518                 appData.border);
18519       } else {
18520         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18521       }
18522       if(appData.useFont) {
18523         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18524                 appData.renderPiecesWithFont,
18525                 appData.fontToPieceTable,
18526                 Col2Text(9),    // appData.fontBackColorWhite
18527                 Col2Text(10) ); // appData.fontForeColorBlack
18528       } else {
18529         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18530                 appData.pieceDirectory);
18531         if(!appData.pieceDirectory[0])
18532           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18533                 Col2Text(0),   // whitePieceColor
18534                 Col2Text(1) ); // blackPieceColor
18535       }
18536       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18537                 Col2Text(4),   // highlightSquareColor
18538                 Col2Text(5) ); // premoveHighlightColor
18539         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18540         if(insert != q) insert[-1] = NULLCHAR;
18541         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18542         if(q)   free(q);
18543     }
18544     ActivateTheme(FALSE);
18545 }