Reparse ambiguous move under built-in rules
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298
299 /* States for ics_getting_history */
300 #define H_FALSE 0
301 #define H_REQUESTED 1
302 #define H_GOT_REQ_HEADER 2
303 #define H_GOT_UNREQ_HEADER 3
304 #define H_GETTING_MOVES 4
305 #define H_GOT_UNWANTED_HEADER 5
306
307 /* whosays values for GameEnds */
308 #define GE_ICS 0
309 #define GE_ENGINE 1
310 #define GE_PLAYER 2
311 #define GE_FILE 3
312 #define GE_XBOARD 4
313 #define GE_ENGINE1 5
314 #define GE_ENGINE2 6
315
316 /* Maximum number of games in a cmail message */
317 #define CMAIL_MAX_GAMES 20
318
319 /* Different types of move when calling RegisterMove */
320 #define CMAIL_MOVE   0
321 #define CMAIL_RESIGN 1
322 #define CMAIL_DRAW   2
323 #define CMAIL_ACCEPT 3
324
325 /* Different types of result to remember for each game */
326 #define CMAIL_NOT_RESULT 0
327 #define CMAIL_OLD_RESULT 1
328 #define CMAIL_NEW_RESULT 2
329
330 /* Telnet protocol constants */
331 #define TN_WILL 0373
332 #define TN_WONT 0374
333 #define TN_DO   0375
334 #define TN_DONT 0376
335 #define TN_IAC  0377
336 #define TN_ECHO 0001
337 #define TN_SGA  0003
338 #define TN_PORT 23
339
340 char*
341 safeStrCpy (char *dst, const char *src, size_t count)
342 { // [HGM] made safe
343   int i;
344   assert( dst != NULL );
345   assert( src != NULL );
346   assert( count > 0 );
347
348   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
349   if(  i == count && dst[count-1] != NULLCHAR)
350     {
351       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
352       if(appData.debugMode)
353         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
354     }
355
356   return dst;
357 }
358
359 /* Some compiler can't cast u64 to double
360  * This function do the job for us:
361
362  * We use the highest bit for cast, this only
363  * works if the highest bit is not
364  * in use (This should not happen)
365  *
366  * We used this for all compiler
367  */
368 double
369 u64ToDouble (u64 value)
370 {
371   double r;
372   u64 tmp = value & u64Const(0x7fffffffffffffff);
373   r = (double)(s64)tmp;
374   if (value & u64Const(0x8000000000000000))
375        r +=  9.2233720368547758080e18; /* 2^63 */
376  return r;
377 }
378
379 /* Fake up flags for now, as we aren't keeping track of castling
380    availability yet. [HGM] Change of logic: the flag now only
381    indicates the type of castlings allowed by the rule of the game.
382    The actual rights themselves are maintained in the array
383    castlingRights, as part of the game history, and are not probed
384    by this function.
385  */
386 int
387 PosFlags (index)
388 {
389   int flags = F_ALL_CASTLE_OK;
390   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
391   switch (gameInfo.variant) {
392   case VariantSuicide:
393     flags &= ~F_ALL_CASTLE_OK;
394   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
395     flags |= F_IGNORE_CHECK;
396   case VariantLosers:
397     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
398     break;
399   case VariantAtomic:
400     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
401     break;
402   case VariantKriegspiel:
403     flags |= F_KRIEGSPIEL_CAPTURE;
404     break;
405   case VariantCapaRandom:
406   case VariantFischeRandom:
407     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
408   case VariantNoCastle:
409   case VariantShatranj:
410   case VariantCourier:
411   case VariantMakruk:
412   case VariantASEAN:
413   case VariantGrand:
414     flags &= ~F_ALL_CASTLE_OK;
415     break;
416   case VariantChu:
417   case VariantChuChess:
418   case VariantLion:
419     flags |= F_NULL_MOVE;
420     break;
421   default:
422     break;
423   }
424   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
425   return flags;
426 }
427
428 FILE *gameFileFP, *debugFP, *serverFP;
429 char *currentDebugFile; // [HGM] debug split: to remember name
430
431 /*
432     [AS] Note: sometimes, the sscanf() function is used to parse the input
433     into a fixed-size buffer. Because of this, we must be prepared to
434     receive strings as long as the size of the input buffer, which is currently
435     set to 4K for Windows and 8K for the rest.
436     So, we must either allocate sufficiently large buffers here, or
437     reduce the size of the input buffer in the input reading part.
438 */
439
440 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
441 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
442 char thinkOutput1[MSG_SIZ*10];
443
444 ChessProgramState first, second, pairing;
445
446 /* premove variables */
447 int premoveToX = 0;
448 int premoveToY = 0;
449 int premoveFromX = 0;
450 int premoveFromY = 0;
451 int premovePromoChar = 0;
452 int gotPremove = 0;
453 Boolean alarmSounded;
454 /* end premove variables */
455
456 char *ics_prefix = "$";
457 enum ICS_TYPE ics_type = ICS_GENERIC;
458
459 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
460 int pauseExamForwardMostMove = 0;
461 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
462 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
463 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
464 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
465 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
466 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
467 int whiteFlag = FALSE, blackFlag = FALSE;
468 int userOfferedDraw = FALSE;
469 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
470 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
471 int cmailMoveType[CMAIL_MAX_GAMES];
472 long ics_clock_paused = 0;
473 ProcRef icsPR = NoProc, cmailPR = NoProc;
474 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
475 GameMode gameMode = BeginningOfGame;
476 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
477 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
478 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
479 int hiddenThinkOutputState = 0; /* [AS] */
480 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
481 int adjudicateLossPlies = 6;
482 char white_holding[64], black_holding[64];
483 TimeMark lastNodeCountTime;
484 long lastNodeCount=0;
485 int shiftKey, controlKey; // [HGM] set by mouse handler
486
487 int have_sent_ICS_logon = 0;
488 int movesPerSession;
489 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
490 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
491 Boolean adjustedClock;
492 long timeControl_2; /* [AS] Allow separate time controls */
493 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
494 long timeRemaining[2][MAX_MOVES];
495 int matchGame = 0, nextGame = 0, roundNr = 0;
496 Boolean waitingForGame = FALSE, startingEngine = FALSE;
497 TimeMark programStartTime, pauseStart;
498 char ics_handle[MSG_SIZ];
499 int have_set_title = 0;
500
501 /* animateTraining preserves the state of appData.animate
502  * when Training mode is activated. This allows the
503  * response to be animated when appData.animate == TRUE and
504  * appData.animateDragging == TRUE.
505  */
506 Boolean animateTraining;
507
508 GameInfo gameInfo;
509
510 AppData appData;
511
512 Board boards[MAX_MOVES];
513 /* [HGM] Following 7 needed for accurate legality tests: */
514 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
515 signed char  initialRights[BOARD_FILES];
516 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
517 int   initialRulePlies, FENrulePlies;
518 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
519 int loadFlag = 0;
520 Boolean shuffleOpenings;
521 int mute; // mute all sounds
522
523 // [HGM] vari: next 12 to save and restore variations
524 #define MAX_VARIATIONS 10
525 int framePtr = MAX_MOVES-1; // points to free stack entry
526 int storedGames = 0;
527 int savedFirst[MAX_VARIATIONS];
528 int savedLast[MAX_VARIATIONS];
529 int savedFramePtr[MAX_VARIATIONS];
530 char *savedDetails[MAX_VARIATIONS];
531 ChessMove savedResult[MAX_VARIATIONS];
532
533 void PushTail P((int firstMove, int lastMove));
534 Boolean PopTail P((Boolean annotate));
535 void PushInner P((int firstMove, int lastMove));
536 void PopInner P((Boolean annotate));
537 void CleanupTail P((void));
538
539 ChessSquare  FIDEArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
543         BlackKing, BlackBishop, BlackKnight, BlackRook }
544 };
545
546 ChessSquare twoKingsArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
548         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
550         BlackKing, BlackKing, BlackKnight, BlackRook }
551 };
552
553 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
554     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
555         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
556     { BlackRook, BlackMan, BlackBishop, BlackQueen,
557         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
558 };
559
560 ChessSquare SpartanArray[2][BOARD_FILES] = {
561     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
562         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
563     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
564         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
565 };
566
567 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
568     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
569         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
570     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
571         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
572 };
573
574 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
575     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
576         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
577     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
578         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
579 };
580
581 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
582     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
583         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
584     { BlackRook, BlackKnight, BlackMan, BlackFerz,
585         BlackKing, BlackMan, BlackKnight, BlackRook }
586 };
587
588 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
589     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
590         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
591     { BlackRook, BlackKnight, BlackMan, BlackFerz,
592         BlackKing, BlackMan, BlackKnight, BlackRook }
593 };
594
595 ChessSquare  lionArray[2][BOARD_FILES] = {
596     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
597         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
598     { BlackRook, BlackLion, BlackBishop, BlackQueen,
599         BlackKing, BlackBishop, BlackKnight, BlackRook }
600 };
601
602
603 #if (BOARD_FILES>=10)
604 ChessSquare ShogiArray[2][BOARD_FILES] = {
605     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
606         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
607     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
608         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
609 };
610
611 ChessSquare XiangqiArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
613         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
615         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
616 };
617
618 ChessSquare CapablancaArray[2][BOARD_FILES] = {
619     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
620         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
621     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
622         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
623 };
624
625 ChessSquare GreatArray[2][BOARD_FILES] = {
626     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
627         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
628     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
629         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
630 };
631
632 ChessSquare JanusArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
634         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
635     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
636         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
637 };
638
639 ChessSquare GrandArray[2][BOARD_FILES] = {
640     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
641         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
642     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
643         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
644 };
645
646 ChessSquare ChuChessArray[2][BOARD_FILES] = {
647     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
648         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
649     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
650         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
651 };
652
653 #ifdef GOTHIC
654 ChessSquare GothicArray[2][BOARD_FILES] = {
655     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
656         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
657     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
658         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
659 };
660 #else // !GOTHIC
661 #define GothicArray CapablancaArray
662 #endif // !GOTHIC
663
664 #ifdef FALCON
665 ChessSquare FalconArray[2][BOARD_FILES] = {
666     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
667         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
668     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
669         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
670 };
671 #else // !FALCON
672 #define FalconArray CapablancaArray
673 #endif // !FALCON
674
675 #else // !(BOARD_FILES>=10)
676 #define XiangqiPosition FIDEArray
677 #define CapablancaArray FIDEArray
678 #define GothicArray FIDEArray
679 #define GreatArray FIDEArray
680 #endif // !(BOARD_FILES>=10)
681
682 #if (BOARD_FILES>=12)
683 ChessSquare CourierArray[2][BOARD_FILES] = {
684     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
685         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
686     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
687         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
688 };
689 ChessSquare ChuArray[6][BOARD_FILES] = {
690     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
691       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
692     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
693       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
694     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
695       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
696     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
697       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
698     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
699       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
700     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
701       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
702 };
703 #else // !(BOARD_FILES>=12)
704 #define CourierArray CapablancaArray
705 #define ChuArray CapablancaArray
706 #endif // !(BOARD_FILES>=12)
707
708
709 Board initialPosition;
710
711
712 /* Convert str to a rating. Checks for special cases of "----",
713
714    "++++", etc. Also strips ()'s */
715 int
716 string_to_rating (char *str)
717 {
718   while(*str && !isdigit(*str)) ++str;
719   if (!*str)
720     return 0;   /* One of the special "no rating" cases */
721   else
722     return atoi(str);
723 }
724
725 void
726 ClearProgramStats ()
727 {
728     /* Init programStats */
729     programStats.movelist[0] = 0;
730     programStats.depth = 0;
731     programStats.nr_moves = 0;
732     programStats.moves_left = 0;
733     programStats.nodes = 0;
734     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
735     programStats.score = 0;
736     programStats.got_only_move = 0;
737     programStats.got_fail = 0;
738     programStats.line_is_book = 0;
739 }
740
741 void
742 CommonEngineInit ()
743 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
744     if (appData.firstPlaysBlack) {
745         first.twoMachinesColor = "black\n";
746         second.twoMachinesColor = "white\n";
747     } else {
748         first.twoMachinesColor = "white\n";
749         second.twoMachinesColor = "black\n";
750     }
751
752     first.other = &second;
753     second.other = &first;
754
755     { float norm = 1;
756         if(appData.timeOddsMode) {
757             norm = appData.timeOdds[0];
758             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
759         }
760         first.timeOdds  = appData.timeOdds[0]/norm;
761         second.timeOdds = appData.timeOdds[1]/norm;
762     }
763
764     if(programVersion) free(programVersion);
765     if (appData.noChessProgram) {
766         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
767         sprintf(programVersion, "%s", PACKAGE_STRING);
768     } else {
769       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
770       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
771       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
772     }
773 }
774
775 void
776 UnloadEngine (ChessProgramState *cps)
777 {
778         /* Kill off first chess program */
779         if (cps->isr != NULL)
780           RemoveInputSource(cps->isr);
781         cps->isr = NULL;
782
783         if (cps->pr != NoProc) {
784             ExitAnalyzeMode();
785             DoSleep( appData.delayBeforeQuit );
786             SendToProgram("quit\n", cps);
787             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
788         }
789         cps->pr = NoProc;
790         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
791 }
792
793 void
794 ClearOptions (ChessProgramState *cps)
795 {
796     int i;
797     cps->nrOptions = cps->comboCnt = 0;
798     for(i=0; i<MAX_OPTIONS; i++) {
799         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
800         cps->option[i].textValue = 0;
801     }
802 }
803
804 char *engineNames[] = {
805   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
806      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
807 N_("first"),
808   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("second")
811 };
812
813 void
814 InitEngine (ChessProgramState *cps, int n)
815 {   // [HGM] all engine initialiation put in a function that does one engine
816
817     ClearOptions(cps);
818
819     cps->which = engineNames[n];
820     cps->maybeThinking = FALSE;
821     cps->pr = NoProc;
822     cps->isr = NULL;
823     cps->sendTime = 2;
824     cps->sendDrawOffers = 1;
825
826     cps->program = appData.chessProgram[n];
827     cps->host = appData.host[n];
828     cps->dir = appData.directory[n];
829     cps->initString = appData.engInitString[n];
830     cps->computerString = appData.computerString[n];
831     cps->useSigint  = TRUE;
832     cps->useSigterm = TRUE;
833     cps->reuse = appData.reuse[n];
834     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
835     cps->useSetboard = FALSE;
836     cps->useSAN = FALSE;
837     cps->usePing = FALSE;
838     cps->lastPing = 0;
839     cps->lastPong = 0;
840     cps->usePlayother = FALSE;
841     cps->useColors = TRUE;
842     cps->useUsermove = FALSE;
843     cps->sendICS = FALSE;
844     cps->sendName = appData.icsActive;
845     cps->sdKludge = FALSE;
846     cps->stKludge = FALSE;
847     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
848     TidyProgramName(cps->program, cps->host, cps->tidy);
849     cps->matchWins = 0;
850     ASSIGN(cps->variants, appData.variant);
851     cps->analysisSupport = 2; /* detect */
852     cps->analyzing = FALSE;
853     cps->initDone = FALSE;
854     cps->reload = FALSE;
855     cps->pseudo = appData.pseudo[n];
856
857     /* New features added by Tord: */
858     cps->useFEN960 = FALSE;
859     cps->useOOCastle = TRUE;
860     /* End of new features added by Tord. */
861     cps->fenOverride  = appData.fenOverride[n];
862
863     /* [HGM] time odds: set factor for each machine */
864     cps->timeOdds  = appData.timeOdds[n];
865
866     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
867     cps->accumulateTC = appData.accumulateTC[n];
868     cps->maxNrOfSessions = 1;
869
870     /* [HGM] debug */
871     cps->debug = FALSE;
872
873     cps->drawDepth = appData.drawDepth[n];
874     cps->supportsNPS = UNKNOWN;
875     cps->memSize = FALSE;
876     cps->maxCores = FALSE;
877     ASSIGN(cps->egtFormats, "");
878
879     /* [HGM] options */
880     cps->optionSettings  = appData.engOptions[n];
881
882     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
883     cps->isUCI = appData.isUCI[n]; /* [AS] */
884     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
885     cps->highlight = 0;
886
887     if (appData.protocolVersion[n] > PROTOVER
888         || appData.protocolVersion[n] < 1)
889       {
890         char buf[MSG_SIZ];
891         int len;
892
893         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
894                        appData.protocolVersion[n]);
895         if( (len >= MSG_SIZ) && appData.debugMode )
896           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
897
898         DisplayFatalError(buf, 0, 2);
899       }
900     else
901       {
902         cps->protocolVersion = appData.protocolVersion[n];
903       }
904
905     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
906     ParseFeatures(appData.featureDefaults, cps);
907 }
908
909 ChessProgramState *savCps;
910
911 GameMode oldMode;
912
913 void
914 LoadEngine ()
915 {
916     int i;
917     if(WaitForEngine(savCps, LoadEngine)) return;
918     CommonEngineInit(); // recalculate time odds
919     if(gameInfo.variant != StringToVariant(appData.variant)) {
920         // we changed variant when loading the engine; this forces us to reset
921         Reset(TRUE, savCps != &first);
922         oldMode = BeginningOfGame; // to prevent restoring old mode
923     }
924     InitChessProgram(savCps, FALSE);
925     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
926     DisplayMessage("", "");
927     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
928     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
929     ThawUI();
930     SetGNUMode();
931     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
932 }
933
934 void
935 ReplaceEngine (ChessProgramState *cps, int n)
936 {
937     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
938     keepInfo = 1;
939     if(oldMode != BeginningOfGame) EditGameEvent();
940     keepInfo = 0;
941     UnloadEngine(cps);
942     appData.noChessProgram = FALSE;
943     appData.clockMode = TRUE;
944     InitEngine(cps, n);
945     UpdateLogos(TRUE);
946     if(n) return; // only startup first engine immediately; second can wait
947     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
948     LoadEngine();
949 }
950
951 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
952 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
953
954 static char resetOptions[] =
955         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
956         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
957         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
958         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
959
960 void
961 FloatToFront(char **list, char *engineLine)
962 {
963     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
964     int i=0;
965     if(appData.recentEngines <= 0) return;
966     TidyProgramName(engineLine, "localhost", tidy+1);
967     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
968     strncpy(buf+1, *list, MSG_SIZ-50);
969     if(p = strstr(buf, tidy)) { // tidy name appears in list
970         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
971         while(*p++ = *++q); // squeeze out
972     }
973     strcat(tidy, buf+1); // put list behind tidy name
974     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
975     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
976     ASSIGN(*list, tidy+1);
977 }
978
979 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
980
981 void
982 Load (ChessProgramState *cps, int i)
983 {
984     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
985     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
986         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
987         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
988         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
989         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
990         appData.firstProtocolVersion = PROTOVER;
991         ParseArgsFromString(buf);
992         SwapEngines(i);
993         ReplaceEngine(cps, i);
994         FloatToFront(&appData.recentEngineList, engineLine);
995         return;
996     }
997     p = engineName;
998     while(q = strchr(p, SLASH)) p = q+1;
999     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1000     if(engineDir[0] != NULLCHAR) {
1001         ASSIGN(appData.directory[i], engineDir); p = engineName;
1002     } else if(p != engineName) { // derive directory from engine path, when not given
1003         p[-1] = 0;
1004         ASSIGN(appData.directory[i], engineName);
1005         p[-1] = SLASH;
1006         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1007     } else { ASSIGN(appData.directory[i], "."); }
1008     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1009     if(params[0]) {
1010         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1011         snprintf(command, MSG_SIZ, "%s %s", p, params);
1012         p = command;
1013     }
1014     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1015     ASSIGN(appData.chessProgram[i], p);
1016     appData.isUCI[i] = isUCI;
1017     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1018     appData.hasOwnBookUCI[i] = hasBook;
1019     if(!nickName[0]) useNick = FALSE;
1020     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1021     if(addToList) {
1022         int len;
1023         char quote;
1024         q = firstChessProgramNames;
1025         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1026         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1027         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1028                         quote, p, quote, appData.directory[i],
1029                         useNick ? " -fn \"" : "",
1030                         useNick ? nickName : "",
1031                         useNick ? "\"" : "",
1032                         v1 ? " -firstProtocolVersion 1" : "",
1033                         hasBook ? "" : " -fNoOwnBookUCI",
1034                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1035                         storeVariant ? " -variant " : "",
1036                         storeVariant ? VariantName(gameInfo.variant) : "");
1037         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1038         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1039         if(insert != q) insert[-1] = NULLCHAR;
1040         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1041         if(q)   free(q);
1042         FloatToFront(&appData.recentEngineList, buf);
1043     }
1044     ReplaceEngine(cps, i);
1045 }
1046
1047 void
1048 InitTimeControls ()
1049 {
1050     int matched, min, sec;
1051     /*
1052      * Parse timeControl resource
1053      */
1054     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1055                           appData.movesPerSession)) {
1056         char buf[MSG_SIZ];
1057         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1058         DisplayFatalError(buf, 0, 2);
1059     }
1060
1061     /*
1062      * Parse searchTime resource
1063      */
1064     if (*appData.searchTime != NULLCHAR) {
1065         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1066         if (matched == 1) {
1067             searchTime = min * 60;
1068         } else if (matched == 2) {
1069             searchTime = min * 60 + sec;
1070         } else {
1071             char buf[MSG_SIZ];
1072             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1073             DisplayFatalError(buf, 0, 2);
1074         }
1075     }
1076 }
1077
1078 void
1079 InitBackEnd1 ()
1080 {
1081
1082     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1083     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1084
1085     GetTimeMark(&programStartTime);
1086     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1087     appData.seedBase = random() + (random()<<15);
1088     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1089
1090     ClearProgramStats();
1091     programStats.ok_to_send = 1;
1092     programStats.seen_stat = 0;
1093
1094     /*
1095      * Initialize game list
1096      */
1097     ListNew(&gameList);
1098
1099
1100     /*
1101      * Internet chess server status
1102      */
1103     if (appData.icsActive) {
1104         appData.matchMode = FALSE;
1105         appData.matchGames = 0;
1106 #if ZIPPY
1107         appData.noChessProgram = !appData.zippyPlay;
1108 #else
1109         appData.zippyPlay = FALSE;
1110         appData.zippyTalk = FALSE;
1111         appData.noChessProgram = TRUE;
1112 #endif
1113         if (*appData.icsHelper != NULLCHAR) {
1114             appData.useTelnet = TRUE;
1115             appData.telnetProgram = appData.icsHelper;
1116         }
1117     } else {
1118         appData.zippyTalk = appData.zippyPlay = FALSE;
1119     }
1120
1121     /* [AS] Initialize pv info list [HGM] and game state */
1122     {
1123         int i, j;
1124
1125         for( i=0; i<=framePtr; i++ ) {
1126             pvInfoList[i].depth = -1;
1127             boards[i][EP_STATUS] = EP_NONE;
1128             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1129         }
1130     }
1131
1132     InitTimeControls();
1133
1134     /* [AS] Adjudication threshold */
1135     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1136
1137     InitEngine(&first, 0);
1138     InitEngine(&second, 1);
1139     CommonEngineInit();
1140
1141     pairing.which = "pairing"; // pairing engine
1142     pairing.pr = NoProc;
1143     pairing.isr = NULL;
1144     pairing.program = appData.pairingEngine;
1145     pairing.host = "localhost";
1146     pairing.dir = ".";
1147
1148     if (appData.icsActive) {
1149         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1150     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1151         appData.clockMode = FALSE;
1152         first.sendTime = second.sendTime = 0;
1153     }
1154
1155 #if ZIPPY
1156     /* Override some settings from environment variables, for backward
1157        compatibility.  Unfortunately it's not feasible to have the env
1158        vars just set defaults, at least in xboard.  Ugh.
1159     */
1160     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1161       ZippyInit();
1162     }
1163 #endif
1164
1165     if (!appData.icsActive) {
1166       char buf[MSG_SIZ];
1167       int len;
1168
1169       /* Check for variants that are supported only in ICS mode,
1170          or not at all.  Some that are accepted here nevertheless
1171          have bugs; see comments below.
1172       */
1173       VariantClass variant = StringToVariant(appData.variant);
1174       switch (variant) {
1175       case VariantBughouse:     /* need four players and two boards */
1176       case VariantKriegspiel:   /* need to hide pieces and move details */
1177         /* case VariantFischeRandom: (Fabien: moved below) */
1178         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1179         if( (len >= MSG_SIZ) && appData.debugMode )
1180           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1181
1182         DisplayFatalError(buf, 0, 2);
1183         return;
1184
1185       case VariantUnknown:
1186       case VariantLoadable:
1187       case Variant29:
1188       case Variant30:
1189       case Variant31:
1190       case Variant32:
1191       case Variant33:
1192       case Variant34:
1193       case Variant35:
1194       case Variant36:
1195       default:
1196         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1197         if( (len >= MSG_SIZ) && appData.debugMode )
1198           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1199
1200         DisplayFatalError(buf, 0, 2);
1201         return;
1202
1203       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1204       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1205       case VariantGothic:     /* [HGM] should work */
1206       case VariantCapablanca: /* [HGM] should work */
1207       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1208       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1209       case VariantChu:        /* [HGM] experimental */
1210       case VariantKnightmate: /* [HGM] should work */
1211       case VariantCylinder:   /* [HGM] untested */
1212       case VariantFalcon:     /* [HGM] untested */
1213       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1214                                  offboard interposition not understood */
1215       case VariantNormal:     /* definitely works! */
1216       case VariantWildCastle: /* pieces not automatically shuffled */
1217       case VariantNoCastle:   /* pieces not automatically shuffled */
1218       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1219       case VariantLosers:     /* should work except for win condition,
1220                                  and doesn't know captures are mandatory */
1221       case VariantSuicide:    /* should work except for win condition,
1222                                  and doesn't know captures are mandatory */
1223       case VariantGiveaway:   /* should work except for win condition,
1224                                  and doesn't know captures are mandatory */
1225       case VariantTwoKings:   /* should work */
1226       case VariantAtomic:     /* should work except for win condition */
1227       case Variant3Check:     /* should work except for win condition */
1228       case VariantShatranj:   /* should work except for all win conditions */
1229       case VariantMakruk:     /* should work except for draw countdown */
1230       case VariantASEAN :     /* should work except for draw countdown */
1231       case VariantBerolina:   /* might work if TestLegality is off */
1232       case VariantCapaRandom: /* should work */
1233       case VariantJanus:      /* should work */
1234       case VariantSuper:      /* experimental */
1235       case VariantGreat:      /* experimental, requires legality testing to be off */
1236       case VariantSChess:     /* S-Chess, should work */
1237       case VariantGrand:      /* should work */
1238       case VariantSpartan:    /* should work */
1239       case VariantLion:       /* should work */
1240       case VariantChuChess:   /* should work */
1241         break;
1242       }
1243     }
1244
1245 }
1246
1247 int
1248 NextIntegerFromString (char ** str, long * value)
1249 {
1250     int result = -1;
1251     char * s = *str;
1252
1253     while( *s == ' ' || *s == '\t' ) {
1254         s++;
1255     }
1256
1257     *value = 0;
1258
1259     if( *s >= '0' && *s <= '9' ) {
1260         while( *s >= '0' && *s <= '9' ) {
1261             *value = *value * 10 + (*s - '0');
1262             s++;
1263         }
1264
1265         result = 0;
1266     }
1267
1268     *str = s;
1269
1270     return result;
1271 }
1272
1273 int
1274 NextTimeControlFromString (char ** str, long * value)
1275 {
1276     long temp;
1277     int result = NextIntegerFromString( str, &temp );
1278
1279     if( result == 0 ) {
1280         *value = temp * 60; /* Minutes */
1281         if( **str == ':' ) {
1282             (*str)++;
1283             result = NextIntegerFromString( str, &temp );
1284             *value += temp; /* Seconds */
1285         }
1286     }
1287
1288     return result;
1289 }
1290
1291 int
1292 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1293 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1294     int result = -1, type = 0; long temp, temp2;
1295
1296     if(**str != ':') return -1; // old params remain in force!
1297     (*str)++;
1298     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1299     if( NextIntegerFromString( str, &temp ) ) return -1;
1300     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1301
1302     if(**str != '/') {
1303         /* time only: incremental or sudden-death time control */
1304         if(**str == '+') { /* increment follows; read it */
1305             (*str)++;
1306             if(**str == '!') type = *(*str)++; // Bronstein TC
1307             if(result = NextIntegerFromString( str, &temp2)) return -1;
1308             *inc = temp2 * 1000;
1309             if(**str == '.') { // read fraction of increment
1310                 char *start = ++(*str);
1311                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1312                 temp2 *= 1000;
1313                 while(start++ < *str) temp2 /= 10;
1314                 *inc += temp2;
1315             }
1316         } else *inc = 0;
1317         *moves = 0; *tc = temp * 1000; *incType = type;
1318         return 0;
1319     }
1320
1321     (*str)++; /* classical time control */
1322     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1323
1324     if(result == 0) {
1325         *moves = temp;
1326         *tc    = temp2 * 1000;
1327         *inc   = 0;
1328         *incType = type;
1329     }
1330     return result;
1331 }
1332
1333 int
1334 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1335 {   /* [HGM] get time to add from the multi-session time-control string */
1336     int incType, moves=1; /* kludge to force reading of first session */
1337     long time, increment;
1338     char *s = tcString;
1339
1340     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1341     do {
1342         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1343         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1344         if(movenr == -1) return time;    /* last move before new session     */
1345         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1346         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1347         if(!moves) return increment;     /* current session is incremental   */
1348         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1349     } while(movenr >= -1);               /* try again for next session       */
1350
1351     return 0; // no new time quota on this move
1352 }
1353
1354 int
1355 ParseTimeControl (char *tc, float ti, int mps)
1356 {
1357   long tc1;
1358   long tc2;
1359   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1360   int min, sec=0;
1361
1362   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1363   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1364       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1365   if(ti > 0) {
1366
1367     if(mps)
1368       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1369     else
1370       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1371   } else {
1372     if(mps)
1373       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1374     else
1375       snprintf(buf, MSG_SIZ, ":%s", mytc);
1376   }
1377   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1378
1379   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1380     return FALSE;
1381   }
1382
1383   if( *tc == '/' ) {
1384     /* Parse second time control */
1385     tc++;
1386
1387     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1388       return FALSE;
1389     }
1390
1391     if( tc2 == 0 ) {
1392       return FALSE;
1393     }
1394
1395     timeControl_2 = tc2 * 1000;
1396   }
1397   else {
1398     timeControl_2 = 0;
1399   }
1400
1401   if( tc1 == 0 ) {
1402     return FALSE;
1403   }
1404
1405   timeControl = tc1 * 1000;
1406
1407   if (ti >= 0) {
1408     timeIncrement = ti * 1000;  /* convert to ms */
1409     movesPerSession = 0;
1410   } else {
1411     timeIncrement = 0;
1412     movesPerSession = mps;
1413   }
1414   return TRUE;
1415 }
1416
1417 void
1418 InitBackEnd2 ()
1419 {
1420     if (appData.debugMode) {
1421 #    ifdef __GIT_VERSION
1422       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1423 #    else
1424       fprintf(debugFP, "Version: %s\n", programVersion);
1425 #    endif
1426     }
1427     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1428
1429     set_cont_sequence(appData.wrapContSeq);
1430     if (appData.matchGames > 0) {
1431         appData.matchMode = TRUE;
1432     } else if (appData.matchMode) {
1433         appData.matchGames = 1;
1434     }
1435     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1436         appData.matchGames = appData.sameColorGames;
1437     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1438         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1439         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1440     }
1441     Reset(TRUE, FALSE);
1442     if (appData.noChessProgram || first.protocolVersion == 1) {
1443       InitBackEnd3();
1444     } else {
1445       /* kludge: allow timeout for initial "feature" commands */
1446       FreezeUI();
1447       DisplayMessage("", _("Starting chess program"));
1448       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1449     }
1450 }
1451
1452 int
1453 CalculateIndex (int index, int gameNr)
1454 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1455     int res;
1456     if(index > 0) return index; // fixed nmber
1457     if(index == 0) return 1;
1458     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1459     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1460     return res;
1461 }
1462
1463 int
1464 LoadGameOrPosition (int gameNr)
1465 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1466     if (*appData.loadGameFile != NULLCHAR) {
1467         if (!LoadGameFromFile(appData.loadGameFile,
1468                 CalculateIndex(appData.loadGameIndex, gameNr),
1469                               appData.loadGameFile, FALSE)) {
1470             DisplayFatalError(_("Bad game file"), 0, 1);
1471             return 0;
1472         }
1473     } else if (*appData.loadPositionFile != NULLCHAR) {
1474         if (!LoadPositionFromFile(appData.loadPositionFile,
1475                 CalculateIndex(appData.loadPositionIndex, gameNr),
1476                                   appData.loadPositionFile)) {
1477             DisplayFatalError(_("Bad position file"), 0, 1);
1478             return 0;
1479         }
1480     }
1481     return 1;
1482 }
1483
1484 void
1485 ReserveGame (int gameNr, char resChar)
1486 {
1487     FILE *tf = fopen(appData.tourneyFile, "r+");
1488     char *p, *q, c, buf[MSG_SIZ];
1489     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1490     safeStrCpy(buf, lastMsg, MSG_SIZ);
1491     DisplayMessage(_("Pick new game"), "");
1492     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1493     ParseArgsFromFile(tf);
1494     p = q = appData.results;
1495     if(appData.debugMode) {
1496       char *r = appData.participants;
1497       fprintf(debugFP, "results = '%s'\n", p);
1498       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1499       fprintf(debugFP, "\n");
1500     }
1501     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1502     nextGame = q - p;
1503     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1504     safeStrCpy(q, p, strlen(p) + 2);
1505     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1506     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1507     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1508         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1509         q[nextGame] = '*';
1510     }
1511     fseek(tf, -(strlen(p)+4), SEEK_END);
1512     c = fgetc(tf);
1513     if(c != '"') // depending on DOS or Unix line endings we can be one off
1514          fseek(tf, -(strlen(p)+2), SEEK_END);
1515     else fseek(tf, -(strlen(p)+3), SEEK_END);
1516     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1517     DisplayMessage(buf, "");
1518     free(p); appData.results = q;
1519     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1520        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1521       int round = appData.defaultMatchGames * appData.tourneyType;
1522       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1523          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1524         UnloadEngine(&first);  // next game belongs to other pairing;
1525         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1526     }
1527     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1528 }
1529
1530 void
1531 MatchEvent (int mode)
1532 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1533         int dummy;
1534         if(matchMode) { // already in match mode: switch it off
1535             abortMatch = TRUE;
1536             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1537             return;
1538         }
1539 //      if(gameMode != BeginningOfGame) {
1540 //          DisplayError(_("You can only start a match from the initial position."), 0);
1541 //          return;
1542 //      }
1543         abortMatch = FALSE;
1544         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1545         /* Set up machine vs. machine match */
1546         nextGame = 0;
1547         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1548         if(appData.tourneyFile[0]) {
1549             ReserveGame(-1, 0);
1550             if(nextGame > appData.matchGames) {
1551                 char buf[MSG_SIZ];
1552                 if(strchr(appData.results, '*') == NULL) {
1553                     FILE *f;
1554                     appData.tourneyCycles++;
1555                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1556                         fclose(f);
1557                         NextTourneyGame(-1, &dummy);
1558                         ReserveGame(-1, 0);
1559                         if(nextGame <= appData.matchGames) {
1560                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1561                             matchMode = mode;
1562                             ScheduleDelayedEvent(NextMatchGame, 10000);
1563                             return;
1564                         }
1565                     }
1566                 }
1567                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1568                 DisplayError(buf, 0);
1569                 appData.tourneyFile[0] = 0;
1570                 return;
1571             }
1572         } else
1573         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1574             DisplayFatalError(_("Can't have a match with no chess programs"),
1575                               0, 2);
1576             return;
1577         }
1578         matchMode = mode;
1579         matchGame = roundNr = 1;
1580         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1581         NextMatchGame();
1582 }
1583
1584 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1585
1586 void
1587 InitBackEnd3 P((void))
1588 {
1589     GameMode initialMode;
1590     char buf[MSG_SIZ];
1591     int err, len;
1592
1593     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1594        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1595         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1596        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1597        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1598         char c, *q = first.variants, *p = strchr(q, ',');
1599         if(p) *p = NULLCHAR;
1600         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1601             int w, h, s;
1602             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1603                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1604             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1605             Reset(TRUE, FALSE);         // and re-initialize
1606         }
1607         if(p) *p = ',';
1608     }
1609
1610     InitChessProgram(&first, startedFromSetupPosition);
1611
1612     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1613         free(programVersion);
1614         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1615         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1616         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1617     }
1618
1619     if (appData.icsActive) {
1620 #ifdef WIN32
1621         /* [DM] Make a console window if needed [HGM] merged ifs */
1622         ConsoleCreate();
1623 #endif
1624         err = establish();
1625         if (err != 0)
1626           {
1627             if (*appData.icsCommPort != NULLCHAR)
1628               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1629                              appData.icsCommPort);
1630             else
1631               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1632                         appData.icsHost, appData.icsPort);
1633
1634             if( (len >= MSG_SIZ) && appData.debugMode )
1635               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1636
1637             DisplayFatalError(buf, err, 1);
1638             return;
1639         }
1640         SetICSMode();
1641         telnetISR =
1642           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1643         fromUserISR =
1644           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1645         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1646             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1647     } else if (appData.noChessProgram) {
1648         SetNCPMode();
1649     } else {
1650         SetGNUMode();
1651     }
1652
1653     if (*appData.cmailGameName != NULLCHAR) {
1654         SetCmailMode();
1655         OpenLoopback(&cmailPR);
1656         cmailISR =
1657           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1658     }
1659
1660     ThawUI();
1661     DisplayMessage("", "");
1662     if (StrCaseCmp(appData.initialMode, "") == 0) {
1663       initialMode = BeginningOfGame;
1664       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1665         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1666         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1667         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1668         ModeHighlight();
1669       }
1670     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1671       initialMode = TwoMachinesPlay;
1672     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1673       initialMode = AnalyzeFile;
1674     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1675       initialMode = AnalyzeMode;
1676     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1677       initialMode = MachinePlaysWhite;
1678     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1679       initialMode = MachinePlaysBlack;
1680     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1681       initialMode = EditGame;
1682     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1683       initialMode = EditPosition;
1684     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1685       initialMode = Training;
1686     } else {
1687       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1688       if( (len >= MSG_SIZ) && appData.debugMode )
1689         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1690
1691       DisplayFatalError(buf, 0, 2);
1692       return;
1693     }
1694
1695     if (appData.matchMode) {
1696         if(appData.tourneyFile[0]) { // start tourney from command line
1697             FILE *f;
1698             if(f = fopen(appData.tourneyFile, "r")) {
1699                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1700                 fclose(f);
1701                 appData.clockMode = TRUE;
1702                 SetGNUMode();
1703             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1704         }
1705         MatchEvent(TRUE);
1706     } else if (*appData.cmailGameName != NULLCHAR) {
1707         /* Set up cmail mode */
1708         ReloadCmailMsgEvent(TRUE);
1709     } else {
1710         /* Set up other modes */
1711         if (initialMode == AnalyzeFile) {
1712           if (*appData.loadGameFile == NULLCHAR) {
1713             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1714             return;
1715           }
1716         }
1717         if (*appData.loadGameFile != NULLCHAR) {
1718             (void) LoadGameFromFile(appData.loadGameFile,
1719                                     appData.loadGameIndex,
1720                                     appData.loadGameFile, TRUE);
1721         } else if (*appData.loadPositionFile != NULLCHAR) {
1722             (void) LoadPositionFromFile(appData.loadPositionFile,
1723                                         appData.loadPositionIndex,
1724                                         appData.loadPositionFile);
1725             /* [HGM] try to make self-starting even after FEN load */
1726             /* to allow automatic setup of fairy variants with wtm */
1727             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1728                 gameMode = BeginningOfGame;
1729                 setboardSpoiledMachineBlack = 1;
1730             }
1731             /* [HGM] loadPos: make that every new game uses the setup */
1732             /* from file as long as we do not switch variant          */
1733             if(!blackPlaysFirst) {
1734                 startedFromPositionFile = TRUE;
1735                 CopyBoard(filePosition, boards[0]);
1736             }
1737         }
1738         if (initialMode == AnalyzeMode) {
1739           if (appData.noChessProgram) {
1740             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1741             return;
1742           }
1743           if (appData.icsActive) {
1744             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1745             return;
1746           }
1747           AnalyzeModeEvent();
1748         } else if (initialMode == AnalyzeFile) {
1749           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1750           ShowThinkingEvent();
1751           AnalyzeFileEvent();
1752           AnalysisPeriodicEvent(1);
1753         } else if (initialMode == MachinePlaysWhite) {
1754           if (appData.noChessProgram) {
1755             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1756                               0, 2);
1757             return;
1758           }
1759           if (appData.icsActive) {
1760             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1761                               0, 2);
1762             return;
1763           }
1764           MachineWhiteEvent();
1765         } else if (initialMode == MachinePlaysBlack) {
1766           if (appData.noChessProgram) {
1767             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1768                               0, 2);
1769             return;
1770           }
1771           if (appData.icsActive) {
1772             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1773                               0, 2);
1774             return;
1775           }
1776           MachineBlackEvent();
1777         } else if (initialMode == TwoMachinesPlay) {
1778           if (appData.noChessProgram) {
1779             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1780                               0, 2);
1781             return;
1782           }
1783           if (appData.icsActive) {
1784             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1785                               0, 2);
1786             return;
1787           }
1788           TwoMachinesEvent();
1789         } else if (initialMode == EditGame) {
1790           EditGameEvent();
1791         } else if (initialMode == EditPosition) {
1792           EditPositionEvent();
1793         } else if (initialMode == Training) {
1794           if (*appData.loadGameFile == NULLCHAR) {
1795             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1796             return;
1797           }
1798           TrainingEvent();
1799         }
1800     }
1801 }
1802
1803 void
1804 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1805 {
1806     DisplayBook(current+1);
1807
1808     MoveHistorySet( movelist, first, last, current, pvInfoList );
1809
1810     EvalGraphSet( first, last, current, pvInfoList );
1811
1812     MakeEngineOutputTitle();
1813 }
1814
1815 /*
1816  * Establish will establish a contact to a remote host.port.
1817  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1818  *  used to talk to the host.
1819  * Returns 0 if okay, error code if not.
1820  */
1821 int
1822 establish ()
1823 {
1824     char buf[MSG_SIZ];
1825
1826     if (*appData.icsCommPort != NULLCHAR) {
1827         /* Talk to the host through a serial comm port */
1828         return OpenCommPort(appData.icsCommPort, &icsPR);
1829
1830     } else if (*appData.gateway != NULLCHAR) {
1831         if (*appData.remoteShell == NULLCHAR) {
1832             /* Use the rcmd protocol to run telnet program on a gateway host */
1833             snprintf(buf, sizeof(buf), "%s %s %s",
1834                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1835             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1836
1837         } else {
1838             /* Use the rsh program to run telnet program on a gateway host */
1839             if (*appData.remoteUser == NULLCHAR) {
1840                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1841                         appData.gateway, appData.telnetProgram,
1842                         appData.icsHost, appData.icsPort);
1843             } else {
1844                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1845                         appData.remoteShell, appData.gateway,
1846                         appData.remoteUser, appData.telnetProgram,
1847                         appData.icsHost, appData.icsPort);
1848             }
1849             return StartChildProcess(buf, "", &icsPR);
1850
1851         }
1852     } else if (appData.useTelnet) {
1853         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1854
1855     } else {
1856         /* TCP socket interface differs somewhat between
1857            Unix and NT; handle details in the front end.
1858            */
1859         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1860     }
1861 }
1862
1863 void
1864 EscapeExpand (char *p, char *q)
1865 {       // [HGM] initstring: routine to shape up string arguments
1866         while(*p++ = *q++) if(p[-1] == '\\')
1867             switch(*q++) {
1868                 case 'n': p[-1] = '\n'; break;
1869                 case 'r': p[-1] = '\r'; break;
1870                 case 't': p[-1] = '\t'; break;
1871                 case '\\': p[-1] = '\\'; break;
1872                 case 0: *p = 0; return;
1873                 default: p[-1] = q[-1]; break;
1874             }
1875 }
1876
1877 void
1878 show_bytes (FILE *fp, char *buf, int count)
1879 {
1880     while (count--) {
1881         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1882             fprintf(fp, "\\%03o", *buf & 0xff);
1883         } else {
1884             putc(*buf, fp);
1885         }
1886         buf++;
1887     }
1888     fflush(fp);
1889 }
1890
1891 /* Returns an errno value */
1892 int
1893 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1894 {
1895     char buf[8192], *p, *q, *buflim;
1896     int left, newcount, outcount;
1897
1898     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1899         *appData.gateway != NULLCHAR) {
1900         if (appData.debugMode) {
1901             fprintf(debugFP, ">ICS: ");
1902             show_bytes(debugFP, message, count);
1903             fprintf(debugFP, "\n");
1904         }
1905         return OutputToProcess(pr, message, count, outError);
1906     }
1907
1908     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1909     p = message;
1910     q = buf;
1911     left = count;
1912     newcount = 0;
1913     while (left) {
1914         if (q >= buflim) {
1915             if (appData.debugMode) {
1916                 fprintf(debugFP, ">ICS: ");
1917                 show_bytes(debugFP, buf, newcount);
1918                 fprintf(debugFP, "\n");
1919             }
1920             outcount = OutputToProcess(pr, buf, newcount, outError);
1921             if (outcount < newcount) return -1; /* to be sure */
1922             q = buf;
1923             newcount = 0;
1924         }
1925         if (*p == '\n') {
1926             *q++ = '\r';
1927             newcount++;
1928         } else if (((unsigned char) *p) == TN_IAC) {
1929             *q++ = (char) TN_IAC;
1930             newcount ++;
1931         }
1932         *q++ = *p++;
1933         newcount++;
1934         left--;
1935     }
1936     if (appData.debugMode) {
1937         fprintf(debugFP, ">ICS: ");
1938         show_bytes(debugFP, buf, newcount);
1939         fprintf(debugFP, "\n");
1940     }
1941     outcount = OutputToProcess(pr, buf, newcount, outError);
1942     if (outcount < newcount) return -1; /* to be sure */
1943     return count;
1944 }
1945
1946 void
1947 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1948 {
1949     int outError, outCount;
1950     static int gotEof = 0;
1951     static FILE *ini;
1952
1953     /* Pass data read from player on to ICS */
1954     if (count > 0) {
1955         gotEof = 0;
1956         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1957         if (outCount < count) {
1958             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1959         }
1960         if(have_sent_ICS_logon == 2) {
1961           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1962             fprintf(ini, "%s", message);
1963             have_sent_ICS_logon = 3;
1964           } else
1965             have_sent_ICS_logon = 1;
1966         } else if(have_sent_ICS_logon == 3) {
1967             fprintf(ini, "%s", message);
1968             fclose(ini);
1969           have_sent_ICS_logon = 1;
1970         }
1971     } else if (count < 0) {
1972         RemoveInputSource(isr);
1973         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1974     } else if (gotEof++ > 0) {
1975         RemoveInputSource(isr);
1976         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1977     }
1978 }
1979
1980 void
1981 KeepAlive ()
1982 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1983     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1984     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1985     SendToICS("date\n");
1986     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1987 }
1988
1989 /* added routine for printf style output to ics */
1990 void
1991 ics_printf (char *format, ...)
1992 {
1993     char buffer[MSG_SIZ];
1994     va_list args;
1995
1996     va_start(args, format);
1997     vsnprintf(buffer, sizeof(buffer), format, args);
1998     buffer[sizeof(buffer)-1] = '\0';
1999     SendToICS(buffer);
2000     va_end(args);
2001 }
2002
2003 void
2004 SendToICS (char *s)
2005 {
2006     int count, outCount, outError;
2007
2008     if (icsPR == NoProc) return;
2009
2010     count = strlen(s);
2011     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2012     if (outCount < count) {
2013         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2014     }
2015 }
2016
2017 /* This is used for sending logon scripts to the ICS. Sending
2018    without a delay causes problems when using timestamp on ICC
2019    (at least on my machine). */
2020 void
2021 SendToICSDelayed (char *s, long msdelay)
2022 {
2023     int count, outCount, outError;
2024
2025     if (icsPR == NoProc) return;
2026
2027     count = strlen(s);
2028     if (appData.debugMode) {
2029         fprintf(debugFP, ">ICS: ");
2030         show_bytes(debugFP, s, count);
2031         fprintf(debugFP, "\n");
2032     }
2033     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2034                                       msdelay);
2035     if (outCount < count) {
2036         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2037     }
2038 }
2039
2040
2041 /* Remove all highlighting escape sequences in s
2042    Also deletes any suffix starting with '('
2043    */
2044 char *
2045 StripHighlightAndTitle (char *s)
2046 {
2047     static char retbuf[MSG_SIZ];
2048     char *p = retbuf;
2049
2050     while (*s != NULLCHAR) {
2051         while (*s == '\033') {
2052             while (*s != NULLCHAR && !isalpha(*s)) s++;
2053             if (*s != NULLCHAR) s++;
2054         }
2055         while (*s != NULLCHAR && *s != '\033') {
2056             if (*s == '(' || *s == '[') {
2057                 *p = NULLCHAR;
2058                 return retbuf;
2059             }
2060             *p++ = *s++;
2061         }
2062     }
2063     *p = NULLCHAR;
2064     return retbuf;
2065 }
2066
2067 /* Remove all highlighting escape sequences in s */
2068 char *
2069 StripHighlight (char *s)
2070 {
2071     static char retbuf[MSG_SIZ];
2072     char *p = retbuf;
2073
2074     while (*s != NULLCHAR) {
2075         while (*s == '\033') {
2076             while (*s != NULLCHAR && !isalpha(*s)) s++;
2077             if (*s != NULLCHAR) s++;
2078         }
2079         while (*s != NULLCHAR && *s != '\033') {
2080             *p++ = *s++;
2081         }
2082     }
2083     *p = NULLCHAR;
2084     return retbuf;
2085 }
2086
2087 char engineVariant[MSG_SIZ];
2088 char *variantNames[] = VARIANT_NAMES;
2089 char *
2090 VariantName (VariantClass v)
2091 {
2092     if(v == VariantUnknown || *engineVariant) return engineVariant;
2093     return variantNames[v];
2094 }
2095
2096
2097 /* Identify a variant from the strings the chess servers use or the
2098    PGN Variant tag names we use. */
2099 VariantClass
2100 StringToVariant (char *e)
2101 {
2102     char *p;
2103     int wnum = -1;
2104     VariantClass v = VariantNormal;
2105     int i, found = FALSE;
2106     char buf[MSG_SIZ];
2107     int len;
2108
2109     if (!e) return v;
2110
2111     /* [HGM] skip over optional board-size prefixes */
2112     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2113         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2114         while( *e++ != '_');
2115     }
2116
2117     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2118         v = VariantNormal;
2119         found = TRUE;
2120     } else
2121     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2122       if (p = StrCaseStr(e, variantNames[i])) {
2123         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2124         v = (VariantClass) i;
2125         found = TRUE;
2126         break;
2127       }
2128     }
2129
2130     if (!found) {
2131       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2132           || StrCaseStr(e, "wild/fr")
2133           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2134         v = VariantFischeRandom;
2135       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2136                  (i = 1, p = StrCaseStr(e, "w"))) {
2137         p += i;
2138         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2139         if (isdigit(*p)) {
2140           wnum = atoi(p);
2141         } else {
2142           wnum = -1;
2143         }
2144         switch (wnum) {
2145         case 0: /* FICS only, actually */
2146         case 1:
2147           /* Castling legal even if K starts on d-file */
2148           v = VariantWildCastle;
2149           break;
2150         case 2:
2151         case 3:
2152         case 4:
2153           /* Castling illegal even if K & R happen to start in
2154              normal positions. */
2155           v = VariantNoCastle;
2156           break;
2157         case 5:
2158         case 7:
2159         case 8:
2160         case 10:
2161         case 11:
2162         case 12:
2163         case 13:
2164         case 14:
2165         case 15:
2166         case 18:
2167         case 19:
2168           /* Castling legal iff K & R start in normal positions */
2169           v = VariantNormal;
2170           break;
2171         case 6:
2172         case 20:
2173         case 21:
2174           /* Special wilds for position setup; unclear what to do here */
2175           v = VariantLoadable;
2176           break;
2177         case 9:
2178           /* Bizarre ICC game */
2179           v = VariantTwoKings;
2180           break;
2181         case 16:
2182           v = VariantKriegspiel;
2183           break;
2184         case 17:
2185           v = VariantLosers;
2186           break;
2187         case 22:
2188           v = VariantFischeRandom;
2189           break;
2190         case 23:
2191           v = VariantCrazyhouse;
2192           break;
2193         case 24:
2194           v = VariantBughouse;
2195           break;
2196         case 25:
2197           v = Variant3Check;
2198           break;
2199         case 26:
2200           /* Not quite the same as FICS suicide! */
2201           v = VariantGiveaway;
2202           break;
2203         case 27:
2204           v = VariantAtomic;
2205           break;
2206         case 28:
2207           v = VariantShatranj;
2208           break;
2209
2210         /* Temporary names for future ICC types.  The name *will* change in
2211            the next xboard/WinBoard release after ICC defines it. */
2212         case 29:
2213           v = Variant29;
2214           break;
2215         case 30:
2216           v = Variant30;
2217           break;
2218         case 31:
2219           v = Variant31;
2220           break;
2221         case 32:
2222           v = Variant32;
2223           break;
2224         case 33:
2225           v = Variant33;
2226           break;
2227         case 34:
2228           v = Variant34;
2229           break;
2230         case 35:
2231           v = Variant35;
2232           break;
2233         case 36:
2234           v = Variant36;
2235           break;
2236         case 37:
2237           v = VariantShogi;
2238           break;
2239         case 38:
2240           v = VariantXiangqi;
2241           break;
2242         case 39:
2243           v = VariantCourier;
2244           break;
2245         case 40:
2246           v = VariantGothic;
2247           break;
2248         case 41:
2249           v = VariantCapablanca;
2250           break;
2251         case 42:
2252           v = VariantKnightmate;
2253           break;
2254         case 43:
2255           v = VariantFairy;
2256           break;
2257         case 44:
2258           v = VariantCylinder;
2259           break;
2260         case 45:
2261           v = VariantFalcon;
2262           break;
2263         case 46:
2264           v = VariantCapaRandom;
2265           break;
2266         case 47:
2267           v = VariantBerolina;
2268           break;
2269         case 48:
2270           v = VariantJanus;
2271           break;
2272         case 49:
2273           v = VariantSuper;
2274           break;
2275         case 50:
2276           v = VariantGreat;
2277           break;
2278         case -1:
2279           /* Found "wild" or "w" in the string but no number;
2280              must assume it's normal chess. */
2281           v = VariantNormal;
2282           break;
2283         default:
2284           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2285           if( (len >= MSG_SIZ) && appData.debugMode )
2286             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2287
2288           DisplayError(buf, 0);
2289           v = VariantUnknown;
2290           break;
2291         }
2292       }
2293     }
2294     if (appData.debugMode) {
2295       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2296               e, wnum, VariantName(v));
2297     }
2298     return v;
2299 }
2300
2301 static int leftover_start = 0, leftover_len = 0;
2302 char star_match[STAR_MATCH_N][MSG_SIZ];
2303
2304 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2305    advance *index beyond it, and set leftover_start to the new value of
2306    *index; else return FALSE.  If pattern contains the character '*', it
2307    matches any sequence of characters not containing '\r', '\n', or the
2308    character following the '*' (if any), and the matched sequence(s) are
2309    copied into star_match.
2310    */
2311 int
2312 looking_at ( char *buf, int *index, char *pattern)
2313 {
2314     char *bufp = &buf[*index], *patternp = pattern;
2315     int star_count = 0;
2316     char *matchp = star_match[0];
2317
2318     for (;;) {
2319         if (*patternp == NULLCHAR) {
2320             *index = leftover_start = bufp - buf;
2321             *matchp = NULLCHAR;
2322             return TRUE;
2323         }
2324         if (*bufp == NULLCHAR) return FALSE;
2325         if (*patternp == '*') {
2326             if (*bufp == *(patternp + 1)) {
2327                 *matchp = NULLCHAR;
2328                 matchp = star_match[++star_count];
2329                 patternp += 2;
2330                 bufp++;
2331                 continue;
2332             } else if (*bufp == '\n' || *bufp == '\r') {
2333                 patternp++;
2334                 if (*patternp == NULLCHAR)
2335                   continue;
2336                 else
2337                   return FALSE;
2338             } else {
2339                 *matchp++ = *bufp++;
2340                 continue;
2341             }
2342         }
2343         if (*patternp != *bufp) return FALSE;
2344         patternp++;
2345         bufp++;
2346     }
2347 }
2348
2349 void
2350 SendToPlayer (char *data, int length)
2351 {
2352     int error, outCount;
2353     outCount = OutputToProcess(NoProc, data, length, &error);
2354     if (outCount < length) {
2355         DisplayFatalError(_("Error writing to display"), error, 1);
2356     }
2357 }
2358
2359 void
2360 PackHolding (char packed[], char *holding)
2361 {
2362     char *p = holding;
2363     char *q = packed;
2364     int runlength = 0;
2365     int curr = 9999;
2366     do {
2367         if (*p == curr) {
2368             runlength++;
2369         } else {
2370             switch (runlength) {
2371               case 0:
2372                 break;
2373               case 1:
2374                 *q++ = curr;
2375                 break;
2376               case 2:
2377                 *q++ = curr;
2378                 *q++ = curr;
2379                 break;
2380               default:
2381                 sprintf(q, "%d", runlength);
2382                 while (*q) q++;
2383                 *q++ = curr;
2384                 break;
2385             }
2386             runlength = 1;
2387             curr = *p;
2388         }
2389     } while (*p++);
2390     *q = NULLCHAR;
2391 }
2392
2393 /* Telnet protocol requests from the front end */
2394 void
2395 TelnetRequest (unsigned char ddww, unsigned char option)
2396 {
2397     unsigned char msg[3];
2398     int outCount, outError;
2399
2400     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2401
2402     if (appData.debugMode) {
2403         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2404         switch (ddww) {
2405           case TN_DO:
2406             ddwwStr = "DO";
2407             break;
2408           case TN_DONT:
2409             ddwwStr = "DONT";
2410             break;
2411           case TN_WILL:
2412             ddwwStr = "WILL";
2413             break;
2414           case TN_WONT:
2415             ddwwStr = "WONT";
2416             break;
2417           default:
2418             ddwwStr = buf1;
2419             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2420             break;
2421         }
2422         switch (option) {
2423           case TN_ECHO:
2424             optionStr = "ECHO";
2425             break;
2426           default:
2427             optionStr = buf2;
2428             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2429             break;
2430         }
2431         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2432     }
2433     msg[0] = TN_IAC;
2434     msg[1] = ddww;
2435     msg[2] = option;
2436     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2437     if (outCount < 3) {
2438         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2439     }
2440 }
2441
2442 void
2443 DoEcho ()
2444 {
2445     if (!appData.icsActive) return;
2446     TelnetRequest(TN_DO, TN_ECHO);
2447 }
2448
2449 void
2450 DontEcho ()
2451 {
2452     if (!appData.icsActive) return;
2453     TelnetRequest(TN_DONT, TN_ECHO);
2454 }
2455
2456 void
2457 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2458 {
2459     /* put the holdings sent to us by the server on the board holdings area */
2460     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2461     char p;
2462     ChessSquare piece;
2463
2464     if(gameInfo.holdingsWidth < 2)  return;
2465     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2466         return; // prevent overwriting by pre-board holdings
2467
2468     if( (int)lowestPiece >= BlackPawn ) {
2469         holdingsColumn = 0;
2470         countsColumn = 1;
2471         holdingsStartRow = BOARD_HEIGHT-1;
2472         direction = -1;
2473     } else {
2474         holdingsColumn = BOARD_WIDTH-1;
2475         countsColumn = BOARD_WIDTH-2;
2476         holdingsStartRow = 0;
2477         direction = 1;
2478     }
2479
2480     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2481         board[i][holdingsColumn] = EmptySquare;
2482         board[i][countsColumn]   = (ChessSquare) 0;
2483     }
2484     while( (p=*holdings++) != NULLCHAR ) {
2485         piece = CharToPiece( ToUpper(p) );
2486         if(piece == EmptySquare) continue;
2487         /*j = (int) piece - (int) WhitePawn;*/
2488         j = PieceToNumber(piece);
2489         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2490         if(j < 0) continue;               /* should not happen */
2491         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2492         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2493         board[holdingsStartRow+j*direction][countsColumn]++;
2494     }
2495 }
2496
2497
2498 void
2499 VariantSwitch (Board board, VariantClass newVariant)
2500 {
2501    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2502    static Board oldBoard;
2503
2504    startedFromPositionFile = FALSE;
2505    if(gameInfo.variant == newVariant) return;
2506
2507    /* [HGM] This routine is called each time an assignment is made to
2508     * gameInfo.variant during a game, to make sure the board sizes
2509     * are set to match the new variant. If that means adding or deleting
2510     * holdings, we shift the playing board accordingly
2511     * This kludge is needed because in ICS observe mode, we get boards
2512     * of an ongoing game without knowing the variant, and learn about the
2513     * latter only later. This can be because of the move list we requested,
2514     * in which case the game history is refilled from the beginning anyway,
2515     * but also when receiving holdings of a crazyhouse game. In the latter
2516     * case we want to add those holdings to the already received position.
2517     */
2518
2519
2520    if (appData.debugMode) {
2521      fprintf(debugFP, "Switch board from %s to %s\n",
2522              VariantName(gameInfo.variant), VariantName(newVariant));
2523      setbuf(debugFP, NULL);
2524    }
2525    shuffleOpenings = 0;       /* [HGM] shuffle */
2526    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2527    switch(newVariant)
2528      {
2529      case VariantShogi:
2530        newWidth = 9;  newHeight = 9;
2531        gameInfo.holdingsSize = 7;
2532      case VariantBughouse:
2533      case VariantCrazyhouse:
2534        newHoldingsWidth = 2; break;
2535      case VariantGreat:
2536        newWidth = 10;
2537      case VariantSuper:
2538        newHoldingsWidth = 2;
2539        gameInfo.holdingsSize = 8;
2540        break;
2541      case VariantGothic:
2542      case VariantCapablanca:
2543      case VariantCapaRandom:
2544        newWidth = 10;
2545      default:
2546        newHoldingsWidth = gameInfo.holdingsSize = 0;
2547      };
2548
2549    if(newWidth  != gameInfo.boardWidth  ||
2550       newHeight != gameInfo.boardHeight ||
2551       newHoldingsWidth != gameInfo.holdingsWidth ) {
2552
2553      /* shift position to new playing area, if needed */
2554      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2555        for(i=0; i<BOARD_HEIGHT; i++)
2556          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2557            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2558              board[i][j];
2559        for(i=0; i<newHeight; i++) {
2560          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2561          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2562        }
2563      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2564        for(i=0; i<BOARD_HEIGHT; i++)
2565          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2566            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2567              board[i][j];
2568      }
2569      board[HOLDINGS_SET] = 0;
2570      gameInfo.boardWidth  = newWidth;
2571      gameInfo.boardHeight = newHeight;
2572      gameInfo.holdingsWidth = newHoldingsWidth;
2573      gameInfo.variant = newVariant;
2574      InitDrawingSizes(-2, 0);
2575    } else gameInfo.variant = newVariant;
2576    CopyBoard(oldBoard, board);   // remember correctly formatted board
2577      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2578    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2579 }
2580
2581 static int loggedOn = FALSE;
2582
2583 /*-- Game start info cache: --*/
2584 int gs_gamenum;
2585 char gs_kind[MSG_SIZ];
2586 static char player1Name[128] = "";
2587 static char player2Name[128] = "";
2588 static char cont_seq[] = "\n\\   ";
2589 static int player1Rating = -1;
2590 static int player2Rating = -1;
2591 /*----------------------------*/
2592
2593 ColorClass curColor = ColorNormal;
2594 int suppressKibitz = 0;
2595
2596 // [HGM] seekgraph
2597 Boolean soughtPending = FALSE;
2598 Boolean seekGraphUp;
2599 #define MAX_SEEK_ADS 200
2600 #define SQUARE 0x80
2601 char *seekAdList[MAX_SEEK_ADS];
2602 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2603 float tcList[MAX_SEEK_ADS];
2604 char colorList[MAX_SEEK_ADS];
2605 int nrOfSeekAds = 0;
2606 int minRating = 1010, maxRating = 2800;
2607 int hMargin = 10, vMargin = 20, h, w;
2608 extern int squareSize, lineGap;
2609
2610 void
2611 PlotSeekAd (int i)
2612 {
2613         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2614         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2615         if(r < minRating+100 && r >=0 ) r = minRating+100;
2616         if(r > maxRating) r = maxRating;
2617         if(tc < 1.f) tc = 1.f;
2618         if(tc > 95.f) tc = 95.f;
2619         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2620         y = ((double)r - minRating)/(maxRating - minRating)
2621             * (h-vMargin-squareSize/8-1) + vMargin;
2622         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2623         if(strstr(seekAdList[i], " u ")) color = 1;
2624         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2625            !strstr(seekAdList[i], "bullet") &&
2626            !strstr(seekAdList[i], "blitz") &&
2627            !strstr(seekAdList[i], "standard") ) color = 2;
2628         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2629         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2630 }
2631
2632 void
2633 PlotSingleSeekAd (int i)
2634 {
2635         PlotSeekAd(i);
2636 }
2637
2638 void
2639 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2640 {
2641         char buf[MSG_SIZ], *ext = "";
2642         VariantClass v = StringToVariant(type);
2643         if(strstr(type, "wild")) {
2644             ext = type + 4; // append wild number
2645             if(v == VariantFischeRandom) type = "chess960"; else
2646             if(v == VariantLoadable) type = "setup"; else
2647             type = VariantName(v);
2648         }
2649         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2650         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2651             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2652             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2653             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2654             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2655             seekNrList[nrOfSeekAds] = nr;
2656             zList[nrOfSeekAds] = 0;
2657             seekAdList[nrOfSeekAds++] = StrSave(buf);
2658             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2659         }
2660 }
2661
2662 void
2663 EraseSeekDot (int i)
2664 {
2665     int x = xList[i], y = yList[i], d=squareSize/4, k;
2666     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2667     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2668     // now replot every dot that overlapped
2669     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2670         int xx = xList[k], yy = yList[k];
2671         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2672             DrawSeekDot(xx, yy, colorList[k]);
2673     }
2674 }
2675
2676 void
2677 RemoveSeekAd (int nr)
2678 {
2679         int i;
2680         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2681             EraseSeekDot(i);
2682             if(seekAdList[i]) free(seekAdList[i]);
2683             seekAdList[i] = seekAdList[--nrOfSeekAds];
2684             seekNrList[i] = seekNrList[nrOfSeekAds];
2685             ratingList[i] = ratingList[nrOfSeekAds];
2686             colorList[i]  = colorList[nrOfSeekAds];
2687             tcList[i] = tcList[nrOfSeekAds];
2688             xList[i]  = xList[nrOfSeekAds];
2689             yList[i]  = yList[nrOfSeekAds];
2690             zList[i]  = zList[nrOfSeekAds];
2691             seekAdList[nrOfSeekAds] = NULL;
2692             break;
2693         }
2694 }
2695
2696 Boolean
2697 MatchSoughtLine (char *line)
2698 {
2699     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2700     int nr, base, inc, u=0; char dummy;
2701
2702     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2703        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2704        (u=1) &&
2705        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2706         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2707         // match: compact and save the line
2708         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2709         return TRUE;
2710     }
2711     return FALSE;
2712 }
2713
2714 int
2715 DrawSeekGraph ()
2716 {
2717     int i;
2718     if(!seekGraphUp) return FALSE;
2719     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2720     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2721
2722     DrawSeekBackground(0, 0, w, h);
2723     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2724     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2725     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2726         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2727         yy = h-1-yy;
2728         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2729         if(i%500 == 0) {
2730             char buf[MSG_SIZ];
2731             snprintf(buf, MSG_SIZ, "%d", i);
2732             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2733         }
2734     }
2735     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2736     for(i=1; i<100; i+=(i<10?1:5)) {
2737         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2738         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2739         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2740             char buf[MSG_SIZ];
2741             snprintf(buf, MSG_SIZ, "%d", i);
2742             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2743         }
2744     }
2745     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2746     return TRUE;
2747 }
2748
2749 int
2750 SeekGraphClick (ClickType click, int x, int y, int moving)
2751 {
2752     static int lastDown = 0, displayed = 0, lastSecond;
2753     if(y < 0) return FALSE;
2754     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2755         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2756         if(!seekGraphUp) return FALSE;
2757         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2758         DrawPosition(TRUE, NULL);
2759         return TRUE;
2760     }
2761     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2762         if(click == Release || moving) return FALSE;
2763         nrOfSeekAds = 0;
2764         soughtPending = TRUE;
2765         SendToICS(ics_prefix);
2766         SendToICS("sought\n"); // should this be "sought all"?
2767     } else { // issue challenge based on clicked ad
2768         int dist = 10000; int i, closest = 0, second = 0;
2769         for(i=0; i<nrOfSeekAds; i++) {
2770             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2771             if(d < dist) { dist = d; closest = i; }
2772             second += (d - zList[i] < 120); // count in-range ads
2773             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2774         }
2775         if(dist < 120) {
2776             char buf[MSG_SIZ];
2777             second = (second > 1);
2778             if(displayed != closest || second != lastSecond) {
2779                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2780                 lastSecond = second; displayed = closest;
2781             }
2782             if(click == Press) {
2783                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2784                 lastDown = closest;
2785                 return TRUE;
2786             } // on press 'hit', only show info
2787             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2788             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2789             SendToICS(ics_prefix);
2790             SendToICS(buf);
2791             return TRUE; // let incoming board of started game pop down the graph
2792         } else if(click == Release) { // release 'miss' is ignored
2793             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2794             if(moving == 2) { // right up-click
2795                 nrOfSeekAds = 0; // refresh graph
2796                 soughtPending = TRUE;
2797                 SendToICS(ics_prefix);
2798                 SendToICS("sought\n"); // should this be "sought all"?
2799             }
2800             return TRUE;
2801         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2802         // press miss or release hit 'pop down' seek graph
2803         seekGraphUp = FALSE;
2804         DrawPosition(TRUE, NULL);
2805     }
2806     return TRUE;
2807 }
2808
2809 void
2810 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2811 {
2812 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2813 #define STARTED_NONE 0
2814 #define STARTED_MOVES 1
2815 #define STARTED_BOARD 2
2816 #define STARTED_OBSERVE 3
2817 #define STARTED_HOLDINGS 4
2818 #define STARTED_CHATTER 5
2819 #define STARTED_COMMENT 6
2820 #define STARTED_MOVES_NOHIDE 7
2821
2822     static int started = STARTED_NONE;
2823     static char parse[20000];
2824     static int parse_pos = 0;
2825     static char buf[BUF_SIZE + 1];
2826     static int firstTime = TRUE, intfSet = FALSE;
2827     static ColorClass prevColor = ColorNormal;
2828     static int savingComment = FALSE;
2829     static int cmatch = 0; // continuation sequence match
2830     char *bp;
2831     char str[MSG_SIZ];
2832     int i, oldi;
2833     int buf_len;
2834     int next_out;
2835     int tkind;
2836     int backup;    /* [DM] For zippy color lines */
2837     char *p;
2838     char talker[MSG_SIZ]; // [HGM] chat
2839     int channel, collective=0;
2840
2841     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2842
2843     if (appData.debugMode) {
2844       if (!error) {
2845         fprintf(debugFP, "<ICS: ");
2846         show_bytes(debugFP, data, count);
2847         fprintf(debugFP, "\n");
2848       }
2849     }
2850
2851     if (appData.debugMode) { int f = forwardMostMove;
2852         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2853                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2854                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2855     }
2856     if (count > 0) {
2857         /* If last read ended with a partial line that we couldn't parse,
2858            prepend it to the new read and try again. */
2859         if (leftover_len > 0) {
2860             for (i=0; i<leftover_len; i++)
2861               buf[i] = buf[leftover_start + i];
2862         }
2863
2864     /* copy new characters into the buffer */
2865     bp = buf + leftover_len;
2866     buf_len=leftover_len;
2867     for (i=0; i<count; i++)
2868     {
2869         // ignore these
2870         if (data[i] == '\r')
2871             continue;
2872
2873         // join lines split by ICS?
2874         if (!appData.noJoin)
2875         {
2876             /*
2877                 Joining just consists of finding matches against the
2878                 continuation sequence, and discarding that sequence
2879                 if found instead of copying it.  So, until a match
2880                 fails, there's nothing to do since it might be the
2881                 complete sequence, and thus, something we don't want
2882                 copied.
2883             */
2884             if (data[i] == cont_seq[cmatch])
2885             {
2886                 cmatch++;
2887                 if (cmatch == strlen(cont_seq))
2888                 {
2889                     cmatch = 0; // complete match.  just reset the counter
2890
2891                     /*
2892                         it's possible for the ICS to not include the space
2893                         at the end of the last word, making our [correct]
2894                         join operation fuse two separate words.  the server
2895                         does this when the space occurs at the width setting.
2896                     */
2897                     if (!buf_len || buf[buf_len-1] != ' ')
2898                     {
2899                         *bp++ = ' ';
2900                         buf_len++;
2901                     }
2902                 }
2903                 continue;
2904             }
2905             else if (cmatch)
2906             {
2907                 /*
2908                     match failed, so we have to copy what matched before
2909                     falling through and copying this character.  In reality,
2910                     this will only ever be just the newline character, but
2911                     it doesn't hurt to be precise.
2912                 */
2913                 strncpy(bp, cont_seq, cmatch);
2914                 bp += cmatch;
2915                 buf_len += cmatch;
2916                 cmatch = 0;
2917             }
2918         }
2919
2920         // copy this char
2921         *bp++ = data[i];
2922         buf_len++;
2923     }
2924
2925         buf[buf_len] = NULLCHAR;
2926 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2927         next_out = 0;
2928         leftover_start = 0;
2929
2930         i = 0;
2931         while (i < buf_len) {
2932             /* Deal with part of the TELNET option negotiation
2933                protocol.  We refuse to do anything beyond the
2934                defaults, except that we allow the WILL ECHO option,
2935                which ICS uses to turn off password echoing when we are
2936                directly connected to it.  We reject this option
2937                if localLineEditing mode is on (always on in xboard)
2938                and we are talking to port 23, which might be a real
2939                telnet server that will try to keep WILL ECHO on permanently.
2940              */
2941             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2942                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2943                 unsigned char option;
2944                 oldi = i;
2945                 switch ((unsigned char) buf[++i]) {
2946                   case TN_WILL:
2947                     if (appData.debugMode)
2948                       fprintf(debugFP, "\n<WILL ");
2949                     switch (option = (unsigned char) buf[++i]) {
2950                       case TN_ECHO:
2951                         if (appData.debugMode)
2952                           fprintf(debugFP, "ECHO ");
2953                         /* Reply only if this is a change, according
2954                            to the protocol rules. */
2955                         if (remoteEchoOption) break;
2956                         if (appData.localLineEditing &&
2957                             atoi(appData.icsPort) == TN_PORT) {
2958                             TelnetRequest(TN_DONT, TN_ECHO);
2959                         } else {
2960                             EchoOff();
2961                             TelnetRequest(TN_DO, TN_ECHO);
2962                             remoteEchoOption = TRUE;
2963                         }
2964                         break;
2965                       default:
2966                         if (appData.debugMode)
2967                           fprintf(debugFP, "%d ", option);
2968                         /* Whatever this is, we don't want it. */
2969                         TelnetRequest(TN_DONT, option);
2970                         break;
2971                     }
2972                     break;
2973                   case TN_WONT:
2974                     if (appData.debugMode)
2975                       fprintf(debugFP, "\n<WONT ");
2976                     switch (option = (unsigned char) buf[++i]) {
2977                       case TN_ECHO:
2978                         if (appData.debugMode)
2979                           fprintf(debugFP, "ECHO ");
2980                         /* Reply only if this is a change, according
2981                            to the protocol rules. */
2982                         if (!remoteEchoOption) break;
2983                         EchoOn();
2984                         TelnetRequest(TN_DONT, TN_ECHO);
2985                         remoteEchoOption = FALSE;
2986                         break;
2987                       default:
2988                         if (appData.debugMode)
2989                           fprintf(debugFP, "%d ", (unsigned char) option);
2990                         /* Whatever this is, it must already be turned
2991                            off, because we never agree to turn on
2992                            anything non-default, so according to the
2993                            protocol rules, we don't reply. */
2994                         break;
2995                     }
2996                     break;
2997                   case TN_DO:
2998                     if (appData.debugMode)
2999                       fprintf(debugFP, "\n<DO ");
3000                     switch (option = (unsigned char) buf[++i]) {
3001                       default:
3002                         /* Whatever this is, we refuse to do it. */
3003                         if (appData.debugMode)
3004                           fprintf(debugFP, "%d ", option);
3005                         TelnetRequest(TN_WONT, option);
3006                         break;
3007                     }
3008                     break;
3009                   case TN_DONT:
3010                     if (appData.debugMode)
3011                       fprintf(debugFP, "\n<DONT ");
3012                     switch (option = (unsigned char) buf[++i]) {
3013                       default:
3014                         if (appData.debugMode)
3015                           fprintf(debugFP, "%d ", option);
3016                         /* Whatever this is, we are already not doing
3017                            it, because we never agree to do anything
3018                            non-default, so according to the protocol
3019                            rules, we don't reply. */
3020                         break;
3021                     }
3022                     break;
3023                   case TN_IAC:
3024                     if (appData.debugMode)
3025                       fprintf(debugFP, "\n<IAC ");
3026                     /* Doubled IAC; pass it through */
3027                     i--;
3028                     break;
3029                   default:
3030                     if (appData.debugMode)
3031                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3032                     /* Drop all other telnet commands on the floor */
3033                     break;
3034                 }
3035                 if (oldi > next_out)
3036                   SendToPlayer(&buf[next_out], oldi - next_out);
3037                 if (++i > next_out)
3038                   next_out = i;
3039                 continue;
3040             }
3041
3042             /* OK, this at least will *usually* work */
3043             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3044                 loggedOn = TRUE;
3045             }
3046
3047             if (loggedOn && !intfSet) {
3048                 if (ics_type == ICS_ICC) {
3049                   snprintf(str, MSG_SIZ,
3050                           "/set-quietly interface %s\n/set-quietly style 12\n",
3051                           programVersion);
3052                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3053                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3054                 } else if (ics_type == ICS_CHESSNET) {
3055                   snprintf(str, MSG_SIZ, "/style 12\n");
3056                 } else {
3057                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3058                   strcat(str, programVersion);
3059                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3060                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3061                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3062 #ifdef WIN32
3063                   strcat(str, "$iset nohighlight 1\n");
3064 #endif
3065                   strcat(str, "$iset lock 1\n$style 12\n");
3066                 }
3067                 SendToICS(str);
3068                 NotifyFrontendLogin();
3069                 intfSet = TRUE;
3070             }
3071
3072             if (started == STARTED_COMMENT) {
3073                 /* Accumulate characters in comment */
3074                 parse[parse_pos++] = buf[i];
3075                 if (buf[i] == '\n') {
3076                     parse[parse_pos] = NULLCHAR;
3077                     if(chattingPartner>=0) {
3078                         char mess[MSG_SIZ];
3079                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3080                         OutputChatMessage(chattingPartner, mess);
3081                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3082                             int p;
3083                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3084                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3085                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3086                                 OutputChatMessage(p, mess);
3087                                 break;
3088                             }
3089                         }
3090                         chattingPartner = -1;
3091                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3092                         collective = 0;
3093                     } else
3094                     if(!suppressKibitz) // [HGM] kibitz
3095                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3096                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3097                         int nrDigit = 0, nrAlph = 0, j;
3098                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3099                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3100                         parse[parse_pos] = NULLCHAR;
3101                         // try to be smart: if it does not look like search info, it should go to
3102                         // ICS interaction window after all, not to engine-output window.
3103                         for(j=0; j<parse_pos; j++) { // count letters and digits
3104                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3105                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3106                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3107                         }
3108                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3109                             int depth=0; float score;
3110                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3111                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3112                                 pvInfoList[forwardMostMove-1].depth = depth;
3113                                 pvInfoList[forwardMostMove-1].score = 100*score;
3114                             }
3115                             OutputKibitz(suppressKibitz, parse);
3116                         } else {
3117                             char tmp[MSG_SIZ];
3118                             if(gameMode == IcsObserving) // restore original ICS messages
3119                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3120                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3121                             else
3122                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3123                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3124                             SendToPlayer(tmp, strlen(tmp));
3125                         }
3126                         next_out = i+1; // [HGM] suppress printing in ICS window
3127                     }
3128                     started = STARTED_NONE;
3129                 } else {
3130                     /* Don't match patterns against characters in comment */
3131                     i++;
3132                     continue;
3133                 }
3134             }
3135             if (started == STARTED_CHATTER) {
3136                 if (buf[i] != '\n') {
3137                     /* Don't match patterns against characters in chatter */
3138                     i++;
3139                     continue;
3140                 }
3141                 started = STARTED_NONE;
3142                 if(suppressKibitz) next_out = i+1;
3143             }
3144
3145             /* Kludge to deal with rcmd protocol */
3146             if (firstTime && looking_at(buf, &i, "\001*")) {
3147                 DisplayFatalError(&buf[1], 0, 1);
3148                 continue;
3149             } else {
3150                 firstTime = FALSE;
3151             }
3152
3153             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3154                 ics_type = ICS_ICC;
3155                 ics_prefix = "/";
3156                 if (appData.debugMode)
3157                   fprintf(debugFP, "ics_type %d\n", ics_type);
3158                 continue;
3159             }
3160             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3161                 ics_type = ICS_FICS;
3162                 ics_prefix = "$";
3163                 if (appData.debugMode)
3164                   fprintf(debugFP, "ics_type %d\n", ics_type);
3165                 continue;
3166             }
3167             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3168                 ics_type = ICS_CHESSNET;
3169                 ics_prefix = "/";
3170                 if (appData.debugMode)
3171                   fprintf(debugFP, "ics_type %d\n", ics_type);
3172                 continue;
3173             }
3174
3175             if (!loggedOn &&
3176                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3177                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3178                  looking_at(buf, &i, "will be \"*\""))) {
3179               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3180               continue;
3181             }
3182
3183             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3184               char buf[MSG_SIZ];
3185               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3186               DisplayIcsInteractionTitle(buf);
3187               have_set_title = TRUE;
3188             }
3189
3190             /* skip finger notes */
3191             if (started == STARTED_NONE &&
3192                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3193                  (buf[i] == '1' && buf[i+1] == '0')) &&
3194                 buf[i+2] == ':' && buf[i+3] == ' ') {
3195               started = STARTED_CHATTER;
3196               i += 3;
3197               continue;
3198             }
3199
3200             oldi = i;
3201             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3202             if(appData.seekGraph) {
3203                 if(soughtPending && MatchSoughtLine(buf+i)) {
3204                     i = strstr(buf+i, "rated") - buf;
3205                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3206                     next_out = leftover_start = i;
3207                     started = STARTED_CHATTER;
3208                     suppressKibitz = TRUE;
3209                     continue;
3210                 }
3211                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3212                         && looking_at(buf, &i, "* ads displayed")) {
3213                     soughtPending = FALSE;
3214                     seekGraphUp = TRUE;
3215                     DrawSeekGraph();
3216                     continue;
3217                 }
3218                 if(appData.autoRefresh) {
3219                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3220                         int s = (ics_type == ICS_ICC); // ICC format differs
3221                         if(seekGraphUp)
3222                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3223                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3224                         looking_at(buf, &i, "*% "); // eat prompt
3225                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3226                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3227                         next_out = i; // suppress
3228                         continue;
3229                     }
3230                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3231                         char *p = star_match[0];
3232                         while(*p) {
3233                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3234                             while(*p && *p++ != ' '); // next
3235                         }
3236                         looking_at(buf, &i, "*% "); // eat prompt
3237                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3238                         next_out = i;
3239                         continue;
3240                     }
3241                 }
3242             }
3243
3244             /* skip formula vars */
3245             if (started == STARTED_NONE &&
3246                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3247               started = STARTED_CHATTER;
3248               i += 3;
3249               continue;
3250             }
3251
3252             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3253             if (appData.autoKibitz && started == STARTED_NONE &&
3254                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3255                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3256                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3257                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3258                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3259                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3260                         suppressKibitz = TRUE;
3261                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3262                         next_out = i;
3263                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3264                                 && (gameMode == IcsPlayingWhite)) ||
3265                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3266                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3267                             started = STARTED_CHATTER; // own kibitz we simply discard
3268                         else {
3269                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3270                             parse_pos = 0; parse[0] = NULLCHAR;
3271                             savingComment = TRUE;
3272                             suppressKibitz = gameMode != IcsObserving ? 2 :
3273                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3274                         }
3275                         continue;
3276                 } else
3277                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3278                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3279                          && atoi(star_match[0])) {
3280                     // suppress the acknowledgements of our own autoKibitz
3281                     char *p;
3282                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3283                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3284                     SendToPlayer(star_match[0], strlen(star_match[0]));
3285                     if(looking_at(buf, &i, "*% ")) // eat prompt
3286                         suppressKibitz = FALSE;
3287                     next_out = i;
3288                     continue;
3289                 }
3290             } // [HGM] kibitz: end of patch
3291
3292             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3293
3294             // [HGM] chat: intercept tells by users for which we have an open chat window
3295             channel = -1;
3296             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3297                                            looking_at(buf, &i, "* whispers:") ||
3298                                            looking_at(buf, &i, "* kibitzes:") ||
3299                                            looking_at(buf, &i, "* shouts:") ||
3300                                            looking_at(buf, &i, "* c-shouts:") ||
3301                                            looking_at(buf, &i, "--> * ") ||
3302                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3303                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3304                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3305                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3306                 int p;
3307                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3308                 chattingPartner = -1; collective = 0;
3309
3310                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3311                 for(p=0; p<MAX_CHAT; p++) {
3312                     collective = 1;
3313                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3314                     talker[0] = '['; strcat(talker, "] ");
3315                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3316                     chattingPartner = p; break;
3317                     }
3318                 } else
3319                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3320                 for(p=0; p<MAX_CHAT; p++) {
3321                     collective = 1;
3322                     if(!strcmp("kibitzes", chatPartner[p])) {
3323                         talker[0] = '['; strcat(talker, "] ");
3324                         chattingPartner = p; break;
3325                     }
3326                 } else
3327                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(!strcmp("whispers", chatPartner[p])) {
3331                         talker[0] = '['; strcat(talker, "] ");
3332                         chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3336                   if(buf[i-8] == '-' && buf[i-3] == 't')
3337                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3338                     collective = 1;
3339                     if(!strcmp("c-shouts", chatPartner[p])) {
3340                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3341                         chattingPartner = p; break;
3342                     }
3343                   }
3344                   if(chattingPartner < 0)
3345                   for(p=0; p<MAX_CHAT; p++) {
3346                     collective = 1;
3347                     if(!strcmp("shouts", chatPartner[p])) {
3348                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3349                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3350                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3351                         chattingPartner = p; break;
3352                     }
3353                   }
3354                 }
3355                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3356                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3357                     talker[0] = 0;
3358                     Colorize(ColorTell, FALSE);
3359                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3360                     collective |= 2;
3361                     chattingPartner = p; break;
3362                 }
3363                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3364                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3365                     started = STARTED_COMMENT;
3366                     parse_pos = 0; parse[0] = NULLCHAR;
3367                     savingComment = 3 + chattingPartner; // counts as TRUE
3368                     if(collective == 3) i = oldi; else {
3369                         suppressKibitz = TRUE;
3370                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3371                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3372                         continue;
3373                     }
3374                 }
3375             } // [HGM] chat: end of patch
3376
3377           backup = i;
3378             if (appData.zippyTalk || appData.zippyPlay) {
3379                 /* [DM] Backup address for color zippy lines */
3380 #if ZIPPY
3381                if (loggedOn == TRUE)
3382                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3383                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3384 #endif
3385             } // [DM] 'else { ' deleted
3386                 if (
3387                     /* Regular tells and says */
3388                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3389                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3390                     looking_at(buf, &i, "* says: ") ||
3391                     /* Don't color "message" or "messages" output */
3392                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3393                     looking_at(buf, &i, "*. * at *:*: ") ||
3394                     looking_at(buf, &i, "--* (*:*): ") ||
3395                     /* Message notifications (same color as tells) */
3396                     looking_at(buf, &i, "* has left a message ") ||
3397                     looking_at(buf, &i, "* just sent you a message:\n") ||
3398                     /* Whispers and kibitzes */
3399                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3400                     looking_at(buf, &i, "* kibitzes: ") ||
3401                     /* Channel tells */
3402                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3403
3404                   if (tkind == 1 && strchr(star_match[0], ':')) {
3405                       /* Avoid "tells you:" spoofs in channels */
3406                      tkind = 3;
3407                   }
3408                   if (star_match[0][0] == NULLCHAR ||
3409                       strchr(star_match[0], ' ') ||
3410                       (tkind == 3 && strchr(star_match[1], ' '))) {
3411                     /* Reject bogus matches */
3412                     i = oldi;
3413                   } else {
3414                     if (appData.colorize) {
3415                       if (oldi > next_out) {
3416                         SendToPlayer(&buf[next_out], oldi - next_out);
3417                         next_out = oldi;
3418                       }
3419                       switch (tkind) {
3420                       case 1:
3421                         Colorize(ColorTell, FALSE);
3422                         curColor = ColorTell;
3423                         break;
3424                       case 2:
3425                         Colorize(ColorKibitz, FALSE);
3426                         curColor = ColorKibitz;
3427                         break;
3428                       case 3:
3429                         p = strrchr(star_match[1], '(');
3430                         if (p == NULL) {
3431                           p = star_match[1];
3432                         } else {
3433                           p++;
3434                         }
3435                         if (atoi(p) == 1) {
3436                           Colorize(ColorChannel1, FALSE);
3437                           curColor = ColorChannel1;
3438                         } else {
3439                           Colorize(ColorChannel, FALSE);
3440                           curColor = ColorChannel;
3441                         }
3442                         break;
3443                       case 5:
3444                         curColor = ColorNormal;
3445                         break;
3446                       }
3447                     }
3448                     if (started == STARTED_NONE && appData.autoComment &&
3449                         (gameMode == IcsObserving ||
3450                          gameMode == IcsPlayingWhite ||
3451                          gameMode == IcsPlayingBlack)) {
3452                       parse_pos = i - oldi;
3453                       memcpy(parse, &buf[oldi], parse_pos);
3454                       parse[parse_pos] = NULLCHAR;
3455                       started = STARTED_COMMENT;
3456                       savingComment = TRUE;
3457                     } else if(collective != 3) {
3458                       started = STARTED_CHATTER;
3459                       savingComment = FALSE;
3460                     }
3461                     loggedOn = TRUE;
3462                     continue;
3463                   }
3464                 }
3465
3466                 if (looking_at(buf, &i, "* s-shouts: ") ||
3467                     looking_at(buf, &i, "* c-shouts: ")) {
3468                     if (appData.colorize) {
3469                         if (oldi > next_out) {
3470                             SendToPlayer(&buf[next_out], oldi - next_out);
3471                             next_out = oldi;
3472                         }
3473                         Colorize(ColorSShout, FALSE);
3474                         curColor = ColorSShout;
3475                     }
3476                     loggedOn = TRUE;
3477                     started = STARTED_CHATTER;
3478                     continue;
3479                 }
3480
3481                 if (looking_at(buf, &i, "--->")) {
3482                     loggedOn = TRUE;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "* shouts: ") ||
3487                     looking_at(buf, &i, "--> ")) {
3488                     if (appData.colorize) {
3489                         if (oldi > next_out) {
3490                             SendToPlayer(&buf[next_out], oldi - next_out);
3491                             next_out = oldi;
3492                         }
3493                         Colorize(ColorShout, FALSE);
3494                         curColor = ColorShout;
3495                     }
3496                     loggedOn = TRUE;
3497                     started = STARTED_CHATTER;
3498                     continue;
3499                 }
3500
3501                 if (looking_at( buf, &i, "Challenge:")) {
3502                     if (appData.colorize) {
3503                         if (oldi > next_out) {
3504                             SendToPlayer(&buf[next_out], oldi - next_out);
3505                             next_out = oldi;
3506                         }
3507                         Colorize(ColorChallenge, FALSE);
3508                         curColor = ColorChallenge;
3509                     }
3510                     loggedOn = TRUE;
3511                     continue;
3512                 }
3513
3514                 if (looking_at(buf, &i, "* offers you") ||
3515                     looking_at(buf, &i, "* offers to be") ||
3516                     looking_at(buf, &i, "* would like to") ||
3517                     looking_at(buf, &i, "* requests to") ||
3518                     looking_at(buf, &i, "Your opponent offers") ||
3519                     looking_at(buf, &i, "Your opponent requests")) {
3520
3521                     if (appData.colorize) {
3522                         if (oldi > next_out) {
3523                             SendToPlayer(&buf[next_out], oldi - next_out);
3524                             next_out = oldi;
3525                         }
3526                         Colorize(ColorRequest, FALSE);
3527                         curColor = ColorRequest;
3528                     }
3529                     continue;
3530                 }
3531
3532                 if (looking_at(buf, &i, "* (*) seeking")) {
3533                     if (appData.colorize) {
3534                         if (oldi > next_out) {
3535                             SendToPlayer(&buf[next_out], oldi - next_out);
3536                             next_out = oldi;
3537                         }
3538                         Colorize(ColorSeek, FALSE);
3539                         curColor = ColorSeek;
3540                     }
3541                     continue;
3542             }
3543
3544           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3545
3546             if (looking_at(buf, &i, "\\   ")) {
3547                 if (prevColor != ColorNormal) {
3548                     if (oldi > next_out) {
3549                         SendToPlayer(&buf[next_out], oldi - next_out);
3550                         next_out = oldi;
3551                     }
3552                     Colorize(prevColor, TRUE);
3553                     curColor = prevColor;
3554                 }
3555                 if (savingComment) {
3556                     parse_pos = i - oldi;
3557                     memcpy(parse, &buf[oldi], parse_pos);
3558                     parse[parse_pos] = NULLCHAR;
3559                     started = STARTED_COMMENT;
3560                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3561                         chattingPartner = savingComment - 3; // kludge to remember the box
3562                 } else {
3563                     started = STARTED_CHATTER;
3564                 }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "Black Strength :") ||
3569                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3570                 looking_at(buf, &i, "<10>") ||
3571                 looking_at(buf, &i, "#@#")) {
3572                 /* Wrong board style */
3573                 loggedOn = TRUE;
3574                 SendToICS(ics_prefix);
3575                 SendToICS("set style 12\n");
3576                 SendToICS(ics_prefix);
3577                 SendToICS("refresh\n");
3578                 continue;
3579             }
3580
3581             if (looking_at(buf, &i, "login:")) {
3582               if (!have_sent_ICS_logon) {
3583                 if(ICSInitScript())
3584                   have_sent_ICS_logon = 1;
3585                 else // no init script was found
3586                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3587               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3588                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3589               }
3590                 continue;
3591             }
3592
3593             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3594                 (looking_at(buf, &i, "\n<12> ") ||
3595                  looking_at(buf, &i, "<12> "))) {
3596                 loggedOn = TRUE;
3597                 if (oldi > next_out) {
3598                     SendToPlayer(&buf[next_out], oldi - next_out);
3599                 }
3600                 next_out = i;
3601                 started = STARTED_BOARD;
3602                 parse_pos = 0;
3603                 continue;
3604             }
3605
3606             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3607                 looking_at(buf, &i, "<b1> ")) {
3608                 if (oldi > next_out) {
3609                     SendToPlayer(&buf[next_out], oldi - next_out);
3610                 }
3611                 next_out = i;
3612                 started = STARTED_HOLDINGS;
3613                 parse_pos = 0;
3614                 continue;
3615             }
3616
3617             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3618                 loggedOn = TRUE;
3619                 /* Header for a move list -- first line */
3620
3621                 switch (ics_getting_history) {
3622                   case H_FALSE:
3623                     switch (gameMode) {
3624                       case IcsIdle:
3625                       case BeginningOfGame:
3626                         /* User typed "moves" or "oldmoves" while we
3627                            were idle.  Pretend we asked for these
3628                            moves and soak them up so user can step
3629                            through them and/or save them.
3630                            */
3631                         Reset(FALSE, TRUE);
3632                         gameMode = IcsObserving;
3633                         ModeHighlight();
3634                         ics_gamenum = -1;
3635                         ics_getting_history = H_GOT_UNREQ_HEADER;
3636                         break;
3637                       case EditGame: /*?*/
3638                       case EditPosition: /*?*/
3639                         /* Should above feature work in these modes too? */
3640                         /* For now it doesn't */
3641                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3642                         break;
3643                       default:
3644                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3645                         break;
3646                     }
3647                     break;
3648                   case H_REQUESTED:
3649                     /* Is this the right one? */
3650                     if (gameInfo.white && gameInfo.black &&
3651                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3652                         strcmp(gameInfo.black, star_match[2]) == 0) {
3653                         /* All is well */
3654                         ics_getting_history = H_GOT_REQ_HEADER;
3655                     }
3656                     break;
3657                   case H_GOT_REQ_HEADER:
3658                   case H_GOT_UNREQ_HEADER:
3659                   case H_GOT_UNWANTED_HEADER:
3660                   case H_GETTING_MOVES:
3661                     /* Should not happen */
3662                     DisplayError(_("Error gathering move list: two headers"), 0);
3663                     ics_getting_history = H_FALSE;
3664                     break;
3665                 }
3666
3667                 /* Save player ratings into gameInfo if needed */
3668                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3669                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3670                     (gameInfo.whiteRating == -1 ||
3671                      gameInfo.blackRating == -1)) {
3672
3673                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3674                     gameInfo.blackRating = string_to_rating(star_match[3]);
3675                     if (appData.debugMode)
3676                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3677                               gameInfo.whiteRating, gameInfo.blackRating);
3678                 }
3679                 continue;
3680             }
3681
3682             if (looking_at(buf, &i,
3683               "* * match, initial time: * minute*, increment: * second")) {
3684                 /* Header for a move list -- second line */
3685                 /* Initial board will follow if this is a wild game */
3686                 if (gameInfo.event != NULL) free(gameInfo.event);
3687                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3688                 gameInfo.event = StrSave(str);
3689                 /* [HGM] we switched variant. Translate boards if needed. */
3690                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3691                 continue;
3692             }
3693
3694             if (looking_at(buf, &i, "Move  ")) {
3695                 /* Beginning of a move list */
3696                 switch (ics_getting_history) {
3697                   case H_FALSE:
3698                     /* Normally should not happen */
3699                     /* Maybe user hit reset while we were parsing */
3700                     break;
3701                   case H_REQUESTED:
3702                     /* Happens if we are ignoring a move list that is not
3703                      * the one we just requested.  Common if the user
3704                      * tries to observe two games without turning off
3705                      * getMoveList */
3706                     break;
3707                   case H_GETTING_MOVES:
3708                     /* Should not happen */
3709                     DisplayError(_("Error gathering move list: nested"), 0);
3710                     ics_getting_history = H_FALSE;
3711                     break;
3712                   case H_GOT_REQ_HEADER:
3713                     ics_getting_history = H_GETTING_MOVES;
3714                     started = STARTED_MOVES;
3715                     parse_pos = 0;
3716                     if (oldi > next_out) {
3717                         SendToPlayer(&buf[next_out], oldi - next_out);
3718                     }
3719                     break;
3720                   case H_GOT_UNREQ_HEADER:
3721                     ics_getting_history = H_GETTING_MOVES;
3722                     started = STARTED_MOVES_NOHIDE;
3723                     parse_pos = 0;
3724                     break;
3725                   case H_GOT_UNWANTED_HEADER:
3726                     ics_getting_history = H_FALSE;
3727                     break;
3728                 }
3729                 continue;
3730             }
3731
3732             if (looking_at(buf, &i, "% ") ||
3733                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3734                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3735                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3736                     soughtPending = FALSE;
3737                     seekGraphUp = TRUE;
3738                     DrawSeekGraph();
3739                 }
3740                 if(suppressKibitz) next_out = i;
3741                 savingComment = FALSE;
3742                 suppressKibitz = 0;
3743                 switch (started) {
3744                   case STARTED_MOVES:
3745                   case STARTED_MOVES_NOHIDE:
3746                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3747                     parse[parse_pos + i - oldi] = NULLCHAR;
3748                     ParseGameHistory(parse);
3749 #if ZIPPY
3750                     if (appData.zippyPlay && first.initDone) {
3751                         FeedMovesToProgram(&first, forwardMostMove);
3752                         if (gameMode == IcsPlayingWhite) {
3753                             if (WhiteOnMove(forwardMostMove)) {
3754                                 if (first.sendTime) {
3755                                   if (first.useColors) {
3756                                     SendToProgram("black\n", &first);
3757                                   }
3758                                   SendTimeRemaining(&first, TRUE);
3759                                 }
3760                                 if (first.useColors) {
3761                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3762                                 }
3763                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3764                                 first.maybeThinking = TRUE;
3765                             } else {
3766                                 if (first.usePlayother) {
3767                                   if (first.sendTime) {
3768                                     SendTimeRemaining(&first, TRUE);
3769                                   }
3770                                   SendToProgram("playother\n", &first);
3771                                   firstMove = FALSE;
3772                                 } else {
3773                                   firstMove = TRUE;
3774                                 }
3775                             }
3776                         } else if (gameMode == IcsPlayingBlack) {
3777                             if (!WhiteOnMove(forwardMostMove)) {
3778                                 if (first.sendTime) {
3779                                   if (first.useColors) {
3780                                     SendToProgram("white\n", &first);
3781                                   }
3782                                   SendTimeRemaining(&first, FALSE);
3783                                 }
3784                                 if (first.useColors) {
3785                                   SendToProgram("black\n", &first);
3786                                 }
3787                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3788                                 first.maybeThinking = TRUE;
3789                             } else {
3790                                 if (first.usePlayother) {
3791                                   if (first.sendTime) {
3792                                     SendTimeRemaining(&first, FALSE);
3793                                   }
3794                                   SendToProgram("playother\n", &first);
3795                                   firstMove = FALSE;
3796                                 } else {
3797                                   firstMove = TRUE;
3798                                 }
3799                             }
3800                         }
3801                     }
3802 #endif
3803                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3804                         /* Moves came from oldmoves or moves command
3805                            while we weren't doing anything else.
3806                            */
3807                         currentMove = forwardMostMove;
3808                         ClearHighlights();/*!!could figure this out*/
3809                         flipView = appData.flipView;
3810                         DrawPosition(TRUE, boards[currentMove]);
3811                         DisplayBothClocks();
3812                         snprintf(str, MSG_SIZ, "%s %s %s",
3813                                 gameInfo.white, _("vs."),  gameInfo.black);
3814                         DisplayTitle(str);
3815                         gameMode = IcsIdle;
3816                     } else {
3817                         /* Moves were history of an active game */
3818                         if (gameInfo.resultDetails != NULL) {
3819                             free(gameInfo.resultDetails);
3820                             gameInfo.resultDetails = NULL;
3821                         }
3822                     }
3823                     HistorySet(parseList, backwardMostMove,
3824                                forwardMostMove, currentMove-1);
3825                     DisplayMove(currentMove - 1);
3826                     if (started == STARTED_MOVES) next_out = i;
3827                     started = STARTED_NONE;
3828                     ics_getting_history = H_FALSE;
3829                     break;
3830
3831                   case STARTED_OBSERVE:
3832                     started = STARTED_NONE;
3833                     SendToICS(ics_prefix);
3834                     SendToICS("refresh\n");
3835                     break;
3836
3837                   default:
3838                     break;
3839                 }
3840                 if(bookHit) { // [HGM] book: simulate book reply
3841                     static char bookMove[MSG_SIZ]; // a bit generous?
3842
3843                     programStats.nodes = programStats.depth = programStats.time =
3844                     programStats.score = programStats.got_only_move = 0;
3845                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3846
3847                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3848                     strcat(bookMove, bookHit);
3849                     HandleMachineMove(bookMove, &first);
3850                 }
3851                 continue;
3852             }
3853
3854             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3855                  started == STARTED_HOLDINGS ||
3856                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3857                 /* Accumulate characters in move list or board */
3858                 parse[parse_pos++] = buf[i];
3859             }
3860
3861             /* Start of game messages.  Mostly we detect start of game
3862                when the first board image arrives.  On some versions
3863                of the ICS, though, we need to do a "refresh" after starting
3864                to observe in order to get the current board right away. */
3865             if (looking_at(buf, &i, "Adding game * to observation list")) {
3866                 started = STARTED_OBSERVE;
3867                 continue;
3868             }
3869
3870             /* Handle auto-observe */
3871             if (appData.autoObserve &&
3872                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3873                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3874                 char *player;
3875                 /* Choose the player that was highlighted, if any. */
3876                 if (star_match[0][0] == '\033' ||
3877                     star_match[1][0] != '\033') {
3878                     player = star_match[0];
3879                 } else {
3880                     player = star_match[2];
3881                 }
3882                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3883                         ics_prefix, StripHighlightAndTitle(player));
3884                 SendToICS(str);
3885
3886                 /* Save ratings from notify string */
3887                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3888                 player1Rating = string_to_rating(star_match[1]);
3889                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3890                 player2Rating = string_to_rating(star_match[3]);
3891
3892                 if (appData.debugMode)
3893                   fprintf(debugFP,
3894                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3895                           player1Name, player1Rating,
3896                           player2Name, player2Rating);
3897
3898                 continue;
3899             }
3900
3901             /* Deal with automatic examine mode after a game,
3902                and with IcsObserving -> IcsExamining transition */
3903             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3904                 looking_at(buf, &i, "has made you an examiner of game *")) {
3905
3906                 int gamenum = atoi(star_match[0]);
3907                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3908                     gamenum == ics_gamenum) {
3909                     /* We were already playing or observing this game;
3910                        no need to refetch history */
3911                     gameMode = IcsExamining;
3912                     if (pausing) {
3913                         pauseExamForwardMostMove = forwardMostMove;
3914                     } else if (currentMove < forwardMostMove) {
3915                         ForwardInner(forwardMostMove);
3916                     }
3917                 } else {
3918                     /* I don't think this case really can happen */
3919                     SendToICS(ics_prefix);
3920                     SendToICS("refresh\n");
3921                 }
3922                 continue;
3923             }
3924
3925             /* Error messages */
3926 //          if (ics_user_moved) {
3927             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3928                 if (looking_at(buf, &i, "Illegal move") ||
3929                     looking_at(buf, &i, "Not a legal move") ||
3930                     looking_at(buf, &i, "Your king is in check") ||
3931                     looking_at(buf, &i, "It isn't your turn") ||
3932                     looking_at(buf, &i, "It is not your move")) {
3933                     /* Illegal move */
3934                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3935                         currentMove = forwardMostMove-1;
3936                         DisplayMove(currentMove - 1); /* before DMError */
3937                         DrawPosition(FALSE, boards[currentMove]);
3938                         SwitchClocks(forwardMostMove-1); // [HGM] race
3939                         DisplayBothClocks();
3940                     }
3941                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3942                     ics_user_moved = 0;
3943                     continue;
3944                 }
3945             }
3946
3947             if (looking_at(buf, &i, "still have time") ||
3948                 looking_at(buf, &i, "not out of time") ||
3949                 looking_at(buf, &i, "either player is out of time") ||
3950                 looking_at(buf, &i, "has timeseal; checking")) {
3951                 /* We must have called his flag a little too soon */
3952                 whiteFlag = blackFlag = FALSE;
3953                 continue;
3954             }
3955
3956             if (looking_at(buf, &i, "added * seconds to") ||
3957                 looking_at(buf, &i, "seconds were added to")) {
3958                 /* Update the clocks */
3959                 SendToICS(ics_prefix);
3960                 SendToICS("refresh\n");
3961                 continue;
3962             }
3963
3964             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3965                 ics_clock_paused = TRUE;
3966                 StopClocks();
3967                 continue;
3968             }
3969
3970             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3971                 ics_clock_paused = FALSE;
3972                 StartClocks();
3973                 continue;
3974             }
3975
3976             /* Grab player ratings from the Creating: message.
3977                Note we have to check for the special case when
3978                the ICS inserts things like [white] or [black]. */
3979             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3980                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3981                 /* star_matches:
3982                    0    player 1 name (not necessarily white)
3983                    1    player 1 rating
3984                    2    empty, white, or black (IGNORED)
3985                    3    player 2 name (not necessarily black)
3986                    4    player 2 rating
3987
3988                    The names/ratings are sorted out when the game
3989                    actually starts (below).
3990                 */
3991                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3992                 player1Rating = string_to_rating(star_match[1]);
3993                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3994                 player2Rating = string_to_rating(star_match[4]);
3995
3996                 if (appData.debugMode)
3997                   fprintf(debugFP,
3998                           "Ratings from 'Creating:' %s %d, %s %d\n",
3999                           player1Name, player1Rating,
4000                           player2Name, player2Rating);
4001
4002                 continue;
4003             }
4004
4005             /* Improved generic start/end-of-game messages */
4006             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4007                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4008                 /* If tkind == 0: */
4009                 /* star_match[0] is the game number */
4010                 /*           [1] is the white player's name */
4011                 /*           [2] is the black player's name */
4012                 /* For end-of-game: */
4013                 /*           [3] is the reason for the game end */
4014                 /*           [4] is a PGN end game-token, preceded by " " */
4015                 /* For start-of-game: */
4016                 /*           [3] begins with "Creating" or "Continuing" */
4017                 /*           [4] is " *" or empty (don't care). */
4018                 int gamenum = atoi(star_match[0]);
4019                 char *whitename, *blackname, *why, *endtoken;
4020                 ChessMove endtype = EndOfFile;
4021
4022                 if (tkind == 0) {
4023                   whitename = star_match[1];
4024                   blackname = star_match[2];
4025                   why = star_match[3];
4026                   endtoken = star_match[4];
4027                 } else {
4028                   whitename = star_match[1];
4029                   blackname = star_match[3];
4030                   why = star_match[5];
4031                   endtoken = star_match[6];
4032                 }
4033
4034                 /* Game start messages */
4035                 if (strncmp(why, "Creating ", 9) == 0 ||
4036                     strncmp(why, "Continuing ", 11) == 0) {
4037                     gs_gamenum = gamenum;
4038                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4039                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4040                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4041 #if ZIPPY
4042                     if (appData.zippyPlay) {
4043                         ZippyGameStart(whitename, blackname);
4044                     }
4045 #endif /*ZIPPY*/
4046                     partnerBoardValid = FALSE; // [HGM] bughouse
4047                     continue;
4048                 }
4049
4050                 /* Game end messages */
4051                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4052                     ics_gamenum != gamenum) {
4053                     continue;
4054                 }
4055                 while (endtoken[0] == ' ') endtoken++;
4056                 switch (endtoken[0]) {
4057                   case '*':
4058                   default:
4059                     endtype = GameUnfinished;
4060                     break;
4061                   case '0':
4062                     endtype = BlackWins;
4063                     break;
4064                   case '1':
4065                     if (endtoken[1] == '/')
4066                       endtype = GameIsDrawn;
4067                     else
4068                       endtype = WhiteWins;
4069                     break;
4070                 }
4071                 GameEnds(endtype, why, GE_ICS);
4072 #if ZIPPY
4073                 if (appData.zippyPlay && first.initDone) {
4074                     ZippyGameEnd(endtype, why);
4075                     if (first.pr == NoProc) {
4076                       /* Start the next process early so that we'll
4077                          be ready for the next challenge */
4078                       StartChessProgram(&first);
4079                     }
4080                     /* Send "new" early, in case this command takes
4081                        a long time to finish, so that we'll be ready
4082                        for the next challenge. */
4083                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4084                     Reset(TRUE, TRUE);
4085                 }
4086 #endif /*ZIPPY*/
4087                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4088                 continue;
4089             }
4090
4091             if (looking_at(buf, &i, "Removing game * from observation") ||
4092                 looking_at(buf, &i, "no longer observing game *") ||
4093                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4094                 if (gameMode == IcsObserving &&
4095                     atoi(star_match[0]) == ics_gamenum)
4096                   {
4097                       /* icsEngineAnalyze */
4098                       if (appData.icsEngineAnalyze) {
4099                             ExitAnalyzeMode();
4100                             ModeHighlight();
4101                       }
4102                       StopClocks();
4103                       gameMode = IcsIdle;
4104                       ics_gamenum = -1;
4105                       ics_user_moved = FALSE;
4106                   }
4107                 continue;
4108             }
4109
4110             if (looking_at(buf, &i, "no longer examining game *")) {
4111                 if (gameMode == IcsExamining &&
4112                     atoi(star_match[0]) == ics_gamenum)
4113                   {
4114                       gameMode = IcsIdle;
4115                       ics_gamenum = -1;
4116                       ics_user_moved = FALSE;
4117                   }
4118                 continue;
4119             }
4120
4121             /* Advance leftover_start past any newlines we find,
4122                so only partial lines can get reparsed */
4123             if (looking_at(buf, &i, "\n")) {
4124                 prevColor = curColor;
4125                 if (curColor != ColorNormal) {
4126                     if (oldi > next_out) {
4127                         SendToPlayer(&buf[next_out], oldi - next_out);
4128                         next_out = oldi;
4129                     }
4130                     Colorize(ColorNormal, FALSE);
4131                     curColor = ColorNormal;
4132                 }
4133                 if (started == STARTED_BOARD) {
4134                     started = STARTED_NONE;
4135                     parse[parse_pos] = NULLCHAR;
4136                     ParseBoard12(parse);
4137                     ics_user_moved = 0;
4138
4139                     /* Send premove here */
4140                     if (appData.premove) {
4141                       char str[MSG_SIZ];
4142                       if (currentMove == 0 &&
4143                           gameMode == IcsPlayingWhite &&
4144                           appData.premoveWhite) {
4145                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4146                         if (appData.debugMode)
4147                           fprintf(debugFP, "Sending premove:\n");
4148                         SendToICS(str);
4149                       } else if (currentMove == 1 &&
4150                                  gameMode == IcsPlayingBlack &&
4151                                  appData.premoveBlack) {
4152                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4153                         if (appData.debugMode)
4154                           fprintf(debugFP, "Sending premove:\n");
4155                         SendToICS(str);
4156                       } else if (gotPremove) {
4157                         gotPremove = 0;
4158                         ClearPremoveHighlights();
4159                         if (appData.debugMode)
4160                           fprintf(debugFP, "Sending premove:\n");
4161                           UserMoveEvent(premoveFromX, premoveFromY,
4162                                         premoveToX, premoveToY,
4163                                         premovePromoChar);
4164                       }
4165                     }
4166
4167                     /* Usually suppress following prompt */
4168                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4169                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4170                         if (looking_at(buf, &i, "*% ")) {
4171                             savingComment = FALSE;
4172                             suppressKibitz = 0;
4173                         }
4174                     }
4175                     next_out = i;
4176                 } else if (started == STARTED_HOLDINGS) {
4177                     int gamenum;
4178                     char new_piece[MSG_SIZ];
4179                     started = STARTED_NONE;
4180                     parse[parse_pos] = NULLCHAR;
4181                     if (appData.debugMode)
4182                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4183                                                         parse, currentMove);
4184                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4185                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4186                         if (gameInfo.variant == VariantNormal) {
4187                           /* [HGM] We seem to switch variant during a game!
4188                            * Presumably no holdings were displayed, so we have
4189                            * to move the position two files to the right to
4190                            * create room for them!
4191                            */
4192                           VariantClass newVariant;
4193                           switch(gameInfo.boardWidth) { // base guess on board width
4194                                 case 9:  newVariant = VariantShogi; break;
4195                                 case 10: newVariant = VariantGreat; break;
4196                                 default: newVariant = VariantCrazyhouse; break;
4197                           }
4198                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4199                           /* Get a move list just to see the header, which
4200                              will tell us whether this is really bug or zh */
4201                           if (ics_getting_history == H_FALSE) {
4202                             ics_getting_history = H_REQUESTED;
4203                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4204                             SendToICS(str);
4205                           }
4206                         }
4207                         new_piece[0] = NULLCHAR;
4208                         sscanf(parse, "game %d white [%s black [%s <- %s",
4209                                &gamenum, white_holding, black_holding,
4210                                new_piece);
4211                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4212                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4213                         /* [HGM] copy holdings to board holdings area */
4214                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4215                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4216                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4217 #if ZIPPY
4218                         if (appData.zippyPlay && first.initDone) {
4219                             ZippyHoldings(white_holding, black_holding,
4220                                           new_piece);
4221                         }
4222 #endif /*ZIPPY*/
4223                         if (tinyLayout || smallLayout) {
4224                             char wh[16], bh[16];
4225                             PackHolding(wh, white_holding);
4226                             PackHolding(bh, black_holding);
4227                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4228                                     gameInfo.white, gameInfo.black);
4229                         } else {
4230                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4231                                     gameInfo.white, white_holding, _("vs."),
4232                                     gameInfo.black, black_holding);
4233                         }
4234                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4235                         DrawPosition(FALSE, boards[currentMove]);
4236                         DisplayTitle(str);
4237                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4238                         sscanf(parse, "game %d white [%s black [%s <- %s",
4239                                &gamenum, white_holding, black_holding,
4240                                new_piece);
4241                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4242                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4243                         /* [HGM] copy holdings to partner-board holdings area */
4244                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4245                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4246                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4247                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4248                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4249                       }
4250                     }
4251                     /* Suppress following prompt */
4252                     if (looking_at(buf, &i, "*% ")) {
4253                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4254                         savingComment = FALSE;
4255                         suppressKibitz = 0;
4256                     }
4257                     next_out = i;
4258                 }
4259                 continue;
4260             }
4261
4262             i++;                /* skip unparsed character and loop back */
4263         }
4264
4265         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4266 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4267 //          SendToPlayer(&buf[next_out], i - next_out);
4268             started != STARTED_HOLDINGS && leftover_start > next_out) {
4269             SendToPlayer(&buf[next_out], leftover_start - next_out);
4270             next_out = i;
4271         }
4272
4273         leftover_len = buf_len - leftover_start;
4274         /* if buffer ends with something we couldn't parse,
4275            reparse it after appending the next read */
4276
4277     } else if (count == 0) {
4278         RemoveInputSource(isr);
4279         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4280     } else {
4281         DisplayFatalError(_("Error reading from ICS"), error, 1);
4282     }
4283 }
4284
4285
4286 /* Board style 12 looks like this:
4287
4288    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4289
4290  * The "<12> " is stripped before it gets to this routine.  The two
4291  * trailing 0's (flip state and clock ticking) are later addition, and
4292  * some chess servers may not have them, or may have only the first.
4293  * Additional trailing fields may be added in the future.
4294  */
4295
4296 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4297
4298 #define RELATION_OBSERVING_PLAYED    0
4299 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4300 #define RELATION_PLAYING_MYMOVE      1
4301 #define RELATION_PLAYING_NOTMYMOVE  -1
4302 #define RELATION_EXAMINING           2
4303 #define RELATION_ISOLATED_BOARD     -3
4304 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4305
4306 void
4307 ParseBoard12 (char *string)
4308 {
4309 #if ZIPPY
4310     int i, takeback;
4311     char *bookHit = NULL; // [HGM] book
4312 #endif
4313     GameMode newGameMode;
4314     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4315     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4316     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4317     char to_play, board_chars[200];
4318     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4319     char black[32], white[32];
4320     Board board;
4321     int prevMove = currentMove;
4322     int ticking = 2;
4323     ChessMove moveType;
4324     int fromX, fromY, toX, toY;
4325     char promoChar;
4326     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4327     Boolean weird = FALSE, reqFlag = FALSE;
4328
4329     fromX = fromY = toX = toY = -1;
4330
4331     newGame = FALSE;
4332
4333     if (appData.debugMode)
4334       fprintf(debugFP, "Parsing board: %s\n", string);
4335
4336     move_str[0] = NULLCHAR;
4337     elapsed_time[0] = NULLCHAR;
4338     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4339         int  i = 0, j;
4340         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4341             if(string[i] == ' ') { ranks++; files = 0; }
4342             else files++;
4343             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4344             i++;
4345         }
4346         for(j = 0; j <i; j++) board_chars[j] = string[j];
4347         board_chars[i] = '\0';
4348         string += i + 1;
4349     }
4350     n = sscanf(string, PATTERN, &to_play, &double_push,
4351                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4352                &gamenum, white, black, &relation, &basetime, &increment,
4353                &white_stren, &black_stren, &white_time, &black_time,
4354                &moveNum, str, elapsed_time, move_str, &ics_flip,
4355                &ticking);
4356
4357     if (n < 21) {
4358         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4359         DisplayError(str, 0);
4360         return;
4361     }
4362
4363     /* Convert the move number to internal form */
4364     moveNum = (moveNum - 1) * 2;
4365     if (to_play == 'B') moveNum++;
4366     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4367       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4368                         0, 1);
4369       return;
4370     }
4371
4372     switch (relation) {
4373       case RELATION_OBSERVING_PLAYED:
4374       case RELATION_OBSERVING_STATIC:
4375         if (gamenum == -1) {
4376             /* Old ICC buglet */
4377             relation = RELATION_OBSERVING_STATIC;
4378         }
4379         newGameMode = IcsObserving;
4380         break;
4381       case RELATION_PLAYING_MYMOVE:
4382       case RELATION_PLAYING_NOTMYMOVE:
4383         newGameMode =
4384           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4385             IcsPlayingWhite : IcsPlayingBlack;
4386         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4387         break;
4388       case RELATION_EXAMINING:
4389         newGameMode = IcsExamining;
4390         break;
4391       case RELATION_ISOLATED_BOARD:
4392       default:
4393         /* Just display this board.  If user was doing something else,
4394            we will forget about it until the next board comes. */
4395         newGameMode = IcsIdle;
4396         break;
4397       case RELATION_STARTING_POSITION:
4398         newGameMode = gameMode;
4399         break;
4400     }
4401
4402     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4403         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4404          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4405       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4406       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4407       static int lastBgGame = -1;
4408       char *toSqr;
4409       for (k = 0; k < ranks; k++) {
4410         for (j = 0; j < files; j++)
4411           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4412         if(gameInfo.holdingsWidth > 1) {
4413              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4414              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4415         }
4416       }
4417       CopyBoard(partnerBoard, board);
4418       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4419         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4420         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4421       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4422       if(toSqr = strchr(str, '-')) {
4423         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4424         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4425       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4426       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4427       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4428       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4429       if(twoBoards) {
4430           DisplayWhiteClock(white_time*fac, to_play == 'W');
4431           DisplayBlackClock(black_time*fac, to_play != 'W');
4432           activePartner = to_play;
4433           if(gamenum != lastBgGame) {
4434               char buf[MSG_SIZ];
4435               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4436               DisplayTitle(buf);
4437           }
4438           lastBgGame = gamenum;
4439           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4440                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4441       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4442                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4443       if(!twoBoards) DisplayMessage(partnerStatus, "");
4444         partnerBoardValid = TRUE;
4445       return;
4446     }
4447
4448     if(appData.dualBoard && appData.bgObserve) {
4449         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4450             SendToICS(ics_prefix), SendToICS("pobserve\n");
4451         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4452             char buf[MSG_SIZ];
4453             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4454             SendToICS(buf);
4455         }
4456     }
4457
4458     /* Modify behavior for initial board display on move listing
4459        of wild games.
4460        */
4461     switch (ics_getting_history) {
4462       case H_FALSE:
4463       case H_REQUESTED:
4464         break;
4465       case H_GOT_REQ_HEADER:
4466       case H_GOT_UNREQ_HEADER:
4467         /* This is the initial position of the current game */
4468         gamenum = ics_gamenum;
4469         moveNum = 0;            /* old ICS bug workaround */
4470         if (to_play == 'B') {
4471           startedFromSetupPosition = TRUE;
4472           blackPlaysFirst = TRUE;
4473           moveNum = 1;
4474           if (forwardMostMove == 0) forwardMostMove = 1;
4475           if (backwardMostMove == 0) backwardMostMove = 1;
4476           if (currentMove == 0) currentMove = 1;
4477         }
4478         newGameMode = gameMode;
4479         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4480         break;
4481       case H_GOT_UNWANTED_HEADER:
4482         /* This is an initial board that we don't want */
4483         return;
4484       case H_GETTING_MOVES:
4485         /* Should not happen */
4486         DisplayError(_("Error gathering move list: extra board"), 0);
4487         ics_getting_history = H_FALSE;
4488         return;
4489     }
4490
4491    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4492                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4493                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4494      /* [HGM] We seem to have switched variant unexpectedly
4495       * Try to guess new variant from board size
4496       */
4497           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4498           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4499           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4500           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4501           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4502           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4503           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4504           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4505           /* Get a move list just to see the header, which
4506              will tell us whether this is really bug or zh */
4507           if (ics_getting_history == H_FALSE) {
4508             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4509             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4510             SendToICS(str);
4511           }
4512     }
4513
4514     /* Take action if this is the first board of a new game, or of a
4515        different game than is currently being displayed.  */
4516     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4517         relation == RELATION_ISOLATED_BOARD) {
4518
4519         /* Forget the old game and get the history (if any) of the new one */
4520         if (gameMode != BeginningOfGame) {
4521           Reset(TRUE, TRUE);
4522         }
4523         newGame = TRUE;
4524         if (appData.autoRaiseBoard) BoardToTop();
4525         prevMove = -3;
4526         if (gamenum == -1) {
4527             newGameMode = IcsIdle;
4528         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4529                    appData.getMoveList && !reqFlag) {
4530             /* Need to get game history */
4531             ics_getting_history = H_REQUESTED;
4532             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4533             SendToICS(str);
4534         }
4535
4536         /* Initially flip the board to have black on the bottom if playing
4537            black or if the ICS flip flag is set, but let the user change
4538            it with the Flip View button. */
4539         flipView = appData.autoFlipView ?
4540           (newGameMode == IcsPlayingBlack) || ics_flip :
4541           appData.flipView;
4542
4543         /* Done with values from previous mode; copy in new ones */
4544         gameMode = newGameMode;
4545         ModeHighlight();
4546         ics_gamenum = gamenum;
4547         if (gamenum == gs_gamenum) {
4548             int klen = strlen(gs_kind);
4549             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4550             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4551             gameInfo.event = StrSave(str);
4552         } else {
4553             gameInfo.event = StrSave("ICS game");
4554         }
4555         gameInfo.site = StrSave(appData.icsHost);
4556         gameInfo.date = PGNDate();
4557         gameInfo.round = StrSave("-");
4558         gameInfo.white = StrSave(white);
4559         gameInfo.black = StrSave(black);
4560         timeControl = basetime * 60 * 1000;
4561         timeControl_2 = 0;
4562         timeIncrement = increment * 1000;
4563         movesPerSession = 0;
4564         gameInfo.timeControl = TimeControlTagValue();
4565         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4566   if (appData.debugMode) {
4567     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4568     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4569     setbuf(debugFP, NULL);
4570   }
4571
4572         gameInfo.outOfBook = NULL;
4573
4574         /* Do we have the ratings? */
4575         if (strcmp(player1Name, white) == 0 &&
4576             strcmp(player2Name, black) == 0) {
4577             if (appData.debugMode)
4578               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4579                       player1Rating, player2Rating);
4580             gameInfo.whiteRating = player1Rating;
4581             gameInfo.blackRating = player2Rating;
4582         } else if (strcmp(player2Name, white) == 0 &&
4583                    strcmp(player1Name, black) == 0) {
4584             if (appData.debugMode)
4585               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4586                       player2Rating, player1Rating);
4587             gameInfo.whiteRating = player2Rating;
4588             gameInfo.blackRating = player1Rating;
4589         }
4590         player1Name[0] = player2Name[0] = NULLCHAR;
4591
4592         /* Silence shouts if requested */
4593         if (appData.quietPlay &&
4594             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4595             SendToICS(ics_prefix);
4596             SendToICS("set shout 0\n");
4597         }
4598     }
4599
4600     /* Deal with midgame name changes */
4601     if (!newGame) {
4602         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4603             if (gameInfo.white) free(gameInfo.white);
4604             gameInfo.white = StrSave(white);
4605         }
4606         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4607             if (gameInfo.black) free(gameInfo.black);
4608             gameInfo.black = StrSave(black);
4609         }
4610     }
4611
4612     /* Throw away game result if anything actually changes in examine mode */
4613     if (gameMode == IcsExamining && !newGame) {
4614         gameInfo.result = GameUnfinished;
4615         if (gameInfo.resultDetails != NULL) {
4616             free(gameInfo.resultDetails);
4617             gameInfo.resultDetails = NULL;
4618         }
4619     }
4620
4621     /* In pausing && IcsExamining mode, we ignore boards coming
4622        in if they are in a different variation than we are. */
4623     if (pauseExamInvalid) return;
4624     if (pausing && gameMode == IcsExamining) {
4625         if (moveNum <= pauseExamForwardMostMove) {
4626             pauseExamInvalid = TRUE;
4627             forwardMostMove = pauseExamForwardMostMove;
4628             return;
4629         }
4630     }
4631
4632   if (appData.debugMode) {
4633     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4634   }
4635     /* Parse the board */
4636     for (k = 0; k < ranks; k++) {
4637       for (j = 0; j < files; j++)
4638         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4639       if(gameInfo.holdingsWidth > 1) {
4640            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4641            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4642       }
4643     }
4644     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4645       board[5][BOARD_RGHT+1] = WhiteAngel;
4646       board[6][BOARD_RGHT+1] = WhiteMarshall;
4647       board[1][0] = BlackMarshall;
4648       board[2][0] = BlackAngel;
4649       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4650     }
4651     CopyBoard(boards[moveNum], board);
4652     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4653     if (moveNum == 0) {
4654         startedFromSetupPosition =
4655           !CompareBoards(board, initialPosition);
4656         if(startedFromSetupPosition)
4657             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4658     }
4659
4660     /* [HGM] Set castling rights. Take the outermost Rooks,
4661        to make it also work for FRC opening positions. Note that board12
4662        is really defective for later FRC positions, as it has no way to
4663        indicate which Rook can castle if they are on the same side of King.
4664        For the initial position we grant rights to the outermost Rooks,
4665        and remember thos rights, and we then copy them on positions
4666        later in an FRC game. This means WB might not recognize castlings with
4667        Rooks that have moved back to their original position as illegal,
4668        but in ICS mode that is not its job anyway.
4669     */
4670     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4671     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4672
4673         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4674             if(board[0][i] == WhiteRook) j = i;
4675         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4676         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4677             if(board[0][i] == WhiteRook) j = i;
4678         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4679         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4680             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4681         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4682         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4683             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4684         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4685
4686         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4687         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4688         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4689             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4690         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4691             if(board[BOARD_HEIGHT-1][k] == bKing)
4692                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4693         if(gameInfo.variant == VariantTwoKings) {
4694             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4695             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4696             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4697         }
4698     } else { int r;
4699         r = boards[moveNum][CASTLING][0] = initialRights[0];
4700         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4701         r = boards[moveNum][CASTLING][1] = initialRights[1];
4702         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4703         r = boards[moveNum][CASTLING][3] = initialRights[3];
4704         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4705         r = boards[moveNum][CASTLING][4] = initialRights[4];
4706         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4707         /* wildcastle kludge: always assume King has rights */
4708         r = boards[moveNum][CASTLING][2] = initialRights[2];
4709         r = boards[moveNum][CASTLING][5] = initialRights[5];
4710     }
4711     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4712     boards[moveNum][EP_STATUS] = EP_NONE;
4713     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4714     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4715     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4716
4717
4718     if (ics_getting_history == H_GOT_REQ_HEADER ||
4719         ics_getting_history == H_GOT_UNREQ_HEADER) {
4720         /* This was an initial position from a move list, not
4721            the current position */
4722         return;
4723     }
4724
4725     /* Update currentMove and known move number limits */
4726     newMove = newGame || moveNum > forwardMostMove;
4727
4728     if (newGame) {
4729         forwardMostMove = backwardMostMove = currentMove = moveNum;
4730         if (gameMode == IcsExamining && moveNum == 0) {
4731           /* Workaround for ICS limitation: we are not told the wild
4732              type when starting to examine a game.  But if we ask for
4733              the move list, the move list header will tell us */
4734             ics_getting_history = H_REQUESTED;
4735             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4736             SendToICS(str);
4737         }
4738     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4739                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4740 #if ZIPPY
4741         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4742         /* [HGM] applied this also to an engine that is silently watching        */
4743         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4744             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4745             gameInfo.variant == currentlyInitializedVariant) {
4746           takeback = forwardMostMove - moveNum;
4747           for (i = 0; i < takeback; i++) {
4748             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4749             SendToProgram("undo\n", &first);
4750           }
4751         }
4752 #endif
4753
4754         forwardMostMove = moveNum;
4755         if (!pausing || currentMove > forwardMostMove)
4756           currentMove = forwardMostMove;
4757     } else {
4758         /* New part of history that is not contiguous with old part */
4759         if (pausing && gameMode == IcsExamining) {
4760             pauseExamInvalid = TRUE;
4761             forwardMostMove = pauseExamForwardMostMove;
4762             return;
4763         }
4764         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4765 #if ZIPPY
4766             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4767                 // [HGM] when we will receive the move list we now request, it will be
4768                 // fed to the engine from the first move on. So if the engine is not
4769                 // in the initial position now, bring it there.
4770                 InitChessProgram(&first, 0);
4771             }
4772 #endif
4773             ics_getting_history = H_REQUESTED;
4774             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4775             SendToICS(str);
4776         }
4777         forwardMostMove = backwardMostMove = currentMove = moveNum;
4778     }
4779
4780     /* Update the clocks */
4781     if (strchr(elapsed_time, '.')) {
4782       /* Time is in ms */
4783       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4784       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4785     } else {
4786       /* Time is in seconds */
4787       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4788       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4789     }
4790
4791
4792 #if ZIPPY
4793     if (appData.zippyPlay && newGame &&
4794         gameMode != IcsObserving && gameMode != IcsIdle &&
4795         gameMode != IcsExamining)
4796       ZippyFirstBoard(moveNum, basetime, increment);
4797 #endif
4798
4799     /* Put the move on the move list, first converting
4800        to canonical algebraic form. */
4801     if (moveNum > 0) {
4802   if (appData.debugMode) {
4803     int f = forwardMostMove;
4804     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4805             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4806             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4807     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4808     fprintf(debugFP, "moveNum = %d\n", moveNum);
4809     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4810     setbuf(debugFP, NULL);
4811   }
4812         if (moveNum <= backwardMostMove) {
4813             /* We don't know what the board looked like before
4814                this move.  Punt. */
4815           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4816             strcat(parseList[moveNum - 1], " ");
4817             strcat(parseList[moveNum - 1], elapsed_time);
4818             moveList[moveNum - 1][0] = NULLCHAR;
4819         } else if (strcmp(move_str, "none") == 0) {
4820             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4821             /* Again, we don't know what the board looked like;
4822                this is really the start of the game. */
4823             parseList[moveNum - 1][0] = NULLCHAR;
4824             moveList[moveNum - 1][0] = NULLCHAR;
4825             backwardMostMove = moveNum;
4826             startedFromSetupPosition = TRUE;
4827             fromX = fromY = toX = toY = -1;
4828         } else {
4829           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4830           //                 So we parse the long-algebraic move string in stead of the SAN move
4831           int valid; char buf[MSG_SIZ], *prom;
4832
4833           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4834                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4835           // str looks something like "Q/a1-a2"; kill the slash
4836           if(str[1] == '/')
4837             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4838           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4839           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4840                 strcat(buf, prom); // long move lacks promo specification!
4841           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4842                 if(appData.debugMode)
4843                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4844                 safeStrCpy(move_str, buf, MSG_SIZ);
4845           }
4846           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4847                                 &fromX, &fromY, &toX, &toY, &promoChar)
4848                || ParseOneMove(buf, moveNum - 1, &moveType,
4849                                 &fromX, &fromY, &toX, &toY, &promoChar);
4850           // end of long SAN patch
4851           if (valid) {
4852             (void) CoordsToAlgebraic(boards[moveNum - 1],
4853                                      PosFlags(moveNum - 1),
4854                                      fromY, fromX, toY, toX, promoChar,
4855                                      parseList[moveNum-1]);
4856             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4857               case MT_NONE:
4858               case MT_STALEMATE:
4859               default:
4860                 break;
4861               case MT_CHECK:
4862                 if(!IS_SHOGI(gameInfo.variant))
4863                     strcat(parseList[moveNum - 1], "+");
4864                 break;
4865               case MT_CHECKMATE:
4866               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4867                 strcat(parseList[moveNum - 1], "#");
4868                 break;
4869             }
4870             strcat(parseList[moveNum - 1], " ");
4871             strcat(parseList[moveNum - 1], elapsed_time);
4872             /* currentMoveString is set as a side-effect of ParseOneMove */
4873             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4874             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4875             strcat(moveList[moveNum - 1], "\n");
4876
4877             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4878                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4879               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4880                 ChessSquare old, new = boards[moveNum][k][j];
4881                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4882                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4883                   if(old == new) continue;
4884                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4885                   else if(new == WhiteWazir || new == BlackWazir) {
4886                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4887                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4888                       else boards[moveNum][k][j] = old; // preserve type of Gold
4889                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4890                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4891               }
4892           } else {
4893             /* Move from ICS was illegal!?  Punt. */
4894             if (appData.debugMode) {
4895               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4896               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4897             }
4898             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4899             strcat(parseList[moveNum - 1], " ");
4900             strcat(parseList[moveNum - 1], elapsed_time);
4901             moveList[moveNum - 1][0] = NULLCHAR;
4902             fromX = fromY = toX = toY = -1;
4903           }
4904         }
4905   if (appData.debugMode) {
4906     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4907     setbuf(debugFP, NULL);
4908   }
4909
4910 #if ZIPPY
4911         /* Send move to chess program (BEFORE animating it). */
4912         if (appData.zippyPlay && !newGame && newMove &&
4913            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4914
4915             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4916                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4917                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4918                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4919                             move_str);
4920                     DisplayError(str, 0);
4921                 } else {
4922                     if (first.sendTime) {
4923                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4924                     }
4925                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4926                     if (firstMove && !bookHit) {
4927                         firstMove = FALSE;
4928                         if (first.useColors) {
4929                           SendToProgram(gameMode == IcsPlayingWhite ?
4930                                         "white\ngo\n" :
4931                                         "black\ngo\n", &first);
4932                         } else {
4933                           SendToProgram("go\n", &first);
4934                         }
4935                         first.maybeThinking = TRUE;
4936                     }
4937                 }
4938             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4939               if (moveList[moveNum - 1][0] == NULLCHAR) {
4940                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4941                 DisplayError(str, 0);
4942               } else {
4943                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4944                 SendMoveToProgram(moveNum - 1, &first);
4945               }
4946             }
4947         }
4948 #endif
4949     }
4950
4951     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4952         /* If move comes from a remote source, animate it.  If it
4953            isn't remote, it will have already been animated. */
4954         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4955             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4956         }
4957         if (!pausing && appData.highlightLastMove) {
4958             SetHighlights(fromX, fromY, toX, toY);
4959         }
4960     }
4961
4962     /* Start the clocks */
4963     whiteFlag = blackFlag = FALSE;
4964     appData.clockMode = !(basetime == 0 && increment == 0);
4965     if (ticking == 0) {
4966       ics_clock_paused = TRUE;
4967       StopClocks();
4968     } else if (ticking == 1) {
4969       ics_clock_paused = FALSE;
4970     }
4971     if (gameMode == IcsIdle ||
4972         relation == RELATION_OBSERVING_STATIC ||
4973         relation == RELATION_EXAMINING ||
4974         ics_clock_paused)
4975       DisplayBothClocks();
4976     else
4977       StartClocks();
4978
4979     /* Display opponents and material strengths */
4980     if (gameInfo.variant != VariantBughouse &&
4981         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4982         if (tinyLayout || smallLayout) {
4983             if(gameInfo.variant == VariantNormal)
4984               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4985                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4986                     basetime, increment);
4987             else
4988               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4989                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4990                     basetime, increment, (int) gameInfo.variant);
4991         } else {
4992             if(gameInfo.variant == VariantNormal)
4993               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4994                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4995                     basetime, increment);
4996             else
4997               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4998                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4999                     basetime, increment, VariantName(gameInfo.variant));
5000         }
5001         DisplayTitle(str);
5002   if (appData.debugMode) {
5003     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5004   }
5005     }
5006
5007
5008     /* Display the board */
5009     if (!pausing && !appData.noGUI) {
5010
5011       if (appData.premove)
5012           if (!gotPremove ||
5013              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5014              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5015               ClearPremoveHighlights();
5016
5017       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5018         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5019       DrawPosition(j, boards[currentMove]);
5020
5021       DisplayMove(moveNum - 1);
5022       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5023             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5024               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5025         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5026       }
5027     }
5028
5029     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5030 #if ZIPPY
5031     if(bookHit) { // [HGM] book: simulate book reply
5032         static char bookMove[MSG_SIZ]; // a bit generous?
5033
5034         programStats.nodes = programStats.depth = programStats.time =
5035         programStats.score = programStats.got_only_move = 0;
5036         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5037
5038         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5039         strcat(bookMove, bookHit);
5040         HandleMachineMove(bookMove, &first);
5041     }
5042 #endif
5043 }
5044
5045 void
5046 GetMoveListEvent ()
5047 {
5048     char buf[MSG_SIZ];
5049     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5050         ics_getting_history = H_REQUESTED;
5051         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5052         SendToICS(buf);
5053     }
5054 }
5055
5056 void
5057 SendToBoth (char *msg)
5058 {   // to make it easy to keep two engines in step in dual analysis
5059     SendToProgram(msg, &first);
5060     if(second.analyzing) SendToProgram(msg, &second);
5061 }
5062
5063 void
5064 AnalysisPeriodicEvent (int force)
5065 {
5066     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5067          && !force) || !appData.periodicUpdates)
5068       return;
5069
5070     /* Send . command to Crafty to collect stats */
5071     SendToBoth(".\n");
5072
5073     /* Don't send another until we get a response (this makes
5074        us stop sending to old Crafty's which don't understand
5075        the "." command (sending illegal cmds resets node count & time,
5076        which looks bad)) */
5077     programStats.ok_to_send = 0;
5078 }
5079
5080 void
5081 ics_update_width (int new_width)
5082 {
5083         ics_printf("set width %d\n", new_width);
5084 }
5085
5086 void
5087 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5088 {
5089     char buf[MSG_SIZ];
5090
5091     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5092         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5093             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5094             SendToProgram(buf, cps);
5095             return;
5096         }
5097         // null move in variant where engine does not understand it (for analysis purposes)
5098         SendBoard(cps, moveNum + 1); // send position after move in stead.
5099         return;
5100     }
5101     if (cps->useUsermove) {
5102       SendToProgram("usermove ", cps);
5103     }
5104     if (cps->useSAN) {
5105       char *space;
5106       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5107         int len = space - parseList[moveNum];
5108         memcpy(buf, parseList[moveNum], len);
5109         buf[len++] = '\n';
5110         buf[len] = NULLCHAR;
5111       } else {
5112         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5113       }
5114       SendToProgram(buf, cps);
5115     } else {
5116       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5117         AlphaRank(moveList[moveNum], 4);
5118         SendToProgram(moveList[moveNum], cps);
5119         AlphaRank(moveList[moveNum], 4); // and back
5120       } else
5121       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5122        * the engine. It would be nice to have a better way to identify castle
5123        * moves here. */
5124       if(appData.fischerCastling && cps->useOOCastle) {
5125         int fromX = moveList[moveNum][0] - AAA;
5126         int fromY = moveList[moveNum][1] - ONE;
5127         int toX = moveList[moveNum][2] - AAA;
5128         int toY = moveList[moveNum][3] - ONE;
5129         if((boards[moveNum][fromY][fromX] == WhiteKing
5130             && boards[moveNum][toY][toX] == WhiteRook)
5131            || (boards[moveNum][fromY][fromX] == BlackKing
5132                && boards[moveNum][toY][toX] == BlackRook)) {
5133           if(toX > fromX) SendToProgram("O-O\n", cps);
5134           else SendToProgram("O-O-O\n", cps);
5135         }
5136         else SendToProgram(moveList[moveNum], cps);
5137       } else
5138       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5139           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5140                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5141                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5142                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5143           SendToProgram(buf, cps);
5144       } else
5145       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5146         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5147           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5148           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5149                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5150         } else
5151           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5152                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5153         SendToProgram(buf, cps);
5154       }
5155       else SendToProgram(moveList[moveNum], cps);
5156       /* End of additions by Tord */
5157     }
5158
5159     /* [HGM] setting up the opening has brought engine in force mode! */
5160     /*       Send 'go' if we are in a mode where machine should play. */
5161     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5162         (gameMode == TwoMachinesPlay   ||
5163 #if ZIPPY
5164          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5165 #endif
5166          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5167         SendToProgram("go\n", cps);
5168   if (appData.debugMode) {
5169     fprintf(debugFP, "(extra)\n");
5170   }
5171     }
5172     setboardSpoiledMachineBlack = 0;
5173 }
5174
5175 void
5176 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5177 {
5178     char user_move[MSG_SIZ];
5179     char suffix[4];
5180
5181     if(gameInfo.variant == VariantSChess && promoChar) {
5182         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5183         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5184     } else suffix[0] = NULLCHAR;
5185
5186     switch (moveType) {
5187       default:
5188         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5189                 (int)moveType, fromX, fromY, toX, toY);
5190         DisplayError(user_move + strlen("say "), 0);
5191         break;
5192       case WhiteKingSideCastle:
5193       case BlackKingSideCastle:
5194       case WhiteQueenSideCastleWild:
5195       case BlackQueenSideCastleWild:
5196       /* PUSH Fabien */
5197       case WhiteHSideCastleFR:
5198       case BlackHSideCastleFR:
5199       /* POP Fabien */
5200         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5201         break;
5202       case WhiteQueenSideCastle:
5203       case BlackQueenSideCastle:
5204       case WhiteKingSideCastleWild:
5205       case BlackKingSideCastleWild:
5206       /* PUSH Fabien */
5207       case WhiteASideCastleFR:
5208       case BlackASideCastleFR:
5209       /* POP Fabien */
5210         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5211         break;
5212       case WhiteNonPromotion:
5213       case BlackNonPromotion:
5214         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5215         break;
5216       case WhitePromotion:
5217       case BlackPromotion:
5218         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5219            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5220           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5221                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5222                 PieceToChar(WhiteFerz));
5223         else if(gameInfo.variant == VariantGreat)
5224           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5225                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5226                 PieceToChar(WhiteMan));
5227         else
5228           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5229                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5230                 promoChar);
5231         break;
5232       case WhiteDrop:
5233       case BlackDrop:
5234       drop:
5235         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5236                  ToUpper(PieceToChar((ChessSquare) fromX)),
5237                  AAA + toX, ONE + toY);
5238         break;
5239       case IllegalMove:  /* could be a variant we don't quite understand */
5240         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5241       case NormalMove:
5242       case WhiteCapturesEnPassant:
5243       case BlackCapturesEnPassant:
5244         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5245                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5246         break;
5247     }
5248     SendToICS(user_move);
5249     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5250         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5251 }
5252
5253 void
5254 UploadGameEvent ()
5255 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5256     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5257     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5258     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5259       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5260       return;
5261     }
5262     if(gameMode != IcsExamining) { // is this ever not the case?
5263         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5264
5265         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5266           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5267         } else { // on FICS we must first go to general examine mode
5268           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5269         }
5270         if(gameInfo.variant != VariantNormal) {
5271             // try figure out wild number, as xboard names are not always valid on ICS
5272             for(i=1; i<=36; i++) {
5273               snprintf(buf, MSG_SIZ, "wild/%d", i);
5274                 if(StringToVariant(buf) == gameInfo.variant) break;
5275             }
5276             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5277             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5278             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5279         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5280         SendToICS(ics_prefix);
5281         SendToICS(buf);
5282         if(startedFromSetupPosition || backwardMostMove != 0) {
5283           fen = PositionToFEN(backwardMostMove, NULL, 1);
5284           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5285             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5286             SendToICS(buf);
5287           } else { // FICS: everything has to set by separate bsetup commands
5288             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5289             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5290             SendToICS(buf);
5291             if(!WhiteOnMove(backwardMostMove)) {
5292                 SendToICS("bsetup tomove black\n");
5293             }
5294             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5295             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5296             SendToICS(buf);
5297             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5298             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5299             SendToICS(buf);
5300             i = boards[backwardMostMove][EP_STATUS];
5301             if(i >= 0) { // set e.p.
5302               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5303                 SendToICS(buf);
5304             }
5305             bsetup++;
5306           }
5307         }
5308       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5309             SendToICS("bsetup done\n"); // switch to normal examining.
5310     }
5311     for(i = backwardMostMove; i<last; i++) {
5312         char buf[20];
5313         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5314         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5315             int len = strlen(moveList[i]);
5316             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5317             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5318         }
5319         SendToICS(buf);
5320     }
5321     SendToICS(ics_prefix);
5322     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5323 }
5324
5325 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5326
5327 void
5328 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5329 {
5330     if (rf == DROP_RANK) {
5331       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5332       sprintf(move, "%c@%c%c\n",
5333                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5334     } else {
5335         if (promoChar == 'x' || promoChar == NULLCHAR) {
5336           sprintf(move, "%c%c%c%c\n",
5337                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5338           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5339         } else {
5340             sprintf(move, "%c%c%c%c%c\n",
5341                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5342         }
5343     }
5344 }
5345
5346 void
5347 ProcessICSInitScript (FILE *f)
5348 {
5349     char buf[MSG_SIZ];
5350
5351     while (fgets(buf, MSG_SIZ, f)) {
5352         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5353     }
5354
5355     fclose(f);
5356 }
5357
5358
5359 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5360 int dragging;
5361 static ClickType lastClickType;
5362
5363 int
5364 Partner (ChessSquare *p)
5365 { // change piece into promotion partner if one shogi-promotes to the other
5366   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5367   ChessSquare partner;
5368   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5369   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5370   *p = partner;
5371   return 1;
5372 }
5373
5374 void
5375 Sweep (int step)
5376 {
5377     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5378     static int toggleFlag;
5379     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5380     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5381     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5382     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5383     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5384     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5385     do {
5386         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5387         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5388         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5389         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5390         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5391         if(!step) step = -1;
5392     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5393             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5394             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5395             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5396     if(toX >= 0) {
5397         int victim = boards[currentMove][toY][toX];
5398         boards[currentMove][toY][toX] = promoSweep;
5399         DrawPosition(FALSE, boards[currentMove]);
5400         boards[currentMove][toY][toX] = victim;
5401     } else
5402     ChangeDragPiece(promoSweep);
5403 }
5404
5405 int
5406 PromoScroll (int x, int y)
5407 {
5408   int step = 0;
5409
5410   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5411   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5412   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5413   if(!step) return FALSE;
5414   lastX = x; lastY = y;
5415   if((promoSweep < BlackPawn) == flipView) step = -step;
5416   if(step > 0) selectFlag = 1;
5417   if(!selectFlag) Sweep(step);
5418   return FALSE;
5419 }
5420
5421 void
5422 NextPiece (int step)
5423 {
5424     ChessSquare piece = boards[currentMove][toY][toX];
5425     do {
5426         pieceSweep -= step;
5427         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5428         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5429         if(!step) step = -1;
5430     } while(PieceToChar(pieceSweep) == '.');
5431     boards[currentMove][toY][toX] = pieceSweep;
5432     DrawPosition(FALSE, boards[currentMove]);
5433     boards[currentMove][toY][toX] = piece;
5434 }
5435 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5436 void
5437 AlphaRank (char *move, int n)
5438 {
5439 //    char *p = move, c; int x, y;
5440
5441     if (appData.debugMode) {
5442         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5443     }
5444
5445     if(move[1]=='*' &&
5446        move[2]>='0' && move[2]<='9' &&
5447        move[3]>='a' && move[3]<='x'    ) {
5448         move[1] = '@';
5449         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5450         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5451     } else
5452     if(move[0]>='0' && move[0]<='9' &&
5453        move[1]>='a' && move[1]<='x' &&
5454        move[2]>='0' && move[2]<='9' &&
5455        move[3]>='a' && move[3]<='x'    ) {
5456         /* input move, Shogi -> normal */
5457         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5458         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5459         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5460         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5461     } else
5462     if(move[1]=='@' &&
5463        move[3]>='0' && move[3]<='9' &&
5464        move[2]>='a' && move[2]<='x'    ) {
5465         move[1] = '*';
5466         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5467         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5468     } else
5469     if(
5470        move[0]>='a' && move[0]<='x' &&
5471        move[3]>='0' && move[3]<='9' &&
5472        move[2]>='a' && move[2]<='x'    ) {
5473          /* output move, normal -> Shogi */
5474         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5475         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5476         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5477         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5478         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5479     }
5480     if (appData.debugMode) {
5481         fprintf(debugFP, "   out = '%s'\n", move);
5482     }
5483 }
5484
5485 char yy_textstr[8000];
5486
5487 /* Parser for moves from gnuchess, ICS, or user typein box */
5488 Boolean
5489 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5490 {
5491     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5492
5493     switch (*moveType) {
5494       case WhitePromotion:
5495       case BlackPromotion:
5496       case WhiteNonPromotion:
5497       case BlackNonPromotion:
5498       case NormalMove:
5499       case FirstLeg:
5500       case WhiteCapturesEnPassant:
5501       case BlackCapturesEnPassant:
5502       case WhiteKingSideCastle:
5503       case WhiteQueenSideCastle:
5504       case BlackKingSideCastle:
5505       case BlackQueenSideCastle:
5506       case WhiteKingSideCastleWild:
5507       case WhiteQueenSideCastleWild:
5508       case BlackKingSideCastleWild:
5509       case BlackQueenSideCastleWild:
5510       /* Code added by Tord: */
5511       case WhiteHSideCastleFR:
5512       case WhiteASideCastleFR:
5513       case BlackHSideCastleFR:
5514       case BlackASideCastleFR:
5515       /* End of code added by Tord */
5516       case IllegalMove:         /* bug or odd chess variant */
5517         *fromX = currentMoveString[0] - AAA;
5518         *fromY = currentMoveString[1] - ONE;
5519         *toX = currentMoveString[2] - AAA;
5520         *toY = currentMoveString[3] - ONE;
5521         *promoChar = currentMoveString[4];
5522         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5523             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5524     if (appData.debugMode) {
5525         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5526     }
5527             *fromX = *fromY = *toX = *toY = 0;
5528             return FALSE;
5529         }
5530         if (appData.testLegality) {
5531           return (*moveType != IllegalMove);
5532         } else {
5533           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5534                          // [HGM] lion: if this is a double move we are less critical
5535                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5536         }
5537
5538       case WhiteDrop:
5539       case BlackDrop:
5540         *fromX = *moveType == WhiteDrop ?
5541           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5542           (int) CharToPiece(ToLower(currentMoveString[0]));
5543         *fromY = DROP_RANK;
5544         *toX = currentMoveString[2] - AAA;
5545         *toY = currentMoveString[3] - ONE;
5546         *promoChar = NULLCHAR;
5547         return TRUE;
5548
5549       case AmbiguousMove:
5550       case ImpossibleMove:
5551       case EndOfFile:
5552       case ElapsedTime:
5553       case Comment:
5554       case PGNTag:
5555       case NAG:
5556       case WhiteWins:
5557       case BlackWins:
5558       case GameIsDrawn:
5559       default:
5560     if (appData.debugMode) {
5561         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5562     }
5563         /* bug? */
5564         *fromX = *fromY = *toX = *toY = 0;
5565         *promoChar = NULLCHAR;
5566         return FALSE;
5567     }
5568 }
5569
5570 Boolean pushed = FALSE;
5571 char *lastParseAttempt;
5572
5573 void
5574 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5575 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5576   int fromX, fromY, toX, toY; char promoChar;
5577   ChessMove moveType;
5578   Boolean valid;
5579   int nr = 0;
5580
5581   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5582   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5583     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5584     pushed = TRUE;
5585   }
5586   endPV = forwardMostMove;
5587   do {
5588     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5589     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5590     lastParseAttempt = pv;
5591     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5592     if(!valid && nr == 0 &&
5593        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5594         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5595         // Hande case where played move is different from leading PV move
5596         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5597         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5598         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5599         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5600           endPV += 2; // if position different, keep this
5601           moveList[endPV-1][0] = fromX + AAA;
5602           moveList[endPV-1][1] = fromY + ONE;
5603           moveList[endPV-1][2] = toX + AAA;
5604           moveList[endPV-1][3] = toY + ONE;
5605           parseList[endPV-1][0] = NULLCHAR;
5606           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5607         }
5608       }
5609     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5610     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5611     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5612     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5613         valid++; // allow comments in PV
5614         continue;
5615     }
5616     nr++;
5617     if(endPV+1 > framePtr) break; // no space, truncate
5618     if(!valid) break;
5619     endPV++;
5620     CopyBoard(boards[endPV], boards[endPV-1]);
5621     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5622     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5623     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5624     CoordsToAlgebraic(boards[endPV - 1],
5625                              PosFlags(endPV - 1),
5626                              fromY, fromX, toY, toX, promoChar,
5627                              parseList[endPV - 1]);
5628   } while(valid);
5629   if(atEnd == 2) return; // used hidden, for PV conversion
5630   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5631   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5632   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5633                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5634   DrawPosition(TRUE, boards[currentMove]);
5635 }
5636
5637 int
5638 MultiPV (ChessProgramState *cps)
5639 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5640         int i;
5641         for(i=0; i<cps->nrOptions; i++)
5642             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5643                 return i;
5644         return -1;
5645 }
5646
5647 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5648
5649 Boolean
5650 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5651 {
5652         int startPV, multi, lineStart, origIndex = index;
5653         char *p, buf2[MSG_SIZ];
5654         ChessProgramState *cps = (pane ? &second : &first);
5655
5656         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5657         lastX = x; lastY = y;
5658         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5659         lineStart = startPV = index;
5660         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5661         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5662         index = startPV;
5663         do{ while(buf[index] && buf[index] != '\n') index++;
5664         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5665         buf[index] = 0;
5666         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5667                 int n = cps->option[multi].value;
5668                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5669                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5670                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5671                 cps->option[multi].value = n;
5672                 *start = *end = 0;
5673                 return FALSE;
5674         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5675                 ExcludeClick(origIndex - lineStart);
5676                 return FALSE;
5677         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5678                 Collapse(origIndex - lineStart);
5679                 return FALSE;
5680         }
5681         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5682         *start = startPV; *end = index-1;
5683         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5684         return TRUE;
5685 }
5686
5687 char *
5688 PvToSAN (char *pv)
5689 {
5690         static char buf[10*MSG_SIZ];
5691         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5692         *buf = NULLCHAR;
5693         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5694         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5695         for(i = forwardMostMove; i<endPV; i++){
5696             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5697             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5698             k += strlen(buf+k);
5699         }
5700         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5701         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5702         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5703         endPV = savedEnd;
5704         return buf;
5705 }
5706
5707 Boolean
5708 LoadPV (int x, int y)
5709 { // called on right mouse click to load PV
5710   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5711   lastX = x; lastY = y;
5712   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5713   extendGame = FALSE;
5714   return TRUE;
5715 }
5716
5717 void
5718 UnLoadPV ()
5719 {
5720   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5721   if(endPV < 0) return;
5722   if(appData.autoCopyPV) CopyFENToClipboard();
5723   endPV = -1;
5724   if(extendGame && currentMove > forwardMostMove) {
5725         Boolean saveAnimate = appData.animate;
5726         if(pushed) {
5727             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5728                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5729             } else storedGames--; // abandon shelved tail of original game
5730         }
5731         pushed = FALSE;
5732         forwardMostMove = currentMove;
5733         currentMove = oldFMM;
5734         appData.animate = FALSE;
5735         ToNrEvent(forwardMostMove);
5736         appData.animate = saveAnimate;
5737   }
5738   currentMove = forwardMostMove;
5739   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5740   ClearPremoveHighlights();
5741   DrawPosition(TRUE, boards[currentMove]);
5742 }
5743
5744 void
5745 MovePV (int x, int y, int h)
5746 { // step through PV based on mouse coordinates (called on mouse move)
5747   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5748
5749   // we must somehow check if right button is still down (might be released off board!)
5750   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5751   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5752   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5753   if(!step) return;
5754   lastX = x; lastY = y;
5755
5756   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5757   if(endPV < 0) return;
5758   if(y < margin) step = 1; else
5759   if(y > h - margin) step = -1;
5760   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5761   currentMove += step;
5762   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5763   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5764                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5765   DrawPosition(FALSE, boards[currentMove]);
5766 }
5767
5768
5769 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5770 // All positions will have equal probability, but the current method will not provide a unique
5771 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5772 #define DARK 1
5773 #define LITE 2
5774 #define ANY 3
5775
5776 int squaresLeft[4];
5777 int piecesLeft[(int)BlackPawn];
5778 int seed, nrOfShuffles;
5779
5780 void
5781 GetPositionNumber ()
5782 {       // sets global variable seed
5783         int i;
5784
5785         seed = appData.defaultFrcPosition;
5786         if(seed < 0) { // randomize based on time for negative FRC position numbers
5787                 for(i=0; i<50; i++) seed += random();
5788                 seed = random() ^ random() >> 8 ^ random() << 8;
5789                 if(seed<0) seed = -seed;
5790         }
5791 }
5792
5793 int
5794 put (Board board, int pieceType, int rank, int n, int shade)
5795 // put the piece on the (n-1)-th empty squares of the given shade
5796 {
5797         int i;
5798
5799         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5800                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5801                         board[rank][i] = (ChessSquare) pieceType;
5802                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5803                         squaresLeft[ANY]--;
5804                         piecesLeft[pieceType]--;
5805                         return i;
5806                 }
5807         }
5808         return -1;
5809 }
5810
5811
5812 void
5813 AddOnePiece (Board board, int pieceType, int rank, int shade)
5814 // calculate where the next piece goes, (any empty square), and put it there
5815 {
5816         int i;
5817
5818         i = seed % squaresLeft[shade];
5819         nrOfShuffles *= squaresLeft[shade];
5820         seed /= squaresLeft[shade];
5821         put(board, pieceType, rank, i, shade);
5822 }
5823
5824 void
5825 AddTwoPieces (Board board, int pieceType, int rank)
5826 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5827 {
5828         int i, n=squaresLeft[ANY], j=n-1, k;
5829
5830         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5831         i = seed % k;  // pick one
5832         nrOfShuffles *= k;
5833         seed /= k;
5834         while(i >= j) i -= j--;
5835         j = n - 1 - j; i += j;
5836         put(board, pieceType, rank, j, ANY);
5837         put(board, pieceType, rank, i, ANY);
5838 }
5839
5840 void
5841 SetUpShuffle (Board board, int number)
5842 {
5843         int i, p, first=1;
5844
5845         GetPositionNumber(); nrOfShuffles = 1;
5846
5847         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5848         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5849         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5850
5851         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5852
5853         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5854             p = (int) board[0][i];
5855             if(p < (int) BlackPawn) piecesLeft[p] ++;
5856             board[0][i] = EmptySquare;
5857         }
5858
5859         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5860             // shuffles restricted to allow normal castling put KRR first
5861             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5862                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5863             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5864                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5865             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5866                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5867             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5868                 put(board, WhiteRook, 0, 0, ANY);
5869             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5870         }
5871
5872         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5873             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5874             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5875                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5876                 while(piecesLeft[p] >= 2) {
5877                     AddOnePiece(board, p, 0, LITE);
5878                     AddOnePiece(board, p, 0, DARK);
5879                 }
5880                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5881             }
5882
5883         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5884             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5885             // but we leave King and Rooks for last, to possibly obey FRC restriction
5886             if(p == (int)WhiteRook) continue;
5887             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5888             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5889         }
5890
5891         // now everything is placed, except perhaps King (Unicorn) and Rooks
5892
5893         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5894             // Last King gets castling rights
5895             while(piecesLeft[(int)WhiteUnicorn]) {
5896                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5897                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5898             }
5899
5900             while(piecesLeft[(int)WhiteKing]) {
5901                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5902                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5903             }
5904
5905
5906         } else {
5907             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5908             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5909         }
5910
5911         // Only Rooks can be left; simply place them all
5912         while(piecesLeft[(int)WhiteRook]) {
5913                 i = put(board, WhiteRook, 0, 0, ANY);
5914                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5915                         if(first) {
5916                                 first=0;
5917                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5918                         }
5919                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5920                 }
5921         }
5922         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5923             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5924         }
5925
5926         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5927 }
5928
5929 int
5930 SetCharTable (char *table, const char * map)
5931 /* [HGM] moved here from winboard.c because of its general usefulness */
5932 /*       Basically a safe strcpy that uses the last character as King */
5933 {
5934     int result = FALSE; int NrPieces;
5935
5936     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5937                     && NrPieces >= 12 && !(NrPieces&1)) {
5938         int i; /* [HGM] Accept even length from 12 to 34 */
5939
5940         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5941         for( i=0; i<NrPieces/2-1; i++ ) {
5942             table[i] = map[i];
5943             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5944         }
5945         table[(int) WhiteKing]  = map[NrPieces/2-1];
5946         table[(int) BlackKing]  = map[NrPieces-1];
5947
5948         result = TRUE;
5949     }
5950
5951     return result;
5952 }
5953
5954 void
5955 Prelude (Board board)
5956 {       // [HGM] superchess: random selection of exo-pieces
5957         int i, j, k; ChessSquare p;
5958         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5959
5960         GetPositionNumber(); // use FRC position number
5961
5962         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5963             SetCharTable(pieceToChar, appData.pieceToCharTable);
5964             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5965                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5966         }
5967
5968         j = seed%4;                 seed /= 4;
5969         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5970         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5971         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5972         j = seed%3 + (seed%3 >= j); seed /= 3;
5973         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5974         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5975         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5976         j = seed%3;                 seed /= 3;
5977         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5978         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5979         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5980         j = seed%2 + (seed%2 >= j); seed /= 2;
5981         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5982         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5983         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5984         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5985         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5986         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5987         put(board, exoPieces[0],    0, 0, ANY);
5988         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5989 }
5990
5991 void
5992 InitPosition (int redraw)
5993 {
5994     ChessSquare (* pieces)[BOARD_FILES];
5995     int i, j, pawnRow=1, pieceRows=1, overrule,
5996     oldx = gameInfo.boardWidth,
5997     oldy = gameInfo.boardHeight,
5998     oldh = gameInfo.holdingsWidth;
5999     static int oldv;
6000
6001     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6002
6003     /* [AS] Initialize pv info list [HGM] and game status */
6004     {
6005         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6006             pvInfoList[i].depth = 0;
6007             boards[i][EP_STATUS] = EP_NONE;
6008             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6009         }
6010
6011         initialRulePlies = 0; /* 50-move counter start */
6012
6013         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6014         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6015     }
6016
6017
6018     /* [HGM] logic here is completely changed. In stead of full positions */
6019     /* the initialized data only consist of the two backranks. The switch */
6020     /* selects which one we will use, which is than copied to the Board   */
6021     /* initialPosition, which for the rest is initialized by Pawns and    */
6022     /* empty squares. This initial position is then copied to boards[0],  */
6023     /* possibly after shuffling, so that it remains available.            */
6024
6025     gameInfo.holdingsWidth = 0; /* default board sizes */
6026     gameInfo.boardWidth    = 8;
6027     gameInfo.boardHeight   = 8;
6028     gameInfo.holdingsSize  = 0;
6029     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6030     for(i=0; i<BOARD_FILES-6; i++)
6031       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6032     initialPosition[EP_STATUS] = EP_NONE;
6033     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
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), legal[rt][ft] = 1;
7265     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
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++)
7358             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7359         if(!first.highlight || gameMode == EditPosition) return;
7360         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7361         SendToProgram(buf, &first);
7362 }
7363
7364 void
7365 LeftClick (ClickType clickType, int xPix, int yPix)
7366 {
7367     int x, y;
7368     Boolean saveAnimate;
7369     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7370     char promoChoice = NULLCHAR;
7371     ChessSquare piece;
7372     static TimeMark lastClickTime, prevClickTime;
7373
7374     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7375
7376     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7377
7378     if (clickType == Press) ErrorPopDown();
7379     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7380
7381     x = EventToSquare(xPix, BOARD_WIDTH);
7382     y = EventToSquare(yPix, BOARD_HEIGHT);
7383     if (!flipView && y >= 0) {
7384         y = BOARD_HEIGHT - 1 - y;
7385     }
7386     if (flipView && x >= 0) {
7387         x = BOARD_WIDTH - 1 - x;
7388     }
7389
7390     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7391         defaultPromoChoice = promoSweep;
7392         promoSweep = EmptySquare;   // terminate sweep
7393         promoDefaultAltered = TRUE;
7394         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7395     }
7396
7397     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7398         if(clickType == Release) return; // ignore upclick of click-click destination
7399         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7400         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7401         if(gameInfo.holdingsWidth &&
7402                 (WhiteOnMove(currentMove)
7403                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7404                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7405             // click in right holdings, for determining promotion piece
7406             ChessSquare p = boards[currentMove][y][x];
7407             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7408             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7409             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7410                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7411                 fromX = fromY = -1;
7412                 return;
7413             }
7414         }
7415         DrawPosition(FALSE, boards[currentMove]);
7416         return;
7417     }
7418
7419     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7420     if(clickType == Press
7421             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7422               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7423               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7424         return;
7425
7426     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7427         // could be static click on premove from-square: abort premove
7428         gotPremove = 0;
7429         ClearPremoveHighlights();
7430     }
7431
7432     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7433         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7434
7435     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7436         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7437                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7438         defaultPromoChoice = DefaultPromoChoice(side);
7439     }
7440
7441     autoQueen = appData.alwaysPromoteToQueen;
7442
7443     if (fromX == -1) {
7444       int originalY = y;
7445       gatingPiece = EmptySquare;
7446       if (clickType != Press) {
7447         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7448             DragPieceEnd(xPix, yPix); dragging = 0;
7449             DrawPosition(FALSE, NULL);
7450         }
7451         return;
7452       }
7453       doubleClick = FALSE;
7454       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7455         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7456       }
7457       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7458       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7459          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7460          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7461             /* First square */
7462             if (OKToStartUserMove(fromX, fromY)) {
7463                 second = 0;
7464                 ReportClick("lift", x, y);
7465                 MarkTargetSquares(0);
7466                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7467                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7468                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7469                     promoSweep = defaultPromoChoice;
7470                     selectFlag = 0; lastX = xPix; lastY = yPix;
7471                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7472                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7473                 }
7474                 if (appData.highlightDragging) {
7475                     SetHighlights(fromX, fromY, -1, -1);
7476                 } else {
7477                     ClearHighlights();
7478                 }
7479             } else fromX = fromY = -1;
7480             return;
7481         }
7482     }
7483
7484     /* fromX != -1 */
7485     if (clickType == Press && gameMode != EditPosition) {
7486         ChessSquare fromP;
7487         ChessSquare toP;
7488         int frc;
7489
7490         // ignore off-board to clicks
7491         if(y < 0 || x < 0) return;
7492
7493         /* Check if clicking again on the same color piece */
7494         fromP = boards[currentMove][fromY][fromX];
7495         toP = boards[currentMove][y][x];
7496         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7497         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7498            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7499              WhitePawn <= toP && toP <= WhiteKing &&
7500              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7501              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7502             (BlackPawn <= fromP && fromP <= BlackKing &&
7503              BlackPawn <= toP && toP <= BlackKing &&
7504              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7505              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7506             /* Clicked again on same color piece -- changed his mind */
7507             second = (x == fromX && y == fromY);
7508             killX = killY = -1;
7509             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7510                 second = FALSE; // first double-click rather than scond click
7511                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7512             }
7513             promoDefaultAltered = FALSE;
7514             MarkTargetSquares(1);
7515            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7516             if (appData.highlightDragging) {
7517                 SetHighlights(x, y, -1, -1);
7518             } else {
7519                 ClearHighlights();
7520             }
7521             if (OKToStartUserMove(x, y)) {
7522                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7523                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7524                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7525                  gatingPiece = boards[currentMove][fromY][fromX];
7526                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7527                 fromX = x;
7528                 fromY = y; dragging = 1;
7529                 ReportClick("lift", x, y);
7530                 MarkTargetSquares(0);
7531                 DragPieceBegin(xPix, yPix, FALSE);
7532                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7533                     promoSweep = defaultPromoChoice;
7534                     selectFlag = 0; lastX = xPix; lastY = yPix;
7535                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7536                 }
7537             }
7538            }
7539            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7540            second = FALSE;
7541         }
7542         // ignore clicks on holdings
7543         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7544     }
7545
7546     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7547         DragPieceEnd(xPix, yPix); dragging = 0;
7548         if(clearFlag) {
7549             // a deferred attempt to click-click move an empty square on top of a piece
7550             boards[currentMove][y][x] = EmptySquare;
7551             ClearHighlights();
7552             DrawPosition(FALSE, boards[currentMove]);
7553             fromX = fromY = -1; clearFlag = 0;
7554             return;
7555         }
7556         if (appData.animateDragging) {
7557             /* Undo animation damage if any */
7558             DrawPosition(FALSE, NULL);
7559         }
7560         if (second || sweepSelecting) {
7561             /* Second up/down in same square; just abort move */
7562             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7563             second = sweepSelecting = 0;
7564             fromX = fromY = -1;
7565             gatingPiece = EmptySquare;
7566             MarkTargetSquares(1);
7567             ClearHighlights();
7568             gotPremove = 0;
7569             ClearPremoveHighlights();
7570         } else {
7571             /* First upclick in same square; start click-click mode */
7572             SetHighlights(x, y, -1, -1);
7573         }
7574         return;
7575     }
7576
7577     clearFlag = 0;
7578
7579     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7580        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7581         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7582         DisplayMessage(_("only marked squares are legal"),"");
7583         DrawPosition(TRUE, NULL);
7584         return; // ignore to-click
7585     }
7586
7587     /* we now have a different from- and (possibly off-board) to-square */
7588     /* Completed move */
7589     if(!sweepSelecting) {
7590         toX = x;
7591         toY = y;
7592     }
7593
7594     piece = boards[currentMove][fromY][fromX];
7595
7596     saveAnimate = appData.animate;
7597     if (clickType == Press) {
7598         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7599         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7600             // must be Edit Position mode with empty-square selected
7601             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7602             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7603             return;
7604         }
7605         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7606             return;
7607         }
7608         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7609             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7610         } else
7611         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7612         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7613           if(appData.sweepSelect) {
7614             promoSweep = defaultPromoChoice;
7615             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7616             selectFlag = 0; lastX = xPix; lastY = yPix;
7617             Sweep(0); // Pawn that is going to promote: preview promotion piece
7618             sweepSelecting = 1;
7619             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7620             MarkTargetSquares(1);
7621           }
7622           return; // promo popup appears on up-click
7623         }
7624         /* Finish clickclick move */
7625         if (appData.animate || appData.highlightLastMove) {
7626             SetHighlights(fromX, fromY, toX, toY);
7627         } else {
7628             ClearHighlights();
7629         }
7630     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7631         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7632         if (appData.animate || appData.highlightLastMove) {
7633             SetHighlights(fromX, fromY, toX, toY);
7634         } else {
7635             ClearHighlights();
7636         }
7637     } else {
7638 #if 0
7639 // [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
7640         /* Finish drag move */
7641         if (appData.highlightLastMove) {
7642             SetHighlights(fromX, fromY, toX, toY);
7643         } else {
7644             ClearHighlights();
7645         }
7646 #endif
7647         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7648         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7649           dragging *= 2;            // flag button-less dragging if we are dragging
7650           MarkTargetSquares(1);
7651           if(x == killX && y == killY) killX = killY = -1; else {
7652             killX = x; killY = y;     //remeber this square as intermediate
7653             ReportClick("put", x, y); // and inform engine
7654             ReportClick("lift", x, y);
7655             MarkTargetSquares(0);
7656             return;
7657           }
7658         }
7659         DragPieceEnd(xPix, yPix); dragging = 0;
7660         /* Don't animate move and drag both */
7661         appData.animate = FALSE;
7662     }
7663
7664     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7665     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7666         ChessSquare piece = boards[currentMove][fromY][fromX];
7667         if(gameMode == EditPosition && piece != EmptySquare &&
7668            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7669             int n;
7670
7671             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7672                 n = PieceToNumber(piece - (int)BlackPawn);
7673                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7674                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7675                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7676             } else
7677             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7678                 n = PieceToNumber(piece);
7679                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7680                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7681                 boards[currentMove][n][BOARD_WIDTH-2]++;
7682             }
7683             boards[currentMove][fromY][fromX] = EmptySquare;
7684         }
7685         ClearHighlights();
7686         fromX = fromY = -1;
7687         MarkTargetSquares(1);
7688         DrawPosition(TRUE, boards[currentMove]);
7689         return;
7690     }
7691
7692     // off-board moves should not be highlighted
7693     if(x < 0 || y < 0) ClearHighlights();
7694     else ReportClick("put", x, y);
7695
7696     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7697
7698     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7699         SetHighlights(fromX, fromY, toX, toY);
7700         MarkTargetSquares(1);
7701         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7702             // [HGM] super: promotion to captured piece selected from holdings
7703             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7704             promotionChoice = TRUE;
7705             // kludge follows to temporarily execute move on display, without promoting yet
7706             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7707             boards[currentMove][toY][toX] = p;
7708             DrawPosition(FALSE, boards[currentMove]);
7709             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7710             boards[currentMove][toY][toX] = q;
7711             DisplayMessage("Click in holdings to choose piece", "");
7712             return;
7713         }
7714         PromotionPopUp(promoChoice);
7715     } else {
7716         int oldMove = currentMove;
7717         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7718         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7719         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7720         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7721            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7722             DrawPosition(TRUE, boards[currentMove]);
7723         MarkTargetSquares(1);
7724         fromX = fromY = -1;
7725     }
7726     appData.animate = saveAnimate;
7727     if (appData.animate || appData.animateDragging) {
7728         /* Undo animation damage if needed */
7729         DrawPosition(FALSE, NULL);
7730     }
7731 }
7732
7733 int
7734 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7735 {   // front-end-free part taken out of PieceMenuPopup
7736     int whichMenu; int xSqr, ySqr;
7737
7738     if(seekGraphUp) { // [HGM] seekgraph
7739         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7740         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7741         return -2;
7742     }
7743
7744     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7745          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7746         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7747         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7748         if(action == Press)   {
7749             originalFlip = flipView;
7750             flipView = !flipView; // temporarily flip board to see game from partners perspective
7751             DrawPosition(TRUE, partnerBoard);
7752             DisplayMessage(partnerStatus, "");
7753             partnerUp = TRUE;
7754         } else if(action == Release) {
7755             flipView = originalFlip;
7756             DrawPosition(TRUE, boards[currentMove]);
7757             partnerUp = FALSE;
7758         }
7759         return -2;
7760     }
7761
7762     xSqr = EventToSquare(x, BOARD_WIDTH);
7763     ySqr = EventToSquare(y, BOARD_HEIGHT);
7764     if (action == Release) {
7765         if(pieceSweep != EmptySquare) {
7766             EditPositionMenuEvent(pieceSweep, toX, toY);
7767             pieceSweep = EmptySquare;
7768         } else UnLoadPV(); // [HGM] pv
7769     }
7770     if (action != Press) return -2; // return code to be ignored
7771     switch (gameMode) {
7772       case IcsExamining:
7773         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7774       case EditPosition:
7775         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7776         if (xSqr < 0 || ySqr < 0) return -1;
7777         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7778         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7779         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7780         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7781         NextPiece(0);
7782         return 2; // grab
7783       case IcsObserving:
7784         if(!appData.icsEngineAnalyze) return -1;
7785       case IcsPlayingWhite:
7786       case IcsPlayingBlack:
7787         if(!appData.zippyPlay) goto noZip;
7788       case AnalyzeMode:
7789       case AnalyzeFile:
7790       case MachinePlaysWhite:
7791       case MachinePlaysBlack:
7792       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7793         if (!appData.dropMenu) {
7794           LoadPV(x, y);
7795           return 2; // flag front-end to grab mouse events
7796         }
7797         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7798            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7799       case EditGame:
7800       noZip:
7801         if (xSqr < 0 || ySqr < 0) return -1;
7802         if (!appData.dropMenu || appData.testLegality &&
7803             gameInfo.variant != VariantBughouse &&
7804             gameInfo.variant != VariantCrazyhouse) return -1;
7805         whichMenu = 1; // drop menu
7806         break;
7807       default:
7808         return -1;
7809     }
7810
7811     if (((*fromX = xSqr) < 0) ||
7812         ((*fromY = ySqr) < 0)) {
7813         *fromX = *fromY = -1;
7814         return -1;
7815     }
7816     if (flipView)
7817       *fromX = BOARD_WIDTH - 1 - *fromX;
7818     else
7819       *fromY = BOARD_HEIGHT - 1 - *fromY;
7820
7821     return whichMenu;
7822 }
7823
7824 void
7825 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7826 {
7827 //    char * hint = lastHint;
7828     FrontEndProgramStats stats;
7829
7830     stats.which = cps == &first ? 0 : 1;
7831     stats.depth = cpstats->depth;
7832     stats.nodes = cpstats->nodes;
7833     stats.score = cpstats->score;
7834     stats.time = cpstats->time;
7835     stats.pv = cpstats->movelist;
7836     stats.hint = lastHint;
7837     stats.an_move_index = 0;
7838     stats.an_move_count = 0;
7839
7840     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7841         stats.hint = cpstats->move_name;
7842         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7843         stats.an_move_count = cpstats->nr_moves;
7844     }
7845
7846     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
7847
7848     SetProgramStats( &stats );
7849 }
7850
7851 void
7852 ClearEngineOutputPane (int which)
7853 {
7854     static FrontEndProgramStats dummyStats;
7855     dummyStats.which = which;
7856     dummyStats.pv = "#";
7857     SetProgramStats( &dummyStats );
7858 }
7859
7860 #define MAXPLAYERS 500
7861
7862 char *
7863 TourneyStandings (int display)
7864 {
7865     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7866     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7867     char result, *p, *names[MAXPLAYERS];
7868
7869     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7870         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7871     names[0] = p = strdup(appData.participants);
7872     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7873
7874     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7875
7876     while(result = appData.results[nr]) {
7877         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7878         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7879         wScore = bScore = 0;
7880         switch(result) {
7881           case '+': wScore = 2; break;
7882           case '-': bScore = 2; break;
7883           case '=': wScore = bScore = 1; break;
7884           case ' ':
7885           case '*': return strdup("busy"); // tourney not finished
7886         }
7887         score[w] += wScore;
7888         score[b] += bScore;
7889         games[w]++;
7890         games[b]++;
7891         nr++;
7892     }
7893     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7894     for(w=0; w<nPlayers; w++) {
7895         bScore = -1;
7896         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7897         ranking[w] = b; points[w] = bScore; score[b] = -2;
7898     }
7899     p = malloc(nPlayers*34+1);
7900     for(w=0; w<nPlayers && w<display; w++)
7901         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7902     free(names[0]);
7903     return p;
7904 }
7905
7906 void
7907 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7908 {       // count all piece types
7909         int p, f, r;
7910         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7911         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7912         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7913                 p = board[r][f];
7914                 pCnt[p]++;
7915                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7916                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7917                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7918                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7919                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7920                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7921         }
7922 }
7923
7924 int
7925 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7926 {
7927         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7928         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7929
7930         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7931         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7932         if(myPawns == 2 && nMine == 3) // KPP
7933             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7934         if(myPawns == 1 && nMine == 2) // KP
7935             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7936         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7937             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7938         if(myPawns) return FALSE;
7939         if(pCnt[WhiteRook+side])
7940             return pCnt[BlackRook-side] ||
7941                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7942                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7943                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7944         if(pCnt[WhiteCannon+side]) {
7945             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7946             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7947         }
7948         if(pCnt[WhiteKnight+side])
7949             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7950         return FALSE;
7951 }
7952
7953 int
7954 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7955 {
7956         VariantClass v = gameInfo.variant;
7957
7958         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7959         if(v == VariantShatranj) return TRUE; // always winnable through baring
7960         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7961         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7962
7963         if(v == VariantXiangqi) {
7964                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7965
7966                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7967                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7968                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7969                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7970                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7971                 if(stale) // we have at least one last-rank P plus perhaps C
7972                     return majors // KPKX
7973                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7974                 else // KCA*E*
7975                     return pCnt[WhiteFerz+side] // KCAK
7976                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7977                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7978                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7979
7980         } else if(v == VariantKnightmate) {
7981                 if(nMine == 1) return FALSE;
7982                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7983         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7984                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7985
7986                 if(nMine == 1) return FALSE; // bare King
7987                 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
7988                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7989                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7990                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7991                 if(pCnt[WhiteKnight+side])
7992                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7993                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7994                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7995                 if(nBishops)
7996                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7997                 if(pCnt[WhiteAlfil+side])
7998                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7999                 if(pCnt[WhiteWazir+side])
8000                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8001         }
8002
8003         return TRUE;
8004 }
8005
8006 int
8007 CompareWithRights (Board b1, Board b2)
8008 {
8009     int rights = 0;
8010     if(!CompareBoards(b1, b2)) return FALSE;
8011     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8012     /* compare castling rights */
8013     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8014            rights++; /* King lost rights, while rook still had them */
8015     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8016         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8017            rights++; /* but at least one rook lost them */
8018     }
8019     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8020            rights++;
8021     if( b1[CASTLING][5] != NoRights ) {
8022         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8023            rights++;
8024     }
8025     return rights == 0;
8026 }
8027
8028 int
8029 Adjudicate (ChessProgramState *cps)
8030 {       // [HGM] some adjudications useful with buggy engines
8031         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8032         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8033         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8034         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8035         int k, drop, count = 0; static int bare = 1;
8036         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8037         Boolean canAdjudicate = !appData.icsActive;
8038
8039         // most tests only when we understand the game, i.e. legality-checking on
8040             if( appData.testLegality )
8041             {   /* [HGM] Some more adjudications for obstinate engines */
8042                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8043                 static int moveCount = 6;
8044                 ChessMove result;
8045                 char *reason = NULL;
8046
8047                 /* Count what is on board. */
8048                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8049
8050                 /* Some material-based adjudications that have to be made before stalemate test */
8051                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8052                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8053                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8054                      if(canAdjudicate && appData.checkMates) {
8055                          if(engineOpponent)
8056                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8057                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8058                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8059                          return 1;
8060                      }
8061                 }
8062
8063                 /* Bare King in Shatranj (loses) or Losers (wins) */
8064                 if( nrW == 1 || nrB == 1) {
8065                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8066                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8067                      if(canAdjudicate && appData.checkMates) {
8068                          if(engineOpponent)
8069                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8070                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8071                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8072                          return 1;
8073                      }
8074                   } else
8075                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8076                   {    /* bare King */
8077                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8078                         if(canAdjudicate && appData.checkMates) {
8079                             /* but only adjudicate if adjudication enabled */
8080                             if(engineOpponent)
8081                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8082                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8083                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8084                             return 1;
8085                         }
8086                   }
8087                 } else bare = 1;
8088
8089
8090             // don't wait for engine to announce game end if we can judge ourselves
8091             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8092               case MT_CHECK:
8093                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8094                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8095                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8096                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8097                             checkCnt++;
8098                         if(checkCnt >= 2) {
8099                             reason = "Xboard adjudication: 3rd check";
8100                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8101                             break;
8102                         }
8103                     }
8104                 }
8105               case MT_NONE:
8106               default:
8107                 break;
8108               case MT_STEALMATE:
8109               case MT_STALEMATE:
8110               case MT_STAINMATE:
8111                 reason = "Xboard adjudication: Stalemate";
8112                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8113                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8114                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8115                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8116                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8117                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8118                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8119                                                                         EP_CHECKMATE : EP_WINS);
8120                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8121                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8122                 }
8123                 break;
8124               case MT_CHECKMATE:
8125                 reason = "Xboard adjudication: Checkmate";
8126                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8127                 if(gameInfo.variant == VariantShogi) {
8128                     if(forwardMostMove > backwardMostMove
8129                        && moveList[forwardMostMove-1][1] == '@'
8130                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8131                         reason = "XBoard adjudication: pawn-drop mate";
8132                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8133                     }
8134                 }
8135                 break;
8136             }
8137
8138                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8139                     case EP_STALEMATE:
8140                         result = GameIsDrawn; break;
8141                     case EP_CHECKMATE:
8142                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8143                     case EP_WINS:
8144                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8145                     default:
8146                         result = EndOfFile;
8147                 }
8148                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8149                     if(engineOpponent)
8150                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8151                     GameEnds( result, reason, GE_XBOARD );
8152                     return 1;
8153                 }
8154
8155                 /* Next absolutely insufficient mating material. */
8156                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8157                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8158                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8159
8160                      /* always flag draws, for judging claims */
8161                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8162
8163                      if(canAdjudicate && appData.materialDraws) {
8164                          /* but only adjudicate them if adjudication enabled */
8165                          if(engineOpponent) {
8166                            SendToProgram("force\n", engineOpponent); // suppress reply
8167                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8168                          }
8169                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8170                          return 1;
8171                      }
8172                 }
8173
8174                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8175                 if(gameInfo.variant == VariantXiangqi ?
8176                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8177                  : nrW + nrB == 4 &&
8178                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8179                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8180                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8181                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8182                    ) ) {
8183                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8184                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8185                           if(engineOpponent) {
8186                             SendToProgram("force\n", engineOpponent); // suppress reply
8187                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8188                           }
8189                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8190                           return 1;
8191                      }
8192                 } else moveCount = 6;
8193             }
8194
8195         // Repetition draws and 50-move rule can be applied independently of legality testing
8196
8197                 /* Check for rep-draws */
8198                 count = 0;
8199                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8200                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8201                 for(k = forwardMostMove-2;
8202                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8203                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8204                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8205                     k-=2)
8206                 {   int rights=0;
8207                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8208                         /* compare castling rights */
8209                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8210                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8211                                 rights++; /* King lost rights, while rook still had them */
8212                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8213                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8214                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8215                                    rights++; /* but at least one rook lost them */
8216                         }
8217                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8218                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8219                                 rights++;
8220                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8221                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8222                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8223                                    rights++;
8224                         }
8225                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8226                             && appData.drawRepeats > 1) {
8227                              /* adjudicate after user-specified nr of repeats */
8228                              int result = GameIsDrawn;
8229                              char *details = "XBoard adjudication: repetition draw";
8230                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8231                                 // [HGM] xiangqi: check for forbidden perpetuals
8232                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8233                                 for(m=forwardMostMove; m>k; m-=2) {
8234                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8235                                         ourPerpetual = 0; // the current mover did not always check
8236                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8237                                         hisPerpetual = 0; // the opponent did not always check
8238                                 }
8239                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8240                                                                         ourPerpetual, hisPerpetual);
8241                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8242                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8243                                     details = "Xboard adjudication: perpetual checking";
8244                                 } else
8245                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8246                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8247                                 } else
8248                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8249                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8250                                         result = BlackWins;
8251                                         details = "Xboard adjudication: repetition";
8252                                     }
8253                                 } else // it must be XQ
8254                                 // Now check for perpetual chases
8255                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8256                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8257                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8258                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8259                                         static char resdet[MSG_SIZ];
8260                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8261                                         details = resdet;
8262                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8263                                     } else
8264                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8265                                         break; // Abort repetition-checking loop.
8266                                 }
8267                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8268                              }
8269                              if(engineOpponent) {
8270                                SendToProgram("force\n", engineOpponent); // suppress reply
8271                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8272                              }
8273                              GameEnds( result, details, GE_XBOARD );
8274                              return 1;
8275                         }
8276                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8277                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8278                     }
8279                 }
8280
8281                 /* Now we test for 50-move draws. Determine ply count */
8282                 count = forwardMostMove;
8283                 /* look for last irreversble move */
8284                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8285                     count--;
8286                 /* if we hit starting position, add initial plies */
8287                 if( count == backwardMostMove )
8288                     count -= initialRulePlies;
8289                 count = forwardMostMove - count;
8290                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8291                         // adjust reversible move counter for checks in Xiangqi
8292                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8293                         if(i < backwardMostMove) i = backwardMostMove;
8294                         while(i <= forwardMostMove) {
8295                                 lastCheck = inCheck; // check evasion does not count
8296                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8297                                 if(inCheck || lastCheck) count--; // check does not count
8298                                 i++;
8299                         }
8300                 }
8301                 if( count >= 100)
8302                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8303                          /* this is used to judge if draw claims are legal */
8304                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8305                          if(engineOpponent) {
8306                            SendToProgram("force\n", engineOpponent); // suppress reply
8307                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8308                          }
8309                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8310                          return 1;
8311                 }
8312
8313                 /* if draw offer is pending, treat it as a draw claim
8314                  * when draw condition present, to allow engines a way to
8315                  * claim draws before making their move to avoid a race
8316                  * condition occurring after their move
8317                  */
8318                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8319                          char *p = NULL;
8320                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8321                              p = "Draw claim: 50-move rule";
8322                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8323                              p = "Draw claim: 3-fold repetition";
8324                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8325                              p = "Draw claim: insufficient mating material";
8326                          if( p != NULL && canAdjudicate) {
8327                              if(engineOpponent) {
8328                                SendToProgram("force\n", engineOpponent); // suppress reply
8329                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8330                              }
8331                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8332                              return 1;
8333                          }
8334                 }
8335
8336                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8337                     if(engineOpponent) {
8338                       SendToProgram("force\n", engineOpponent); // suppress reply
8339                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8340                     }
8341                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8342                     return 1;
8343                 }
8344         return 0;
8345 }
8346
8347 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8348 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8349 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8350
8351 static int
8352 BitbaseProbe ()
8353 {
8354     int pieces[10], squares[10], cnt=0, r, f, res;
8355     static int loaded;
8356     static PPROBE_EGBB probeBB;
8357     if(!appData.testLegality) return 10;
8358     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8359     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8360     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8361     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8362         ChessSquare piece = boards[forwardMostMove][r][f];
8363         int black = (piece >= BlackPawn);
8364         int type = piece - black*BlackPawn;
8365         if(piece == EmptySquare) continue;
8366         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8367         if(type == WhiteKing) type = WhiteQueen + 1;
8368         type = egbbCode[type];
8369         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8370         pieces[cnt] = type + black*6;
8371         if(++cnt > 5) return 11;
8372     }
8373     pieces[cnt] = squares[cnt] = 0;
8374     // probe EGBB
8375     if(loaded == 2) return 13; // loading failed before
8376     if(loaded == 0) {
8377         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8378         HMODULE lib;
8379         PLOAD_EGBB loadBB;
8380         loaded = 2; // prepare for failure
8381         if(!path) return 13; // no egbb installed
8382         strncpy(buf, path + 8, MSG_SIZ);
8383         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8384         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8385         lib = LoadLibrary(buf);
8386         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8387         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8388         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8389         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8390         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8391         loaded = 1; // success!
8392     }
8393     res = probeBB(forwardMostMove & 1, pieces, squares);
8394     return res > 0 ? 1 : res < 0 ? -1 : 0;
8395 }
8396
8397 char *
8398 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8399 {   // [HGM] book: this routine intercepts moves to simulate book replies
8400     char *bookHit = NULL;
8401
8402     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8403         char buf[MSG_SIZ];
8404         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8405         SendToProgram(buf, cps);
8406     }
8407     //first determine if the incoming move brings opponent into his book
8408     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8409         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8410     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8411     if(bookHit != NULL && !cps->bookSuspend) {
8412         // make sure opponent is not going to reply after receiving move to book position
8413         SendToProgram("force\n", cps);
8414         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8415     }
8416     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8417     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8418     // now arrange restart after book miss
8419     if(bookHit) {
8420         // after a book hit we never send 'go', and the code after the call to this routine
8421         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8422         char buf[MSG_SIZ], *move = bookHit;
8423         if(cps->useSAN) {
8424             int fromX, fromY, toX, toY;
8425             char promoChar;
8426             ChessMove moveType;
8427             move = buf + 30;
8428             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8429                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8430                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8431                                     PosFlags(forwardMostMove),
8432                                     fromY, fromX, toY, toX, promoChar, move);
8433             } else {
8434                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8435                 bookHit = NULL;
8436             }
8437         }
8438         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8439         SendToProgram(buf, cps);
8440         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8441     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8442         SendToProgram("go\n", cps);
8443         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8444     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8445         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8446             SendToProgram("go\n", cps);
8447         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8448     }
8449     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8450 }
8451
8452 int
8453 LoadError (char *errmess, ChessProgramState *cps)
8454 {   // unloads engine and switches back to -ncp mode if it was first
8455     if(cps->initDone) return FALSE;
8456     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8457     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8458     cps->pr = NoProc;
8459     if(cps == &first) {
8460         appData.noChessProgram = TRUE;
8461         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8462         gameMode = BeginningOfGame; ModeHighlight();
8463         SetNCPMode();
8464     }
8465     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8466     DisplayMessage("", ""); // erase waiting message
8467     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8468     return TRUE;
8469 }
8470
8471 char *savedMessage;
8472 ChessProgramState *savedState;
8473 void
8474 DeferredBookMove (void)
8475 {
8476         if(savedState->lastPing != savedState->lastPong)
8477                     ScheduleDelayedEvent(DeferredBookMove, 10);
8478         else
8479         HandleMachineMove(savedMessage, savedState);
8480 }
8481
8482 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8483 static ChessProgramState *stalledEngine;
8484 static char stashedInputMove[MSG_SIZ];
8485
8486 void
8487 HandleMachineMove (char *message, ChessProgramState *cps)
8488 {
8489     static char firstLeg[20];
8490     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8491     char realname[MSG_SIZ];
8492     int fromX, fromY, toX, toY;
8493     ChessMove moveType;
8494     char promoChar, roar;
8495     char *p, *pv=buf1;
8496     int machineWhite, oldError;
8497     char *bookHit;
8498
8499     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8500         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8501         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8502             DisplayError(_("Invalid pairing from pairing engine"), 0);
8503             return;
8504         }
8505         pairingReceived = 1;
8506         NextMatchGame();
8507         return; // Skim the pairing messages here.
8508     }
8509
8510     oldError = cps->userError; cps->userError = 0;
8511
8512 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8513     /*
8514      * Kludge to ignore BEL characters
8515      */
8516     while (*message == '\007') message++;
8517
8518     /*
8519      * [HGM] engine debug message: ignore lines starting with '#' character
8520      */
8521     if(cps->debug && *message == '#') return;
8522
8523     /*
8524      * Look for book output
8525      */
8526     if (cps == &first && bookRequested) {
8527         if (message[0] == '\t' || message[0] == ' ') {
8528             /* Part of the book output is here; append it */
8529             strcat(bookOutput, message);
8530             strcat(bookOutput, "  \n");
8531             return;
8532         } else if (bookOutput[0] != NULLCHAR) {
8533             /* All of book output has arrived; display it */
8534             char *p = bookOutput;
8535             while (*p != NULLCHAR) {
8536                 if (*p == '\t') *p = ' ';
8537                 p++;
8538             }
8539             DisplayInformation(bookOutput);
8540             bookRequested = FALSE;
8541             /* Fall through to parse the current output */
8542         }
8543     }
8544
8545     /*
8546      * Look for machine move.
8547      */
8548     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8549         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8550     {
8551         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8552             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8553             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8554             stalledEngine = cps;
8555             if(appData.ponderNextMove) { // bring opponent out of ponder
8556                 if(gameMode == TwoMachinesPlay) {
8557                     if(cps->other->pause)
8558                         PauseEngine(cps->other);
8559                     else
8560                         SendToProgram("easy\n", cps->other);
8561                 }
8562             }
8563             StopClocks();
8564             return;
8565         }
8566
8567         /* This method is only useful on engines that support ping */
8568         if (cps->lastPing != cps->lastPong) {
8569           if (gameMode == BeginningOfGame) {
8570             /* Extra move from before last new; ignore */
8571             if (appData.debugMode) {
8572                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8573             }
8574           } else {
8575             if (appData.debugMode) {
8576                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8577                         cps->which, gameMode);
8578             }
8579
8580             SendToProgram("undo\n", cps);
8581           }
8582           return;
8583         }
8584
8585         switch (gameMode) {
8586           case BeginningOfGame:
8587             /* Extra move from before last reset; ignore */
8588             if (appData.debugMode) {
8589                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8590             }
8591             return;
8592
8593           case EndOfGame:
8594           case IcsIdle:
8595           default:
8596             /* Extra move after we tried to stop.  The mode test is
8597                not a reliable way of detecting this problem, but it's
8598                the best we can do on engines that don't support ping.
8599             */
8600             if (appData.debugMode) {
8601                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8602                         cps->which, gameMode);
8603             }
8604             SendToProgram("undo\n", cps);
8605             return;
8606
8607           case MachinePlaysWhite:
8608           case IcsPlayingWhite:
8609             machineWhite = TRUE;
8610             break;
8611
8612           case MachinePlaysBlack:
8613           case IcsPlayingBlack:
8614             machineWhite = FALSE;
8615             break;
8616
8617           case TwoMachinesPlay:
8618             machineWhite = (cps->twoMachinesColor[0] == 'w');
8619             break;
8620         }
8621         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8622             if (appData.debugMode) {
8623                 fprintf(debugFP,
8624                         "Ignoring move out of turn by %s, gameMode %d"
8625                         ", forwardMost %d\n",
8626                         cps->which, gameMode, forwardMostMove);
8627             }
8628             return;
8629         }
8630
8631         if(cps->alphaRank) AlphaRank(machineMove, 4);
8632
8633         // [HGM] lion: (some very limited) support for Alien protocol
8634         killX = killY = -1;
8635         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8636             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8637             return;
8638         } else if(firstLeg[0]) { // there was a previous leg;
8639             // only support case where same piece makes two step (and don't even test that!)
8640             char buf[20], *p = machineMove+1, *q = buf+1, f;
8641             safeStrCpy(buf, machineMove, 20);
8642             while(isdigit(*q)) q++; // find start of to-square
8643             safeStrCpy(machineMove, firstLeg, 20);
8644             while(isdigit(*p)) p++;
8645             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8646             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8647             firstLeg[0] = NULLCHAR;
8648         }
8649
8650         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8651                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8652             /* Machine move could not be parsed; ignore it. */
8653           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8654                     machineMove, _(cps->which));
8655             DisplayMoveError(buf1);
8656             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8657                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8658             if (gameMode == TwoMachinesPlay) {
8659               GameEnds(machineWhite ? BlackWins : WhiteWins,
8660                        buf1, GE_XBOARD);
8661             }
8662             return;
8663         }
8664
8665         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8666         /* So we have to redo legality test with true e.p. status here,  */
8667         /* to make sure an illegal e.p. capture does not slip through,   */
8668         /* to cause a forfeit on a justified illegal-move complaint      */
8669         /* of the opponent.                                              */
8670         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8671            ChessMove moveType;
8672            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8673                              fromY, fromX, toY, toX, promoChar);
8674             if(moveType == IllegalMove) {
8675               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8676                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8677                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8678                            buf1, GE_XBOARD);
8679                 return;
8680            } else if(!appData.fischerCastling)
8681            /* [HGM] Kludge to handle engines that send FRC-style castling
8682               when they shouldn't (like TSCP-Gothic) */
8683            switch(moveType) {
8684              case WhiteASideCastleFR:
8685              case BlackASideCastleFR:
8686                toX+=2;
8687                currentMoveString[2]++;
8688                break;
8689              case WhiteHSideCastleFR:
8690              case BlackHSideCastleFR:
8691                toX--;
8692                currentMoveString[2]--;
8693                break;
8694              default: ; // nothing to do, but suppresses warning of pedantic compilers
8695            }
8696         }
8697         hintRequested = FALSE;
8698         lastHint[0] = NULLCHAR;
8699         bookRequested = FALSE;
8700         /* Program may be pondering now */
8701         cps->maybeThinking = TRUE;
8702         if (cps->sendTime == 2) cps->sendTime = 1;
8703         if (cps->offeredDraw) cps->offeredDraw--;
8704
8705         /* [AS] Save move info*/
8706         pvInfoList[ forwardMostMove ].score = programStats.score;
8707         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8708         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8709
8710         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8711
8712         /* Test suites abort the 'game' after one move */
8713         if(*appData.finger) {
8714            static FILE *f;
8715            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8716            if(!f) f = fopen(appData.finger, "w");
8717            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8718            else { DisplayFatalError("Bad output file", errno, 0); return; }
8719            free(fen);
8720            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8721         }
8722
8723         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8724         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8725             int count = 0;
8726
8727             while( count < adjudicateLossPlies ) {
8728                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8729
8730                 if( count & 1 ) {
8731                     score = -score; /* Flip score for winning side */
8732                 }
8733
8734                 if( score > appData.adjudicateLossThreshold ) {
8735                     break;
8736                 }
8737
8738                 count++;
8739             }
8740
8741             if( count >= adjudicateLossPlies ) {
8742                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8743
8744                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8745                     "Xboard adjudication",
8746                     GE_XBOARD );
8747
8748                 return;
8749             }
8750         }
8751
8752         if(Adjudicate(cps)) {
8753             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8754             return; // [HGM] adjudicate: for all automatic game ends
8755         }
8756
8757 #if ZIPPY
8758         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8759             first.initDone) {
8760           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8761                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8762                 SendToICS("draw ");
8763                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8764           }
8765           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8766           ics_user_moved = 1;
8767           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8768                 char buf[3*MSG_SIZ];
8769
8770                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8771                         programStats.score / 100.,
8772                         programStats.depth,
8773                         programStats.time / 100.,
8774                         (unsigned int)programStats.nodes,
8775                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8776                         programStats.movelist);
8777                 SendToICS(buf);
8778           }
8779         }
8780 #endif
8781
8782         /* [AS] Clear stats for next move */
8783         ClearProgramStats();
8784         thinkOutput[0] = NULLCHAR;
8785         hiddenThinkOutputState = 0;
8786
8787         bookHit = NULL;
8788         if (gameMode == TwoMachinesPlay) {
8789             /* [HGM] relaying draw offers moved to after reception of move */
8790             /* and interpreting offer as claim if it brings draw condition */
8791             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8792                 SendToProgram("draw\n", cps->other);
8793             }
8794             if (cps->other->sendTime) {
8795                 SendTimeRemaining(cps->other,
8796                                   cps->other->twoMachinesColor[0] == 'w');
8797             }
8798             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8799             if (firstMove && !bookHit) {
8800                 firstMove = FALSE;
8801                 if (cps->other->useColors) {
8802                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8803                 }
8804                 SendToProgram("go\n", cps->other);
8805             }
8806             cps->other->maybeThinking = TRUE;
8807         }
8808
8809         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8810
8811         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8812
8813         if (!pausing && appData.ringBellAfterMoves) {
8814             if(!roar) RingBell();
8815         }
8816
8817         /*
8818          * Reenable menu items that were disabled while
8819          * machine was thinking
8820          */
8821         if (gameMode != TwoMachinesPlay)
8822             SetUserThinkingEnables();
8823
8824         // [HGM] book: after book hit opponent has received move and is now in force mode
8825         // force the book reply into it, and then fake that it outputted this move by jumping
8826         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8827         if(bookHit) {
8828                 static char bookMove[MSG_SIZ]; // a bit generous?
8829
8830                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8831                 strcat(bookMove, bookHit);
8832                 message = bookMove;
8833                 cps = cps->other;
8834                 programStats.nodes = programStats.depth = programStats.time =
8835                 programStats.score = programStats.got_only_move = 0;
8836                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8837
8838                 if(cps->lastPing != cps->lastPong) {
8839                     savedMessage = message; // args for deferred call
8840                     savedState = cps;
8841                     ScheduleDelayedEvent(DeferredBookMove, 10);
8842                     return;
8843                 }
8844                 goto FakeBookMove;
8845         }
8846
8847         return;
8848     }
8849
8850     /* Set special modes for chess engines.  Later something general
8851      *  could be added here; for now there is just one kludge feature,
8852      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8853      *  when "xboard" is given as an interactive command.
8854      */
8855     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8856         cps->useSigint = FALSE;
8857         cps->useSigterm = FALSE;
8858     }
8859     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8860       ParseFeatures(message+8, cps);
8861       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8862     }
8863
8864     if (!strncmp(message, "setup ", 6) && 
8865         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8866           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8867                                         ) { // [HGM] allow first engine to define opening position
8868       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8869       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8870       *buf = NULLCHAR;
8871       if(sscanf(message, "setup (%s", buf) == 1) {
8872         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8873         ASSIGN(appData.pieceToCharTable, buf);
8874       }
8875       if(startedFromSetupPosition) return;
8876       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8877       if(dummy >= 3) {
8878         while(message[s] && message[s++] != ' ');
8879         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8880            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8881             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8882             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8883           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8884           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8885         }
8886       }
8887       ParseFEN(boards[0], &dummy, message+s, FALSE);
8888       DrawPosition(TRUE, boards[0]);
8889       startedFromSetupPosition = TRUE;
8890       return;
8891     }
8892     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8893       ChessSquare piece = WhitePawn;
8894       char *p=buf2;
8895       if(cps != &first || appData.testLegality) return;
8896       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8897       piece += CharToPiece(*p) - WhitePawn;
8898       if(piece < EmptySquare) {
8899         pieceDefs = TRUE;
8900         ASSIGN(pieceDesc[piece], buf1);
8901         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8902       }
8903       return;
8904     }
8905     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8906      * want this, I was asked to put it in, and obliged.
8907      */
8908     if (!strncmp(message, "setboard ", 9)) {
8909         Board initial_position;
8910
8911         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8912
8913         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8914             DisplayError(_("Bad FEN received from engine"), 0);
8915             return ;
8916         } else {
8917            Reset(TRUE, FALSE);
8918            CopyBoard(boards[0], initial_position);
8919            initialRulePlies = FENrulePlies;
8920            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8921            else gameMode = MachinePlaysBlack;
8922            DrawPosition(FALSE, boards[currentMove]);
8923         }
8924         return;
8925     }
8926
8927     /*
8928      * Look for communication commands
8929      */
8930     if (!strncmp(message, "telluser ", 9)) {
8931         if(message[9] == '\\' && message[10] == '\\')
8932             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8933         PlayTellSound();
8934         DisplayNote(message + 9);
8935         return;
8936     }
8937     if (!strncmp(message, "tellusererror ", 14)) {
8938         cps->userError = 1;
8939         if(message[14] == '\\' && message[15] == '\\')
8940             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8941         PlayTellSound();
8942         DisplayError(message + 14, 0);
8943         return;
8944     }
8945     if (!strncmp(message, "tellopponent ", 13)) {
8946       if (appData.icsActive) {
8947         if (loggedOn) {
8948           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8949           SendToICS(buf1);
8950         }
8951       } else {
8952         DisplayNote(message + 13);
8953       }
8954       return;
8955     }
8956     if (!strncmp(message, "tellothers ", 11)) {
8957       if (appData.icsActive) {
8958         if (loggedOn) {
8959           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8960           SendToICS(buf1);
8961         }
8962       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8963       return;
8964     }
8965     if (!strncmp(message, "tellall ", 8)) {
8966       if (appData.icsActive) {
8967         if (loggedOn) {
8968           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8969           SendToICS(buf1);
8970         }
8971       } else {
8972         DisplayNote(message + 8);
8973       }
8974       return;
8975     }
8976     if (strncmp(message, "warning", 7) == 0) {
8977         /* Undocumented feature, use tellusererror in new code */
8978         DisplayError(message, 0);
8979         return;
8980     }
8981     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8982         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8983         strcat(realname, " query");
8984         AskQuestion(realname, buf2, buf1, cps->pr);
8985         return;
8986     }
8987     /* Commands from the engine directly to ICS.  We don't allow these to be
8988      *  sent until we are logged on. Crafty kibitzes have been known to
8989      *  interfere with the login process.
8990      */
8991     if (loggedOn) {
8992         if (!strncmp(message, "tellics ", 8)) {
8993             SendToICS(message + 8);
8994             SendToICS("\n");
8995             return;
8996         }
8997         if (!strncmp(message, "tellicsnoalias ", 15)) {
8998             SendToICS(ics_prefix);
8999             SendToICS(message + 15);
9000             SendToICS("\n");
9001             return;
9002         }
9003         /* The following are for backward compatibility only */
9004         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9005             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9006             SendToICS(ics_prefix);
9007             SendToICS(message);
9008             SendToICS("\n");
9009             return;
9010         }
9011     }
9012     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9013         if(initPing == cps->lastPong) {
9014             if(gameInfo.variant == VariantUnknown) {
9015                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9016                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9017                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9018             }
9019             initPing = -1;
9020         }
9021         return;
9022     }
9023     if(!strncmp(message, "highlight ", 10)) {
9024         if(appData.testLegality && appData.markers) return;
9025         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9026         return;
9027     }
9028     if(!strncmp(message, "click ", 6)) {
9029         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9030         if(appData.testLegality || !appData.oneClick) return;
9031         sscanf(message+6, "%c%d%c", &f, &y, &c);
9032         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9033         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9034         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9035         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9036         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9037         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9038             LeftClick(Release, lastLeftX, lastLeftY);
9039         controlKey  = (c == ',');
9040         LeftClick(Press, x, y);
9041         LeftClick(Release, x, y);
9042         first.highlight = f;
9043         return;
9044     }
9045     /*
9046      * If the move is illegal, cancel it and redraw the board.
9047      * Also deal with other error cases.  Matching is rather loose
9048      * here to accommodate engines written before the spec.
9049      */
9050     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9051         strncmp(message, "Error", 5) == 0) {
9052         if (StrStr(message, "name") ||
9053             StrStr(message, "rating") || StrStr(message, "?") ||
9054             StrStr(message, "result") || StrStr(message, "board") ||
9055             StrStr(message, "bk") || StrStr(message, "computer") ||
9056             StrStr(message, "variant") || StrStr(message, "hint") ||
9057             StrStr(message, "random") || StrStr(message, "depth") ||
9058             StrStr(message, "accepted")) {
9059             return;
9060         }
9061         if (StrStr(message, "protover")) {
9062           /* Program is responding to input, so it's apparently done
9063              initializing, and this error message indicates it is
9064              protocol version 1.  So we don't need to wait any longer
9065              for it to initialize and send feature commands. */
9066           FeatureDone(cps, 1);
9067           cps->protocolVersion = 1;
9068           return;
9069         }
9070         cps->maybeThinking = FALSE;
9071
9072         if (StrStr(message, "draw")) {
9073             /* Program doesn't have "draw" command */
9074             cps->sendDrawOffers = 0;
9075             return;
9076         }
9077         if (cps->sendTime != 1 &&
9078             (StrStr(message, "time") || StrStr(message, "otim"))) {
9079           /* Program apparently doesn't have "time" or "otim" command */
9080           cps->sendTime = 0;
9081           return;
9082         }
9083         if (StrStr(message, "analyze")) {
9084             cps->analysisSupport = FALSE;
9085             cps->analyzing = FALSE;
9086 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9087             EditGameEvent(); // [HGM] try to preserve loaded game
9088             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9089             DisplayError(buf2, 0);
9090             return;
9091         }
9092         if (StrStr(message, "(no matching move)st")) {
9093           /* Special kludge for GNU Chess 4 only */
9094           cps->stKludge = TRUE;
9095           SendTimeControl(cps, movesPerSession, timeControl,
9096                           timeIncrement, appData.searchDepth,
9097                           searchTime);
9098           return;
9099         }
9100         if (StrStr(message, "(no matching move)sd")) {
9101           /* Special kludge for GNU Chess 4 only */
9102           cps->sdKludge = TRUE;
9103           SendTimeControl(cps, movesPerSession, timeControl,
9104                           timeIncrement, appData.searchDepth,
9105                           searchTime);
9106           return;
9107         }
9108         if (!StrStr(message, "llegal")) {
9109             return;
9110         }
9111         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9112             gameMode == IcsIdle) return;
9113         if (forwardMostMove <= backwardMostMove) return;
9114         if (pausing) PauseEvent();
9115       if(appData.forceIllegal) {
9116             // [HGM] illegal: machine refused move; force position after move into it
9117           SendToProgram("force\n", cps);
9118           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9119                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9120                 // when black is to move, while there might be nothing on a2 or black
9121                 // might already have the move. So send the board as if white has the move.
9122                 // But first we must change the stm of the engine, as it refused the last move
9123                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9124                 if(WhiteOnMove(forwardMostMove)) {
9125                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9126                     SendBoard(cps, forwardMostMove); // kludgeless board
9127                 } else {
9128                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9129                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9130                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9131                 }
9132           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9133             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9134                  gameMode == TwoMachinesPlay)
9135               SendToProgram("go\n", cps);
9136             return;
9137       } else
9138         if (gameMode == PlayFromGameFile) {
9139             /* Stop reading this game file */
9140             gameMode = EditGame;
9141             ModeHighlight();
9142         }
9143         /* [HGM] illegal-move claim should forfeit game when Xboard */
9144         /* only passes fully legal moves                            */
9145         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9146             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9147                                 "False illegal-move claim", GE_XBOARD );
9148             return; // do not take back move we tested as valid
9149         }
9150         currentMove = forwardMostMove-1;
9151         DisplayMove(currentMove-1); /* before DisplayMoveError */
9152         SwitchClocks(forwardMostMove-1); // [HGM] race
9153         DisplayBothClocks();
9154         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9155                 parseList[currentMove], _(cps->which));
9156         DisplayMoveError(buf1);
9157         DrawPosition(FALSE, boards[currentMove]);
9158
9159         SetUserThinkingEnables();
9160         return;
9161     }
9162     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9163         /* Program has a broken "time" command that
9164            outputs a string not ending in newline.
9165            Don't use it. */
9166         cps->sendTime = 0;
9167     }
9168     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9169         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9170             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9171     }
9172
9173     /*
9174      * If chess program startup fails, exit with an error message.
9175      * Attempts to recover here are futile. [HGM] Well, we try anyway
9176      */
9177     if ((StrStr(message, "unknown host") != NULL)
9178         || (StrStr(message, "No remote directory") != NULL)
9179         || (StrStr(message, "not found") != NULL)
9180         || (StrStr(message, "No such file") != NULL)
9181         || (StrStr(message, "can't alloc") != NULL)
9182         || (StrStr(message, "Permission denied") != NULL)) {
9183
9184         cps->maybeThinking = FALSE;
9185         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9186                 _(cps->which), cps->program, cps->host, message);
9187         RemoveInputSource(cps->isr);
9188         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9189             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9190             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9191         }
9192         return;
9193     }
9194
9195     /*
9196      * Look for hint output
9197      */
9198     if (sscanf(message, "Hint: %s", buf1) == 1) {
9199         if (cps == &first && hintRequested) {
9200             hintRequested = FALSE;
9201             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9202                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9203                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9204                                     PosFlags(forwardMostMove),
9205                                     fromY, fromX, toY, toX, promoChar, buf1);
9206                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9207                 DisplayInformation(buf2);
9208             } else {
9209                 /* Hint move could not be parsed!? */
9210               snprintf(buf2, sizeof(buf2),
9211                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9212                         buf1, _(cps->which));
9213                 DisplayError(buf2, 0);
9214             }
9215         } else {
9216           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9217         }
9218         return;
9219     }
9220
9221     /*
9222      * Ignore other messages if game is not in progress
9223      */
9224     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9225         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9226
9227     /*
9228      * look for win, lose, draw, or draw offer
9229      */
9230     if (strncmp(message, "1-0", 3) == 0) {
9231         char *p, *q, *r = "";
9232         p = strchr(message, '{');
9233         if (p) {
9234             q = strchr(p, '}');
9235             if (q) {
9236                 *q = NULLCHAR;
9237                 r = p + 1;
9238             }
9239         }
9240         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9241         return;
9242     } else if (strncmp(message, "0-1", 3) == 0) {
9243         char *p, *q, *r = "";
9244         p = strchr(message, '{');
9245         if (p) {
9246             q = strchr(p, '}');
9247             if (q) {
9248                 *q = NULLCHAR;
9249                 r = p + 1;
9250             }
9251         }
9252         /* Kludge for Arasan 4.1 bug */
9253         if (strcmp(r, "Black resigns") == 0) {
9254             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9255             return;
9256         }
9257         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9258         return;
9259     } else if (strncmp(message, "1/2", 3) == 0) {
9260         char *p, *q, *r = "";
9261         p = strchr(message, '{');
9262         if (p) {
9263             q = strchr(p, '}');
9264             if (q) {
9265                 *q = NULLCHAR;
9266                 r = p + 1;
9267             }
9268         }
9269
9270         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9271         return;
9272
9273     } else if (strncmp(message, "White resign", 12) == 0) {
9274         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9275         return;
9276     } else if (strncmp(message, "Black resign", 12) == 0) {
9277         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9278         return;
9279     } else if (strncmp(message, "White matches", 13) == 0 ||
9280                strncmp(message, "Black matches", 13) == 0   ) {
9281         /* [HGM] ignore GNUShogi noises */
9282         return;
9283     } else if (strncmp(message, "White", 5) == 0 &&
9284                message[5] != '(' &&
9285                StrStr(message, "Black") == NULL) {
9286         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9287         return;
9288     } else if (strncmp(message, "Black", 5) == 0 &&
9289                message[5] != '(') {
9290         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9291         return;
9292     } else if (strcmp(message, "resign") == 0 ||
9293                strcmp(message, "computer resigns") == 0) {
9294         switch (gameMode) {
9295           case MachinePlaysBlack:
9296           case IcsPlayingBlack:
9297             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9298             break;
9299           case MachinePlaysWhite:
9300           case IcsPlayingWhite:
9301             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9302             break;
9303           case TwoMachinesPlay:
9304             if (cps->twoMachinesColor[0] == 'w')
9305               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9306             else
9307               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9308             break;
9309           default:
9310             /* can't happen */
9311             break;
9312         }
9313         return;
9314     } else if (strncmp(message, "opponent mates", 14) == 0) {
9315         switch (gameMode) {
9316           case MachinePlaysBlack:
9317           case IcsPlayingBlack:
9318             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9319             break;
9320           case MachinePlaysWhite:
9321           case IcsPlayingWhite:
9322             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9323             break;
9324           case TwoMachinesPlay:
9325             if (cps->twoMachinesColor[0] == 'w')
9326               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9327             else
9328               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9329             break;
9330           default:
9331             /* can't happen */
9332             break;
9333         }
9334         return;
9335     } else if (strncmp(message, "computer mates", 14) == 0) {
9336         switch (gameMode) {
9337           case MachinePlaysBlack:
9338           case IcsPlayingBlack:
9339             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9340             break;
9341           case MachinePlaysWhite:
9342           case IcsPlayingWhite:
9343             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9344             break;
9345           case TwoMachinesPlay:
9346             if (cps->twoMachinesColor[0] == 'w')
9347               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9348             else
9349               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9350             break;
9351           default:
9352             /* can't happen */
9353             break;
9354         }
9355         return;
9356     } else if (strncmp(message, "checkmate", 9) == 0) {
9357         if (WhiteOnMove(forwardMostMove)) {
9358             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9359         } else {
9360             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9361         }
9362         return;
9363     } else if (strstr(message, "Draw") != NULL ||
9364                strstr(message, "game is a draw") != NULL) {
9365         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9366         return;
9367     } else if (strstr(message, "offer") != NULL &&
9368                strstr(message, "draw") != NULL) {
9369 #if ZIPPY
9370         if (appData.zippyPlay && first.initDone) {
9371             /* Relay offer to ICS */
9372             SendToICS(ics_prefix);
9373             SendToICS("draw\n");
9374         }
9375 #endif
9376         cps->offeredDraw = 2; /* valid until this engine moves twice */
9377         if (gameMode == TwoMachinesPlay) {
9378             if (cps->other->offeredDraw) {
9379                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9380             /* [HGM] in two-machine mode we delay relaying draw offer      */
9381             /* until after we also have move, to see if it is really claim */
9382             }
9383         } else if (gameMode == MachinePlaysWhite ||
9384                    gameMode == MachinePlaysBlack) {
9385           if (userOfferedDraw) {
9386             DisplayInformation(_("Machine accepts your draw offer"));
9387             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9388           } else {
9389             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9390           }
9391         }
9392     }
9393
9394
9395     /*
9396      * Look for thinking output
9397      */
9398     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9399           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9400                                 ) {
9401         int plylev, mvleft, mvtot, curscore, time;
9402         char mvname[MOVE_LEN];
9403         u64 nodes; // [DM]
9404         char plyext;
9405         int ignore = FALSE;
9406         int prefixHint = FALSE;
9407         mvname[0] = NULLCHAR;
9408
9409         switch (gameMode) {
9410           case MachinePlaysBlack:
9411           case IcsPlayingBlack:
9412             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9413             break;
9414           case MachinePlaysWhite:
9415           case IcsPlayingWhite:
9416             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9417             break;
9418           case AnalyzeMode:
9419           case AnalyzeFile:
9420             break;
9421           case IcsObserving: /* [DM] icsEngineAnalyze */
9422             if (!appData.icsEngineAnalyze) ignore = TRUE;
9423             break;
9424           case TwoMachinesPlay:
9425             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9426                 ignore = TRUE;
9427             }
9428             break;
9429           default:
9430             ignore = TRUE;
9431             break;
9432         }
9433
9434         if (!ignore) {
9435             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9436             buf1[0] = NULLCHAR;
9437             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9438                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9439
9440                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9441                     nodes += u64Const(0x100000000);
9442
9443                 if (plyext != ' ' && plyext != '\t') {
9444                     time *= 100;
9445                 }
9446
9447                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9448                 if( cps->scoreIsAbsolute &&
9449                     ( gameMode == MachinePlaysBlack ||
9450                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9451                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9452                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9453                      !WhiteOnMove(currentMove)
9454                     ) )
9455                 {
9456                     curscore = -curscore;
9457                 }
9458
9459                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9460
9461                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9462                         char buf[MSG_SIZ];
9463                         FILE *f;
9464                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9465                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9466                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9467                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9468                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9469                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9470                                 fclose(f);
9471                         }
9472                         else
9473                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9474                           DisplayError(_("failed writing PV"), 0);
9475                 }
9476
9477                 tempStats.depth = plylev;
9478                 tempStats.nodes = nodes;
9479                 tempStats.time = time;
9480                 tempStats.score = curscore;
9481                 tempStats.got_only_move = 0;
9482
9483                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9484                         int ticklen;
9485
9486                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9487                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9488                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9489                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9490                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9491                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9492                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9493                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9494                 }
9495
9496                 /* Buffer overflow protection */
9497                 if (pv[0] != NULLCHAR) {
9498                     if (strlen(pv) >= sizeof(tempStats.movelist)
9499                         && appData.debugMode) {
9500                         fprintf(debugFP,
9501                                 "PV is too long; using the first %u bytes.\n",
9502                                 (unsigned) sizeof(tempStats.movelist) - 1);
9503                     }
9504
9505                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9506                 } else {
9507                     sprintf(tempStats.movelist, " no PV\n");
9508                 }
9509
9510                 if (tempStats.seen_stat) {
9511                     tempStats.ok_to_send = 1;
9512                 }
9513
9514                 if (strchr(tempStats.movelist, '(') != NULL) {
9515                     tempStats.line_is_book = 1;
9516                     tempStats.nr_moves = 0;
9517                     tempStats.moves_left = 0;
9518                 } else {
9519                     tempStats.line_is_book = 0;
9520                 }
9521
9522                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9523                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9524
9525                 SendProgramStatsToFrontend( cps, &tempStats );
9526
9527                 /*
9528                     [AS] Protect the thinkOutput buffer from overflow... this
9529                     is only useful if buf1 hasn't overflowed first!
9530                 */
9531                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9532                          plylev,
9533                          (gameMode == TwoMachinesPlay ?
9534                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9535                          ((double) curscore) / 100.0,
9536                          prefixHint ? lastHint : "",
9537                          prefixHint ? " " : "" );
9538
9539                 if( buf1[0] != NULLCHAR ) {
9540                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9541
9542                     if( strlen(pv) > max_len ) {
9543                         if( appData.debugMode) {
9544                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9545                         }
9546                         pv[max_len+1] = '\0';
9547                     }
9548
9549                     strcat( thinkOutput, pv);
9550                 }
9551
9552                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9553                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9554                     DisplayMove(currentMove - 1);
9555                 }
9556                 return;
9557
9558             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9559                 /* crafty (9.25+) says "(only move) <move>"
9560                  * if there is only 1 legal move
9561                  */
9562                 sscanf(p, "(only move) %s", buf1);
9563                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9564                 sprintf(programStats.movelist, "%s (only move)", buf1);
9565                 programStats.depth = 1;
9566                 programStats.nr_moves = 1;
9567                 programStats.moves_left = 1;
9568                 programStats.nodes = 1;
9569                 programStats.time = 1;
9570                 programStats.got_only_move = 1;
9571
9572                 /* Not really, but we also use this member to
9573                    mean "line isn't going to change" (Crafty
9574                    isn't searching, so stats won't change) */
9575                 programStats.line_is_book = 1;
9576
9577                 SendProgramStatsToFrontend( cps, &programStats );
9578
9579                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9580                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9581                     DisplayMove(currentMove - 1);
9582                 }
9583                 return;
9584             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9585                               &time, &nodes, &plylev, &mvleft,
9586                               &mvtot, mvname) >= 5) {
9587                 /* The stat01: line is from Crafty (9.29+) in response
9588                    to the "." command */
9589                 programStats.seen_stat = 1;
9590                 cps->maybeThinking = TRUE;
9591
9592                 if (programStats.got_only_move || !appData.periodicUpdates)
9593                   return;
9594
9595                 programStats.depth = plylev;
9596                 programStats.time = time;
9597                 programStats.nodes = nodes;
9598                 programStats.moves_left = mvleft;
9599                 programStats.nr_moves = mvtot;
9600                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9601                 programStats.ok_to_send = 1;
9602                 programStats.movelist[0] = '\0';
9603
9604                 SendProgramStatsToFrontend( cps, &programStats );
9605
9606                 return;
9607
9608             } else if (strncmp(message,"++",2) == 0) {
9609                 /* Crafty 9.29+ outputs this */
9610                 programStats.got_fail = 2;
9611                 return;
9612
9613             } else if (strncmp(message,"--",2) == 0) {
9614                 /* Crafty 9.29+ outputs this */
9615                 programStats.got_fail = 1;
9616                 return;
9617
9618             } else if (thinkOutput[0] != NULLCHAR &&
9619                        strncmp(message, "    ", 4) == 0) {
9620                 unsigned message_len;
9621
9622                 p = message;
9623                 while (*p && *p == ' ') p++;
9624
9625                 message_len = strlen( p );
9626
9627                 /* [AS] Avoid buffer overflow */
9628                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9629                     strcat(thinkOutput, " ");
9630                     strcat(thinkOutput, p);
9631                 }
9632
9633                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9634                     strcat(programStats.movelist, " ");
9635                     strcat(programStats.movelist, p);
9636                 }
9637
9638                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9639                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9640                     DisplayMove(currentMove - 1);
9641                 }
9642                 return;
9643             }
9644         }
9645         else {
9646             buf1[0] = NULLCHAR;
9647
9648             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9649                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9650             {
9651                 ChessProgramStats cpstats;
9652
9653                 if (plyext != ' ' && plyext != '\t') {
9654                     time *= 100;
9655                 }
9656
9657                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9658                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9659                     curscore = -curscore;
9660                 }
9661
9662                 cpstats.depth = plylev;
9663                 cpstats.nodes = nodes;
9664                 cpstats.time = time;
9665                 cpstats.score = curscore;
9666                 cpstats.got_only_move = 0;
9667                 cpstats.movelist[0] = '\0';
9668
9669                 if (buf1[0] != NULLCHAR) {
9670                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9671                 }
9672
9673                 cpstats.ok_to_send = 0;
9674                 cpstats.line_is_book = 0;
9675                 cpstats.nr_moves = 0;
9676                 cpstats.moves_left = 0;
9677
9678                 SendProgramStatsToFrontend( cps, &cpstats );
9679             }
9680         }
9681     }
9682 }
9683
9684
9685 /* Parse a game score from the character string "game", and
9686    record it as the history of the current game.  The game
9687    score is NOT assumed to start from the standard position.
9688    The display is not updated in any way.
9689    */
9690 void
9691 ParseGameHistory (char *game)
9692 {
9693     ChessMove moveType;
9694     int fromX, fromY, toX, toY, boardIndex;
9695     char promoChar;
9696     char *p, *q;
9697     char buf[MSG_SIZ];
9698
9699     if (appData.debugMode)
9700       fprintf(debugFP, "Parsing game history: %s\n", game);
9701
9702     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9703     gameInfo.site = StrSave(appData.icsHost);
9704     gameInfo.date = PGNDate();
9705     gameInfo.round = StrSave("-");
9706
9707     /* Parse out names of players */
9708     while (*game == ' ') game++;
9709     p = buf;
9710     while (*game != ' ') *p++ = *game++;
9711     *p = NULLCHAR;
9712     gameInfo.white = StrSave(buf);
9713     while (*game == ' ') game++;
9714     p = buf;
9715     while (*game != ' ' && *game != '\n') *p++ = *game++;
9716     *p = NULLCHAR;
9717     gameInfo.black = StrSave(buf);
9718
9719     /* Parse moves */
9720     boardIndex = blackPlaysFirst ? 1 : 0;
9721     yynewstr(game);
9722     for (;;) {
9723         yyboardindex = boardIndex;
9724         moveType = (ChessMove) Myylex();
9725         switch (moveType) {
9726           case IllegalMove:             /* maybe suicide chess, etc. */
9727   if (appData.debugMode) {
9728     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9729     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9730     setbuf(debugFP, NULL);
9731   }
9732           case WhitePromotion:
9733           case BlackPromotion:
9734           case WhiteNonPromotion:
9735           case BlackNonPromotion:
9736           case NormalMove:
9737           case FirstLeg:
9738           case WhiteCapturesEnPassant:
9739           case BlackCapturesEnPassant:
9740           case WhiteKingSideCastle:
9741           case WhiteQueenSideCastle:
9742           case BlackKingSideCastle:
9743           case BlackQueenSideCastle:
9744           case WhiteKingSideCastleWild:
9745           case WhiteQueenSideCastleWild:
9746           case BlackKingSideCastleWild:
9747           case BlackQueenSideCastleWild:
9748           /* PUSH Fabien */
9749           case WhiteHSideCastleFR:
9750           case WhiteASideCastleFR:
9751           case BlackHSideCastleFR:
9752           case BlackASideCastleFR:
9753           /* POP Fabien */
9754             fromX = currentMoveString[0] - AAA;
9755             fromY = currentMoveString[1] - ONE;
9756             toX = currentMoveString[2] - AAA;
9757             toY = currentMoveString[3] - ONE;
9758             promoChar = currentMoveString[4];
9759             break;
9760           case WhiteDrop:
9761           case BlackDrop:
9762             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9763             fromX = moveType == WhiteDrop ?
9764               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9765             (int) CharToPiece(ToLower(currentMoveString[0]));
9766             fromY = DROP_RANK;
9767             toX = currentMoveString[2] - AAA;
9768             toY = currentMoveString[3] - ONE;
9769             promoChar = NULLCHAR;
9770             break;
9771           case AmbiguousMove:
9772             /* bug? */
9773             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9774   if (appData.debugMode) {
9775     fprintf(debugFP, "Ambiguous 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 ImpossibleMove:
9782             /* bug? */
9783             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9784   if (appData.debugMode) {
9785     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9786     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9787     setbuf(debugFP, NULL);
9788   }
9789             DisplayError(buf, 0);
9790             return;
9791           case EndOfFile:
9792             if (boardIndex < backwardMostMove) {
9793                 /* Oops, gap.  How did that happen? */
9794                 DisplayError(_("Gap in move list"), 0);
9795                 return;
9796             }
9797             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9798             if (boardIndex > forwardMostMove) {
9799                 forwardMostMove = boardIndex;
9800             }
9801             return;
9802           case ElapsedTime:
9803             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9804                 strcat(parseList[boardIndex-1], " ");
9805                 strcat(parseList[boardIndex-1], yy_text);
9806             }
9807             continue;
9808           case Comment:
9809           case PGNTag:
9810           case NAG:
9811           default:
9812             /* ignore */
9813             continue;
9814           case WhiteWins:
9815           case BlackWins:
9816           case GameIsDrawn:
9817           case GameUnfinished:
9818             if (gameMode == IcsExamining) {
9819                 if (boardIndex < backwardMostMove) {
9820                     /* Oops, gap.  How did that happen? */
9821                     return;
9822                 }
9823                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9824                 return;
9825             }
9826             gameInfo.result = moveType;
9827             p = strchr(yy_text, '{');
9828             if (p == NULL) p = strchr(yy_text, '(');
9829             if (p == NULL) {
9830                 p = yy_text;
9831                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9832             } else {
9833                 q = strchr(p, *p == '{' ? '}' : ')');
9834                 if (q != NULL) *q = NULLCHAR;
9835                 p++;
9836             }
9837             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9838             gameInfo.resultDetails = StrSave(p);
9839             continue;
9840         }
9841         if (boardIndex >= forwardMostMove &&
9842             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9843             backwardMostMove = blackPlaysFirst ? 1 : 0;
9844             return;
9845         }
9846         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9847                                  fromY, fromX, toY, toX, promoChar,
9848                                  parseList[boardIndex]);
9849         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9850         /* currentMoveString is set as a side-effect of yylex */
9851         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9852         strcat(moveList[boardIndex], "\n");
9853         boardIndex++;
9854         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9855         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9856           case MT_NONE:
9857           case MT_STALEMATE:
9858           default:
9859             break;
9860           case MT_CHECK:
9861             if(!IS_SHOGI(gameInfo.variant))
9862                 strcat(parseList[boardIndex - 1], "+");
9863             break;
9864           case MT_CHECKMATE:
9865           case MT_STAINMATE:
9866             strcat(parseList[boardIndex - 1], "#");
9867             break;
9868         }
9869     }
9870 }
9871
9872
9873 /* Apply a move to the given board  */
9874 void
9875 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9876 {
9877   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9878   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9879
9880     /* [HGM] compute & store e.p. status and castling rights for new position */
9881     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9882
9883       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9884       oldEP = (signed char)board[EP_STATUS];
9885       board[EP_STATUS] = EP_NONE;
9886       board[EP_FILE] = board[EP_RANK] = 100;
9887
9888   if (fromY == DROP_RANK) {
9889         /* must be first */
9890         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9891             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9892             return;
9893         }
9894         piece = board[toY][toX] = (ChessSquare) fromX;
9895   } else {
9896 //      ChessSquare victim;
9897       int i;
9898
9899       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9900 //           victim = board[killY][killX],
9901            board[killY][killX] = EmptySquare,
9902            board[EP_STATUS] = EP_CAPTURE;
9903
9904       if( board[toY][toX] != EmptySquare ) {
9905            board[EP_STATUS] = EP_CAPTURE;
9906            if( (fromX != toX || fromY != toY) && // not igui!
9907                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9908                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9909                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9910            }
9911       }
9912
9913       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9914            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9915                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9916       } else
9917       if( board[fromY][fromX] == WhitePawn ) {
9918            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9919                board[EP_STATUS] = EP_PAWN_MOVE;
9920            if( toY-fromY==2) {
9921                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9922                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9923                         gameInfo.variant != VariantBerolina || toX < fromX)
9924                       board[EP_STATUS] = toX | berolina;
9925                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9926                         gameInfo.variant != VariantBerolina || toX > fromX)
9927                       board[EP_STATUS] = toX;
9928            }
9929       } else
9930       if( board[fromY][fromX] == BlackPawn ) {
9931            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9932                board[EP_STATUS] = EP_PAWN_MOVE;
9933            if( toY-fromY== -2) {
9934                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
9935                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9936                         gameInfo.variant != VariantBerolina || toX < fromX)
9937                       board[EP_STATUS] = toX | berolina;
9938                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9939                         gameInfo.variant != VariantBerolina || toX > fromX)
9940                       board[EP_STATUS] = toX;
9941            }
9942        }
9943
9944        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9945        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9946        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9947        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9948
9949        for(i=0; i<nrCastlingRights; i++) {
9950            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9951               board[CASTLING][i] == toX   && castlingRank[i] == toY
9952              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9953        }
9954
9955        if(gameInfo.variant == VariantSChess) { // update virginity
9956            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9957            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9958            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9959            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9960        }
9961
9962      if (fromX == toX && fromY == toY) return;
9963
9964      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9965      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9966      if(gameInfo.variant == VariantKnightmate)
9967          king += (int) WhiteUnicorn - (int) WhiteKing;
9968
9969     /* Code added by Tord: */
9970     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9971     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9972         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9973       board[fromY][fromX] = EmptySquare;
9974       board[toY][toX] = EmptySquare;
9975       if((toX > fromX) != (piece == WhiteRook)) {
9976         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9977       } else {
9978         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9979       }
9980     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9981                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9982       board[fromY][fromX] = EmptySquare;
9983       board[toY][toX] = EmptySquare;
9984       if((toX > fromX) != (piece == BlackRook)) {
9985         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9986       } else {
9987         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9988       }
9989     /* End of code added by Tord */
9990
9991     } else if (board[fromY][fromX] == king
9992         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9993         && toY == fromY && toX > fromX+1) {
9994         board[fromY][fromX] = EmptySquare;
9995         board[toY][toX] = king;
9996         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9997         board[fromY][BOARD_RGHT-1] = EmptySquare;
9998     } else if (board[fromY][fromX] == king
9999         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10000                && toY == fromY && toX < fromX-1) {
10001         board[fromY][fromX] = EmptySquare;
10002         board[toY][toX] = king;
10003         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10004         board[fromY][BOARD_LEFT] = EmptySquare;
10005     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10006                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10007                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10008                ) {
10009         /* white pawn promotion */
10010         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10011         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10012             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10013         board[fromY][fromX] = EmptySquare;
10014     } else if ((fromY >= BOARD_HEIGHT>>1)
10015                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10016                && (toX != fromX)
10017                && gameInfo.variant != VariantXiangqi
10018                && gameInfo.variant != VariantBerolina
10019                && (board[fromY][fromX] == WhitePawn)
10020                && (board[toY][toX] == EmptySquare)) {
10021         board[fromY][fromX] = EmptySquare;
10022         board[toY][toX] = WhitePawn;
10023         captured = board[toY - 1][toX];
10024         board[toY - 1][toX] = EmptySquare;
10025     } else if ((fromY == BOARD_HEIGHT-4)
10026                && (toX == fromX)
10027                && gameInfo.variant == VariantBerolina
10028                && (board[fromY][fromX] == WhitePawn)
10029                && (board[toY][toX] == EmptySquare)) {
10030         board[fromY][fromX] = EmptySquare;
10031         board[toY][toX] = WhitePawn;
10032         if(oldEP & EP_BEROLIN_A) {
10033                 captured = board[fromY][fromX-1];
10034                 board[fromY][fromX-1] = EmptySquare;
10035         }else{  captured = board[fromY][fromX+1];
10036                 board[fromY][fromX+1] = EmptySquare;
10037         }
10038     } else if (board[fromY][fromX] == king
10039         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10040                && toY == fromY && toX > fromX+1) {
10041         board[fromY][fromX] = EmptySquare;
10042         board[toY][toX] = king;
10043         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10044         board[fromY][BOARD_RGHT-1] = EmptySquare;
10045     } else if (board[fromY][fromX] == king
10046         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10047                && toY == fromY && toX < fromX-1) {
10048         board[fromY][fromX] = EmptySquare;
10049         board[toY][toX] = king;
10050         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10051         board[fromY][BOARD_LEFT] = EmptySquare;
10052     } else if (fromY == 7 && fromX == 3
10053                && board[fromY][fromX] == BlackKing
10054                && toY == 7 && toX == 5) {
10055         board[fromY][fromX] = EmptySquare;
10056         board[toY][toX] = BlackKing;
10057         board[fromY][7] = EmptySquare;
10058         board[toY][4] = BlackRook;
10059     } else if (fromY == 7 && fromX == 3
10060                && board[fromY][fromX] == BlackKing
10061                && toY == 7 && toX == 1) {
10062         board[fromY][fromX] = EmptySquare;
10063         board[toY][toX] = BlackKing;
10064         board[fromY][0] = EmptySquare;
10065         board[toY][2] = BlackRook;
10066     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10067                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10068                && toY < promoRank && promoChar
10069                ) {
10070         /* black pawn promotion */
10071         board[toY][toX] = CharToPiece(ToLower(promoChar));
10072         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10073             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10074         board[fromY][fromX] = EmptySquare;
10075     } else if ((fromY < BOARD_HEIGHT>>1)
10076                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10077                && (toX != fromX)
10078                && gameInfo.variant != VariantXiangqi
10079                && gameInfo.variant != VariantBerolina
10080                && (board[fromY][fromX] == BlackPawn)
10081                && (board[toY][toX] == EmptySquare)) {
10082         board[fromY][fromX] = EmptySquare;
10083         board[toY][toX] = BlackPawn;
10084         captured = board[toY + 1][toX];
10085         board[toY + 1][toX] = EmptySquare;
10086     } else if ((fromY == 3)
10087                && (toX == fromX)
10088                && gameInfo.variant == VariantBerolina
10089                && (board[fromY][fromX] == BlackPawn)
10090                && (board[toY][toX] == EmptySquare)) {
10091         board[fromY][fromX] = EmptySquare;
10092         board[toY][toX] = BlackPawn;
10093         if(oldEP & EP_BEROLIN_A) {
10094                 captured = board[fromY][fromX-1];
10095                 board[fromY][fromX-1] = EmptySquare;
10096         }else{  captured = board[fromY][fromX+1];
10097                 board[fromY][fromX+1] = EmptySquare;
10098         }
10099     } else {
10100         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10101         board[fromY][fromX] = EmptySquare;
10102         board[toY][toX] = piece;
10103     }
10104   }
10105
10106     if (gameInfo.holdingsWidth != 0) {
10107
10108       /* !!A lot more code needs to be written to support holdings  */
10109       /* [HGM] OK, so I have written it. Holdings are stored in the */
10110       /* penultimate board files, so they are automaticlly stored   */
10111       /* in the game history.                                       */
10112       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10113                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10114         /* Delete from holdings, by decreasing count */
10115         /* and erasing image if necessary            */
10116         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10117         if(p < (int) BlackPawn) { /* white drop */
10118              p -= (int)WhitePawn;
10119                  p = PieceToNumber((ChessSquare)p);
10120              if(p >= gameInfo.holdingsSize) p = 0;
10121              if(--board[p][BOARD_WIDTH-2] <= 0)
10122                   board[p][BOARD_WIDTH-1] = EmptySquare;
10123              if((int)board[p][BOARD_WIDTH-2] < 0)
10124                         board[p][BOARD_WIDTH-2] = 0;
10125         } else {                  /* black drop */
10126              p -= (int)BlackPawn;
10127                  p = PieceToNumber((ChessSquare)p);
10128              if(p >= gameInfo.holdingsSize) p = 0;
10129              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10130                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10131              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10132                         board[BOARD_HEIGHT-1-p][1] = 0;
10133         }
10134       }
10135       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10136           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10137         /* [HGM] holdings: Add to holdings, if holdings exist */
10138         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10139                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10140                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10141         }
10142         p = (int) captured;
10143         if (p >= (int) BlackPawn) {
10144           p -= (int)BlackPawn;
10145           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10146                   /* in Shogi restore piece to its original  first */
10147                   captured = (ChessSquare) (DEMOTED captured);
10148                   p = DEMOTED p;
10149           }
10150           p = PieceToNumber((ChessSquare)p);
10151           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10152           board[p][BOARD_WIDTH-2]++;
10153           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10154         } else {
10155           p -= (int)WhitePawn;
10156           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10157                   captured = (ChessSquare) (DEMOTED captured);
10158                   p = DEMOTED p;
10159           }
10160           p = PieceToNumber((ChessSquare)p);
10161           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10162           board[BOARD_HEIGHT-1-p][1]++;
10163           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10164         }
10165       }
10166     } else if (gameInfo.variant == VariantAtomic) {
10167       if (captured != EmptySquare) {
10168         int y, x;
10169         for (y = toY-1; y <= toY+1; y++) {
10170           for (x = toX-1; x <= toX+1; x++) {
10171             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10172                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10173               board[y][x] = EmptySquare;
10174             }
10175           }
10176         }
10177         board[toY][toX] = EmptySquare;
10178       }
10179     }
10180
10181     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10182         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10183     } else
10184     if(promoChar == '+') {
10185         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10186         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10187         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10188           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10189     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10190         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10191         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10192            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10193         board[toY][toX] = newPiece;
10194     }
10195     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10196                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10197         // [HGM] superchess: take promotion piece out of holdings
10198         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10199         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10200             if(!--board[k][BOARD_WIDTH-2])
10201                 board[k][BOARD_WIDTH-1] = EmptySquare;
10202         } else {
10203             if(!--board[BOARD_HEIGHT-1-k][1])
10204                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10205         }
10206     }
10207 }
10208
10209 /* Updates forwardMostMove */
10210 void
10211 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10212 {
10213     int x = toX, y = toY;
10214     char *s = parseList[forwardMostMove];
10215     ChessSquare p = boards[forwardMostMove][toY][toX];
10216 //    forwardMostMove++; // [HGM] bare: moved downstream
10217
10218     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10219     (void) CoordsToAlgebraic(boards[forwardMostMove],
10220                              PosFlags(forwardMostMove),
10221                              fromY, fromX, y, x, promoChar,
10222                              s);
10223     if(killX >= 0 && killY >= 0)
10224         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10225
10226     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10227         int timeLeft; static int lastLoadFlag=0; int king, piece;
10228         piece = boards[forwardMostMove][fromY][fromX];
10229         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10230         if(gameInfo.variant == VariantKnightmate)
10231             king += (int) WhiteUnicorn - (int) WhiteKing;
10232         if(forwardMostMove == 0) {
10233             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10234                 fprintf(serverMoves, "%s;", UserName());
10235             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10236                 fprintf(serverMoves, "%s;", second.tidy);
10237             fprintf(serverMoves, "%s;", first.tidy);
10238             if(gameMode == MachinePlaysWhite)
10239                 fprintf(serverMoves, "%s;", UserName());
10240             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10241                 fprintf(serverMoves, "%s;", second.tidy);
10242         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10243         lastLoadFlag = loadFlag;
10244         // print base move
10245         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10246         // print castling suffix
10247         if( toY == fromY && piece == king ) {
10248             if(toX-fromX > 1)
10249                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10250             if(fromX-toX >1)
10251                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10252         }
10253         // e.p. suffix
10254         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10255              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10256              boards[forwardMostMove][toY][toX] == EmptySquare
10257              && fromX != toX && fromY != toY)
10258                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10259         // promotion suffix
10260         if(promoChar != NULLCHAR) {
10261             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10262                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10263                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10264             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10265         }
10266         if(!loadFlag) {
10267                 char buf[MOVE_LEN*2], *p; int len;
10268             fprintf(serverMoves, "/%d/%d",
10269                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10270             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10271             else                      timeLeft = blackTimeRemaining/1000;
10272             fprintf(serverMoves, "/%d", timeLeft);
10273                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10274                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10275                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10276                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10277             fprintf(serverMoves, "/%s", buf);
10278         }
10279         fflush(serverMoves);
10280     }
10281
10282     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10283         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10284       return;
10285     }
10286     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10287     if (commentList[forwardMostMove+1] != NULL) {
10288         free(commentList[forwardMostMove+1]);
10289         commentList[forwardMostMove+1] = NULL;
10290     }
10291     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10292     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10293     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10294     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10295     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10296     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10297     adjustedClock = FALSE;
10298     gameInfo.result = GameUnfinished;
10299     if (gameInfo.resultDetails != NULL) {
10300         free(gameInfo.resultDetails);
10301         gameInfo.resultDetails = NULL;
10302     }
10303     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10304                               moveList[forwardMostMove - 1]);
10305     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10306       case MT_NONE:
10307       case MT_STALEMATE:
10308       default:
10309         break;
10310       case MT_CHECK:
10311         if(!IS_SHOGI(gameInfo.variant))
10312             strcat(parseList[forwardMostMove - 1], "+");
10313         break;
10314       case MT_CHECKMATE:
10315       case MT_STAINMATE:
10316         strcat(parseList[forwardMostMove - 1], "#");
10317         break;
10318     }
10319 }
10320
10321 /* Updates currentMove if not pausing */
10322 void
10323 ShowMove (int fromX, int fromY, int toX, int toY)
10324 {
10325     int instant = (gameMode == PlayFromGameFile) ?
10326         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10327     if(appData.noGUI) return;
10328     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10329         if (!instant) {
10330             if (forwardMostMove == currentMove + 1) {
10331                 AnimateMove(boards[forwardMostMove - 1],
10332                             fromX, fromY, toX, toY);
10333             }
10334         }
10335         currentMove = forwardMostMove;
10336     }
10337
10338     killX = killY = -1; // [HGM] lion: used up
10339
10340     if (instant) return;
10341
10342     DisplayMove(currentMove - 1);
10343     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10344             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10345                 SetHighlights(fromX, fromY, toX, toY);
10346             }
10347     }
10348     DrawPosition(FALSE, boards[currentMove]);
10349     DisplayBothClocks();
10350     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10351 }
10352
10353 void
10354 SendEgtPath (ChessProgramState *cps)
10355 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10356         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10357
10358         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10359
10360         while(*p) {
10361             char c, *q = name+1, *r, *s;
10362
10363             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10364             while(*p && *p != ',') *q++ = *p++;
10365             *q++ = ':'; *q = 0;
10366             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10367                 strcmp(name, ",nalimov:") == 0 ) {
10368                 // take nalimov path from the menu-changeable option first, if it is defined
10369               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10370                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10371             } else
10372             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10373                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10374                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10375                 s = r = StrStr(s, ":") + 1; // beginning of path info
10376                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10377                 c = *r; *r = 0;             // temporarily null-terminate path info
10378                     *--q = 0;               // strip of trailig ':' from name
10379                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10380                 *r = c;
10381                 SendToProgram(buf,cps);     // send egtbpath command for this format
10382             }
10383             if(*p == ',') p++; // read away comma to position for next format name
10384         }
10385 }
10386
10387 static int
10388 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10389 {
10390       int width = 8, height = 8, holdings = 0;             // most common sizes
10391       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10392       // correct the deviations default for each variant
10393       if( v == VariantXiangqi ) width = 9,  height = 10;
10394       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10395       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10396       if( v == VariantCapablanca || v == VariantCapaRandom ||
10397           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10398                                 width = 10;
10399       if( v == VariantCourier ) width = 12;
10400       if( v == VariantSuper )                            holdings = 8;
10401       if( v == VariantGreat )   width = 10,              holdings = 8;
10402       if( v == VariantSChess )                           holdings = 7;
10403       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10404       if( v == VariantChuChess) width = 10, height = 10;
10405       if( v == VariantChu )     width = 12, height = 12;
10406       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10407              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10408              holdingsSize >= 0 && holdingsSize != holdings;
10409 }
10410
10411 char variantError[MSG_SIZ];
10412
10413 char *
10414 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10415 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10416       char *p, *variant = VariantName(v);
10417       static char b[MSG_SIZ];
10418       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10419            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10420                                                holdingsSize, variant); // cook up sized variant name
10421            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10422            if(StrStr(list, b) == NULL) {
10423                // specific sized variant not known, check if general sizing allowed
10424                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10425                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10426                             boardWidth, boardHeight, holdingsSize, engine);
10427                    return NULL;
10428                }
10429                /* [HGM] here we really should compare with the maximum supported board size */
10430            }
10431       } else snprintf(b, MSG_SIZ,"%s", variant);
10432       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10433       p = StrStr(list, b);
10434       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10435       if(p == NULL) {
10436           // occurs not at all in list, or only as sub-string
10437           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10438           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10439               int l = strlen(variantError);
10440               char *q;
10441               while(p != list && p[-1] != ',') p--;
10442               q = strchr(p, ',');
10443               if(q) *q = NULLCHAR;
10444               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10445               if(q) *q= ',';
10446           }
10447           return NULL;
10448       }
10449       return b;
10450 }
10451
10452 void
10453 InitChessProgram (ChessProgramState *cps, int setup)
10454 /* setup needed to setup FRC opening position */
10455 {
10456     char buf[MSG_SIZ], *b;
10457     if (appData.noChessProgram) return;
10458     hintRequested = FALSE;
10459     bookRequested = FALSE;
10460
10461     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10462     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10463     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10464     if(cps->memSize) { /* [HGM] memory */
10465       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10466         SendToProgram(buf, cps);
10467     }
10468     SendEgtPath(cps); /* [HGM] EGT */
10469     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10470       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10471         SendToProgram(buf, cps);
10472     }
10473
10474     setboardSpoiledMachineBlack = FALSE;
10475     SendToProgram(cps->initString, cps);
10476     if (gameInfo.variant != VariantNormal &&
10477         gameInfo.variant != VariantLoadable
10478         /* [HGM] also send variant if board size non-standard */
10479         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10480
10481       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10482                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10483       if (b == NULL) {
10484         DisplayFatalError(variantError, 0, 1);
10485         return;
10486       }
10487
10488       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10489       SendToProgram(buf, cps);
10490     }
10491     currentlyInitializedVariant = gameInfo.variant;
10492
10493     /* [HGM] send opening position in FRC to first engine */
10494     if(setup) {
10495           SendToProgram("force\n", cps);
10496           SendBoard(cps, 0);
10497           /* engine is now in force mode! Set flag to wake it up after first move. */
10498           setboardSpoiledMachineBlack = 1;
10499     }
10500
10501     if (cps->sendICS) {
10502       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10503       SendToProgram(buf, cps);
10504     }
10505     cps->maybeThinking = FALSE;
10506     cps->offeredDraw = 0;
10507     if (!appData.icsActive) {
10508         SendTimeControl(cps, movesPerSession, timeControl,
10509                         timeIncrement, appData.searchDepth,
10510                         searchTime);
10511     }
10512     if (appData.showThinking
10513         // [HGM] thinking: four options require thinking output to be sent
10514         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10515                                 ) {
10516         SendToProgram("post\n", cps);
10517     }
10518     SendToProgram("hard\n", cps);
10519     if (!appData.ponderNextMove) {
10520         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10521            it without being sure what state we are in first.  "hard"
10522            is not a toggle, so that one is OK.
10523          */
10524         SendToProgram("easy\n", cps);
10525     }
10526     if (cps->usePing) {
10527       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10528       SendToProgram(buf, cps);
10529     }
10530     cps->initDone = TRUE;
10531     ClearEngineOutputPane(cps == &second);
10532 }
10533
10534
10535 void
10536 ResendOptions (ChessProgramState *cps)
10537 { // send the stored value of the options
10538   int i;
10539   char buf[MSG_SIZ];
10540   Option *opt = cps->option;
10541   for(i=0; i<cps->nrOptions; i++, opt++) {
10542       switch(opt->type) {
10543         case Spin:
10544         case Slider:
10545         case CheckBox:
10546             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10547           break;
10548         case ComboBox:
10549           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10550           break;
10551         default:
10552             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10553           break;
10554         case Button:
10555         case SaveButton:
10556           continue;
10557       }
10558       SendToProgram(buf, cps);
10559   }
10560 }
10561
10562 void
10563 StartChessProgram (ChessProgramState *cps)
10564 {
10565     char buf[MSG_SIZ];
10566     int err;
10567
10568     if (appData.noChessProgram) return;
10569     cps->initDone = FALSE;
10570
10571     if (strcmp(cps->host, "localhost") == 0) {
10572         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10573     } else if (*appData.remoteShell == NULLCHAR) {
10574         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10575     } else {
10576         if (*appData.remoteUser == NULLCHAR) {
10577           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10578                     cps->program);
10579         } else {
10580           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10581                     cps->host, appData.remoteUser, cps->program);
10582         }
10583         err = StartChildProcess(buf, "", &cps->pr);
10584     }
10585
10586     if (err != 0) {
10587       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10588         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10589         if(cps != &first) return;
10590         appData.noChessProgram = TRUE;
10591         ThawUI();
10592         SetNCPMode();
10593 //      DisplayFatalError(buf, err, 1);
10594 //      cps->pr = NoProc;
10595 //      cps->isr = NULL;
10596         return;
10597     }
10598
10599     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10600     if (cps->protocolVersion > 1) {
10601       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10602       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10603         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10604         cps->comboCnt = 0;  //                and values of combo boxes
10605       }
10606       SendToProgram(buf, cps);
10607       if(cps->reload) ResendOptions(cps);
10608     } else {
10609       SendToProgram("xboard\n", cps);
10610     }
10611 }
10612
10613 void
10614 TwoMachinesEventIfReady P((void))
10615 {
10616   static int curMess = 0;
10617   if (first.lastPing != first.lastPong) {
10618     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10619     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10620     return;
10621   }
10622   if (second.lastPing != second.lastPong) {
10623     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10624     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10625     return;
10626   }
10627   DisplayMessage("", ""); curMess = 0;
10628   TwoMachinesEvent();
10629 }
10630
10631 char *
10632 MakeName (char *template)
10633 {
10634     time_t clock;
10635     struct tm *tm;
10636     static char buf[MSG_SIZ];
10637     char *p = buf;
10638     int i;
10639
10640     clock = time((time_t *)NULL);
10641     tm = localtime(&clock);
10642
10643     while(*p++ = *template++) if(p[-1] == '%') {
10644         switch(*template++) {
10645           case 0:   *p = 0; return buf;
10646           case 'Y': i = tm->tm_year+1900; break;
10647           case 'y': i = tm->tm_year-100; break;
10648           case 'M': i = tm->tm_mon+1; break;
10649           case 'd': i = tm->tm_mday; break;
10650           case 'h': i = tm->tm_hour; break;
10651           case 'm': i = tm->tm_min; break;
10652           case 's': i = tm->tm_sec; break;
10653           default:  i = 0;
10654         }
10655         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10656     }
10657     return buf;
10658 }
10659
10660 int
10661 CountPlayers (char *p)
10662 {
10663     int n = 0;
10664     while(p = strchr(p, '\n')) p++, n++; // count participants
10665     return n;
10666 }
10667
10668 FILE *
10669 WriteTourneyFile (char *results, FILE *f)
10670 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10671     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10672     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10673         // create a file with tournament description
10674         fprintf(f, "-participants {%s}\n", appData.participants);
10675         fprintf(f, "-seedBase %d\n", appData.seedBase);
10676         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10677         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10678         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10679         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10680         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10681         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10682         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10683         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10684         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10685         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10686         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10687         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10688         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10689         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10690         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10691         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10692         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10693         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10694         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10695         fprintf(f, "-smpCores %d\n", appData.smpCores);
10696         if(searchTime > 0)
10697                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10698         else {
10699                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10700                 fprintf(f, "-tc %s\n", appData.timeControl);
10701                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10702         }
10703         fprintf(f, "-results \"%s\"\n", results);
10704     }
10705     return f;
10706 }
10707
10708 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10709
10710 void
10711 Substitute (char *participants, int expunge)
10712 {
10713     int i, changed, changes=0, nPlayers=0;
10714     char *p, *q, *r, buf[MSG_SIZ];
10715     if(participants == NULL) return;
10716     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10717     r = p = participants; q = appData.participants;
10718     while(*p && *p == *q) {
10719         if(*p == '\n') r = p+1, nPlayers++;
10720         p++; q++;
10721     }
10722     if(*p) { // difference
10723         while(*p && *p++ != '\n');
10724         while(*q && *q++ != '\n');
10725       changed = nPlayers;
10726         changes = 1 + (strcmp(p, q) != 0);
10727     }
10728     if(changes == 1) { // a single engine mnemonic was changed
10729         q = r; while(*q) nPlayers += (*q++ == '\n');
10730         p = buf; while(*r && (*p = *r++) != '\n') p++;
10731         *p = NULLCHAR;
10732         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10733         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10734         if(mnemonic[i]) { // The substitute is valid
10735             FILE *f;
10736             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10737                 flock(fileno(f), LOCK_EX);
10738                 ParseArgsFromFile(f);
10739                 fseek(f, 0, SEEK_SET);
10740                 FREE(appData.participants); appData.participants = participants;
10741                 if(expunge) { // erase results of replaced engine
10742                     int len = strlen(appData.results), w, b, dummy;
10743                     for(i=0; i<len; i++) {
10744                         Pairing(i, nPlayers, &w, &b, &dummy);
10745                         if((w == changed || b == changed) && appData.results[i] == '*') {
10746                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10747                             fclose(f);
10748                             return;
10749                         }
10750                     }
10751                     for(i=0; i<len; i++) {
10752                         Pairing(i, nPlayers, &w, &b, &dummy);
10753                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10754                     }
10755                 }
10756                 WriteTourneyFile(appData.results, f);
10757                 fclose(f); // release lock
10758                 return;
10759             }
10760         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10761     }
10762     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10763     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10764     free(participants);
10765     return;
10766 }
10767
10768 int
10769 CheckPlayers (char *participants)
10770 {
10771         int i;
10772         char buf[MSG_SIZ], *p;
10773         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10774         while(p = strchr(participants, '\n')) {
10775             *p = NULLCHAR;
10776             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10777             if(!mnemonic[i]) {
10778                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10779                 *p = '\n';
10780                 DisplayError(buf, 0);
10781                 return 1;
10782             }
10783             *p = '\n';
10784             participants = p + 1;
10785         }
10786         return 0;
10787 }
10788
10789 int
10790 CreateTourney (char *name)
10791 {
10792         FILE *f;
10793         if(matchMode && strcmp(name, appData.tourneyFile)) {
10794              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10795         }
10796         if(name[0] == NULLCHAR) {
10797             if(appData.participants[0])
10798                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10799             return 0;
10800         }
10801         f = fopen(name, "r");
10802         if(f) { // file exists
10803             ASSIGN(appData.tourneyFile, name);
10804             ParseArgsFromFile(f); // parse it
10805         } else {
10806             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10807             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10808                 DisplayError(_("Not enough participants"), 0);
10809                 return 0;
10810             }
10811             if(CheckPlayers(appData.participants)) return 0;
10812             ASSIGN(appData.tourneyFile, name);
10813             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10814             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10815         }
10816         fclose(f);
10817         appData.noChessProgram = FALSE;
10818         appData.clockMode = TRUE;
10819         SetGNUMode();
10820         return 1;
10821 }
10822
10823 int
10824 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10825 {
10826     char buf[MSG_SIZ], *p, *q;
10827     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10828     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10829     skip = !all && group[0]; // if group requested, we start in skip mode
10830     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10831         p = names; q = buf; header = 0;
10832         while(*p && *p != '\n') *q++ = *p++;
10833         *q = 0;
10834         if(*p == '\n') p++;
10835         if(buf[0] == '#') {
10836             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10837             depth++; // we must be entering a new group
10838             if(all) continue; // suppress printing group headers when complete list requested
10839             header = 1;
10840             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10841         }
10842         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10843         if(engineList[i]) free(engineList[i]);
10844         engineList[i] = strdup(buf);
10845         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10846         if(engineMnemonic[i]) free(engineMnemonic[i]);
10847         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10848             strcat(buf, " (");
10849             sscanf(q + 8, "%s", buf + strlen(buf));
10850             strcat(buf, ")");
10851         }
10852         engineMnemonic[i] = strdup(buf);
10853         i++;
10854     }
10855     engineList[i] = engineMnemonic[i] = NULL;
10856     return i;
10857 }
10858
10859 // following implemented as macro to avoid type limitations
10860 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10861
10862 void
10863 SwapEngines (int n)
10864 {   // swap settings for first engine and other engine (so far only some selected options)
10865     int h;
10866     char *p;
10867     if(n == 0) return;
10868     SWAP(directory, p)
10869     SWAP(chessProgram, p)
10870     SWAP(isUCI, h)
10871     SWAP(hasOwnBookUCI, h)
10872     SWAP(protocolVersion, h)
10873     SWAP(reuse, h)
10874     SWAP(scoreIsAbsolute, h)
10875     SWAP(timeOdds, h)
10876     SWAP(logo, p)
10877     SWAP(pgnName, p)
10878     SWAP(pvSAN, h)
10879     SWAP(engOptions, p)
10880     SWAP(engInitString, p)
10881     SWAP(computerString, p)
10882     SWAP(features, p)
10883     SWAP(fenOverride, p)
10884     SWAP(NPS, h)
10885     SWAP(accumulateTC, h)
10886     SWAP(drawDepth, h)
10887     SWAP(host, p)
10888     SWAP(pseudo, h)
10889 }
10890
10891 int
10892 GetEngineLine (char *s, int n)
10893 {
10894     int i;
10895     char buf[MSG_SIZ];
10896     extern char *icsNames;
10897     if(!s || !*s) return 0;
10898     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10899     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10900     if(!mnemonic[i]) return 0;
10901     if(n == 11) return 1; // just testing if there was a match
10902     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10903     if(n == 1) SwapEngines(n);
10904     ParseArgsFromString(buf);
10905     if(n == 1) SwapEngines(n);
10906     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10907         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10908         ParseArgsFromString(buf);
10909     }
10910     return 1;
10911 }
10912
10913 int
10914 SetPlayer (int player, char *p)
10915 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10916     int i;
10917     char buf[MSG_SIZ], *engineName;
10918     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10919     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10920     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10921     if(mnemonic[i]) {
10922         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10923         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10924         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10925         ParseArgsFromString(buf);
10926     } else { // no engine with this nickname is installed!
10927         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10928         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10929         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10930         ModeHighlight();
10931         DisplayError(buf, 0);
10932         return 0;
10933     }
10934     free(engineName);
10935     return i;
10936 }
10937
10938 char *recentEngines;
10939
10940 void
10941 RecentEngineEvent (int nr)
10942 {
10943     int n;
10944 //    SwapEngines(1); // bump first to second
10945 //    ReplaceEngine(&second, 1); // and load it there
10946     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10947     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10948     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10949         ReplaceEngine(&first, 0);
10950         FloatToFront(&appData.recentEngineList, command[n]);
10951     }
10952 }
10953
10954 int
10955 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10956 {   // determine players from game number
10957     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10958
10959     if(appData.tourneyType == 0) {
10960         roundsPerCycle = (nPlayers - 1) | 1;
10961         pairingsPerRound = nPlayers / 2;
10962     } else if(appData.tourneyType > 0) {
10963         roundsPerCycle = nPlayers - appData.tourneyType;
10964         pairingsPerRound = appData.tourneyType;
10965     }
10966     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10967     gamesPerCycle = gamesPerRound * roundsPerCycle;
10968     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10969     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10970     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10971     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10972     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10973     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10974
10975     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10976     if(appData.roundSync) *syncInterval = gamesPerRound;
10977
10978     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10979
10980     if(appData.tourneyType == 0) {
10981         if(curPairing == (nPlayers-1)/2 ) {
10982             *whitePlayer = curRound;
10983             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10984         } else {
10985             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10986             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10987             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10988             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10989         }
10990     } else if(appData.tourneyType > 1) {
10991         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10992         *whitePlayer = curRound + appData.tourneyType;
10993     } else if(appData.tourneyType > 0) {
10994         *whitePlayer = curPairing;
10995         *blackPlayer = curRound + appData.tourneyType;
10996     }
10997
10998     // take care of white/black alternation per round.
10999     // For cycles and games this is already taken care of by default, derived from matchGame!
11000     return curRound & 1;
11001 }
11002
11003 int
11004 NextTourneyGame (int nr, int *swapColors)
11005 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11006     char *p, *q;
11007     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11008     FILE *tf;
11009     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11010     tf = fopen(appData.tourneyFile, "r");
11011     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11012     ParseArgsFromFile(tf); fclose(tf);
11013     InitTimeControls(); // TC might be altered from tourney file
11014
11015     nPlayers = CountPlayers(appData.participants); // count participants
11016     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11017     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11018
11019     if(syncInterval) {
11020         p = q = appData.results;
11021         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11022         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11023             DisplayMessage(_("Waiting for other game(s)"),"");
11024             waitingForGame = TRUE;
11025             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11026             return 0;
11027         }
11028         waitingForGame = FALSE;
11029     }
11030
11031     if(appData.tourneyType < 0) {
11032         if(nr>=0 && !pairingReceived) {
11033             char buf[1<<16];
11034             if(pairing.pr == NoProc) {
11035                 if(!appData.pairingEngine[0]) {
11036                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11037                     return 0;
11038                 }
11039                 StartChessProgram(&pairing); // starts the pairing engine
11040             }
11041             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11042             SendToProgram(buf, &pairing);
11043             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11044             SendToProgram(buf, &pairing);
11045             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11046         }
11047         pairingReceived = 0;                              // ... so we continue here
11048         *swapColors = 0;
11049         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11050         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11051         matchGame = 1; roundNr = nr / syncInterval + 1;
11052     }
11053
11054     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11055
11056     // redefine engines, engine dir, etc.
11057     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11058     if(first.pr == NoProc) {
11059       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11060       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11061     }
11062     if(second.pr == NoProc) {
11063       SwapEngines(1);
11064       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11065       SwapEngines(1);         // and make that valid for second engine by swapping
11066       InitEngine(&second, 1);
11067     }
11068     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11069     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11070     return OK;
11071 }
11072
11073 void
11074 NextMatchGame ()
11075 {   // performs game initialization that does not invoke engines, and then tries to start the game
11076     int res, firstWhite, swapColors = 0;
11077     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11078     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
11079         char buf[MSG_SIZ];
11080         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11081         if(strcmp(buf, currentDebugFile)) { // name has changed
11082             FILE *f = fopen(buf, "w");
11083             if(f) { // if opening the new file failed, just keep using the old one
11084                 ASSIGN(currentDebugFile, buf);
11085                 fclose(debugFP);
11086                 debugFP = f;
11087             }
11088             if(appData.serverFileName) {
11089                 if(serverFP) fclose(serverFP);
11090                 serverFP = fopen(appData.serverFileName, "w");
11091                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11092                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11093             }
11094         }
11095     }
11096     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11097     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11098     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11099     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11100     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11101     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11102     Reset(FALSE, first.pr != NoProc);
11103     res = LoadGameOrPosition(matchGame); // setup game
11104     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11105     if(!res) return; // abort when bad game/pos file
11106     TwoMachinesEvent();
11107 }
11108
11109 void
11110 UserAdjudicationEvent (int result)
11111 {
11112     ChessMove gameResult = GameIsDrawn;
11113
11114     if( result > 0 ) {
11115         gameResult = WhiteWins;
11116     }
11117     else if( result < 0 ) {
11118         gameResult = BlackWins;
11119     }
11120
11121     if( gameMode == TwoMachinesPlay ) {
11122         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11123     }
11124 }
11125
11126
11127 // [HGM] save: calculate checksum of game to make games easily identifiable
11128 int
11129 StringCheckSum (char *s)
11130 {
11131         int i = 0;
11132         if(s==NULL) return 0;
11133         while(*s) i = i*259 + *s++;
11134         return i;
11135 }
11136
11137 int
11138 GameCheckSum ()
11139 {
11140         int i, sum=0;
11141         for(i=backwardMostMove; i<forwardMostMove; i++) {
11142                 sum += pvInfoList[i].depth;
11143                 sum += StringCheckSum(parseList[i]);
11144                 sum += StringCheckSum(commentList[i]);
11145                 sum *= 261;
11146         }
11147         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11148         return sum + StringCheckSum(commentList[i]);
11149 } // end of save patch
11150
11151 void
11152 GameEnds (ChessMove result, char *resultDetails, int whosays)
11153 {
11154     GameMode nextGameMode;
11155     int isIcsGame;
11156     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11157
11158     if(endingGame) return; /* [HGM] crash: forbid recursion */
11159     endingGame = 1;
11160     if(twoBoards) { // [HGM] dual: switch back to one board
11161         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11162         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11163     }
11164     if (appData.debugMode) {
11165       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11166               result, resultDetails ? resultDetails : "(null)", whosays);
11167     }
11168
11169     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11170
11171     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11172
11173     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11174         /* If we are playing on ICS, the server decides when the
11175            game is over, but the engine can offer to draw, claim
11176            a draw, or resign.
11177          */
11178 #if ZIPPY
11179         if (appData.zippyPlay && first.initDone) {
11180             if (result == GameIsDrawn) {
11181                 /* In case draw still needs to be claimed */
11182                 SendToICS(ics_prefix);
11183                 SendToICS("draw\n");
11184             } else if (StrCaseStr(resultDetails, "resign")) {
11185                 SendToICS(ics_prefix);
11186                 SendToICS("resign\n");
11187             }
11188         }
11189 #endif
11190         endingGame = 0; /* [HGM] crash */
11191         return;
11192     }
11193
11194     /* If we're loading the game from a file, stop */
11195     if (whosays == GE_FILE) {
11196       (void) StopLoadGameTimer();
11197       gameFileFP = NULL;
11198     }
11199
11200     /* Cancel draw offers */
11201     first.offeredDraw = second.offeredDraw = 0;
11202
11203     /* If this is an ICS game, only ICS can really say it's done;
11204        if not, anyone can. */
11205     isIcsGame = (gameMode == IcsPlayingWhite ||
11206                  gameMode == IcsPlayingBlack ||
11207                  gameMode == IcsObserving    ||
11208                  gameMode == IcsExamining);
11209
11210     if (!isIcsGame || whosays == GE_ICS) {
11211         /* OK -- not an ICS game, or ICS said it was done */
11212         StopClocks();
11213         if (!isIcsGame && !appData.noChessProgram)
11214           SetUserThinkingEnables();
11215
11216         /* [HGM] if a machine claims the game end we verify this claim */
11217         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11218             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11219                 char claimer;
11220                 ChessMove trueResult = (ChessMove) -1;
11221
11222                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11223                                             first.twoMachinesColor[0] :
11224                                             second.twoMachinesColor[0] ;
11225
11226                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11227                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11228                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11229                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11230                 } else
11231                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11232                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11233                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11234                 } else
11235                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11236                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11237                 }
11238
11239                 // now verify win claims, but not in drop games, as we don't understand those yet
11240                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11241                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11242                     (result == WhiteWins && claimer == 'w' ||
11243                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11244                       if (appData.debugMode) {
11245                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11246                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11247                       }
11248                       if(result != trueResult) {
11249                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11250                               result = claimer == 'w' ? BlackWins : WhiteWins;
11251                               resultDetails = buf;
11252                       }
11253                 } else
11254                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11255                     && (forwardMostMove <= backwardMostMove ||
11256                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11257                         (claimer=='b')==(forwardMostMove&1))
11258                                                                                   ) {
11259                       /* [HGM] verify: draws that were not flagged are false claims */
11260                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11261                       result = claimer == 'w' ? BlackWins : WhiteWins;
11262                       resultDetails = buf;
11263                 }
11264                 /* (Claiming a loss is accepted no questions asked!) */
11265             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11266                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11267                 result = GameUnfinished;
11268                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11269             }
11270             /* [HGM] bare: don't allow bare King to win */
11271             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11272                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11273                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11274                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11275                && result != GameIsDrawn)
11276             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11277                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11278                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11279                         if(p >= 0 && p <= (int)WhiteKing) k++;
11280                 }
11281                 if (appData.debugMode) {
11282                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11283                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11284                 }
11285                 if(k <= 1) {
11286                         result = GameIsDrawn;
11287                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11288                         resultDetails = buf;
11289                 }
11290             }
11291         }
11292
11293
11294         if(serverMoves != NULL && !loadFlag) { char c = '=';
11295             if(result==WhiteWins) c = '+';
11296             if(result==BlackWins) c = '-';
11297             if(resultDetails != NULL)
11298                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11299         }
11300         if (resultDetails != NULL) {
11301             gameInfo.result = result;
11302             gameInfo.resultDetails = StrSave(resultDetails);
11303
11304             /* display last move only if game was not loaded from file */
11305             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11306                 DisplayMove(currentMove - 1);
11307
11308             if (forwardMostMove != 0) {
11309                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11310                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11311                                                                 ) {
11312                     if (*appData.saveGameFile != NULLCHAR) {
11313                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11314                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11315                         else
11316                         SaveGameToFile(appData.saveGameFile, TRUE);
11317                     } else if (appData.autoSaveGames) {
11318                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11319                     }
11320                     if (*appData.savePositionFile != NULLCHAR) {
11321                         SavePositionToFile(appData.savePositionFile);
11322                     }
11323                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11324                 }
11325             }
11326
11327             /* Tell program how game ended in case it is learning */
11328             /* [HGM] Moved this to after saving the PGN, just in case */
11329             /* engine died and we got here through time loss. In that */
11330             /* case we will get a fatal error writing the pipe, which */
11331             /* would otherwise lose us the PGN.                       */
11332             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11333             /* output during GameEnds should never be fatal anymore   */
11334             if (gameMode == MachinePlaysWhite ||
11335                 gameMode == MachinePlaysBlack ||
11336                 gameMode == TwoMachinesPlay ||
11337                 gameMode == IcsPlayingWhite ||
11338                 gameMode == IcsPlayingBlack ||
11339                 gameMode == BeginningOfGame) {
11340                 char buf[MSG_SIZ];
11341                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11342                         resultDetails);
11343                 if (first.pr != NoProc) {
11344                     SendToProgram(buf, &first);
11345                 }
11346                 if (second.pr != NoProc &&
11347                     gameMode == TwoMachinesPlay) {
11348                     SendToProgram(buf, &second);
11349                 }
11350             }
11351         }
11352
11353         if (appData.icsActive) {
11354             if (appData.quietPlay &&
11355                 (gameMode == IcsPlayingWhite ||
11356                  gameMode == IcsPlayingBlack)) {
11357                 SendToICS(ics_prefix);
11358                 SendToICS("set shout 1\n");
11359             }
11360             nextGameMode = IcsIdle;
11361             ics_user_moved = FALSE;
11362             /* clean up premove.  It's ugly when the game has ended and the
11363              * premove highlights are still on the board.
11364              */
11365             if (gotPremove) {
11366               gotPremove = FALSE;
11367               ClearPremoveHighlights();
11368               DrawPosition(FALSE, boards[currentMove]);
11369             }
11370             if (whosays == GE_ICS) {
11371                 switch (result) {
11372                 case WhiteWins:
11373                     if (gameMode == IcsPlayingWhite)
11374                         PlayIcsWinSound();
11375                     else if(gameMode == IcsPlayingBlack)
11376                         PlayIcsLossSound();
11377                     break;
11378                 case BlackWins:
11379                     if (gameMode == IcsPlayingBlack)
11380                         PlayIcsWinSound();
11381                     else if(gameMode == IcsPlayingWhite)
11382                         PlayIcsLossSound();
11383                     break;
11384                 case GameIsDrawn:
11385                     PlayIcsDrawSound();
11386                     break;
11387                 default:
11388                     PlayIcsUnfinishedSound();
11389                 }
11390             }
11391             if(appData.quitNext) { ExitEvent(0); return; }
11392         } else if (gameMode == EditGame ||
11393                    gameMode == PlayFromGameFile ||
11394                    gameMode == AnalyzeMode ||
11395                    gameMode == AnalyzeFile) {
11396             nextGameMode = gameMode;
11397         } else {
11398             nextGameMode = EndOfGame;
11399         }
11400         pausing = FALSE;
11401         ModeHighlight();
11402     } else {
11403         nextGameMode = gameMode;
11404     }
11405
11406     if (appData.noChessProgram) {
11407         gameMode = nextGameMode;
11408         ModeHighlight();
11409         endingGame = 0; /* [HGM] crash */
11410         return;
11411     }
11412
11413     if (first.reuse) {
11414         /* Put first chess program into idle state */
11415         if (first.pr != NoProc &&
11416             (gameMode == MachinePlaysWhite ||
11417              gameMode == MachinePlaysBlack ||
11418              gameMode == TwoMachinesPlay ||
11419              gameMode == IcsPlayingWhite ||
11420              gameMode == IcsPlayingBlack ||
11421              gameMode == BeginningOfGame)) {
11422             SendToProgram("force\n", &first);
11423             if (first.usePing) {
11424               char buf[MSG_SIZ];
11425               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11426               SendToProgram(buf, &first);
11427             }
11428         }
11429     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11430         /* Kill off first chess program */
11431         if (first.isr != NULL)
11432           RemoveInputSource(first.isr);
11433         first.isr = NULL;
11434
11435         if (first.pr != NoProc) {
11436             ExitAnalyzeMode();
11437             DoSleep( appData.delayBeforeQuit );
11438             SendToProgram("quit\n", &first);
11439             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11440             first.reload = TRUE;
11441         }
11442         first.pr = NoProc;
11443     }
11444     if (second.reuse) {
11445         /* Put second chess program into idle state */
11446         if (second.pr != NoProc &&
11447             gameMode == TwoMachinesPlay) {
11448             SendToProgram("force\n", &second);
11449             if (second.usePing) {
11450               char buf[MSG_SIZ];
11451               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11452               SendToProgram(buf, &second);
11453             }
11454         }
11455     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11456         /* Kill off second chess program */
11457         if (second.isr != NULL)
11458           RemoveInputSource(second.isr);
11459         second.isr = NULL;
11460
11461         if (second.pr != NoProc) {
11462             DoSleep( appData.delayBeforeQuit );
11463             SendToProgram("quit\n", &second);
11464             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11465             second.reload = TRUE;
11466         }
11467         second.pr = NoProc;
11468     }
11469
11470     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11471         char resChar = '=';
11472         switch (result) {
11473         case WhiteWins:
11474           resChar = '+';
11475           if (first.twoMachinesColor[0] == 'w') {
11476             first.matchWins++;
11477           } else {
11478             second.matchWins++;
11479           }
11480           break;
11481         case BlackWins:
11482           resChar = '-';
11483           if (first.twoMachinesColor[0] == 'b') {
11484             first.matchWins++;
11485           } else {
11486             second.matchWins++;
11487           }
11488           break;
11489         case GameUnfinished:
11490           resChar = ' ';
11491         default:
11492           break;
11493         }
11494
11495         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11496         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11497             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11498             ReserveGame(nextGame, resChar); // sets nextGame
11499             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11500             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11501         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11502
11503         if (nextGame <= appData.matchGames && !abortMatch) {
11504             gameMode = nextGameMode;
11505             matchGame = nextGame; // this will be overruled in tourney mode!
11506             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11507             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11508             endingGame = 0; /* [HGM] crash */
11509             return;
11510         } else {
11511             gameMode = nextGameMode;
11512             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11513                      first.tidy, second.tidy,
11514                      first.matchWins, second.matchWins,
11515                      appData.matchGames - (first.matchWins + second.matchWins));
11516             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11517             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11518             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11519             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11520                 first.twoMachinesColor = "black\n";
11521                 second.twoMachinesColor = "white\n";
11522             } else {
11523                 first.twoMachinesColor = "white\n";
11524                 second.twoMachinesColor = "black\n";
11525             }
11526         }
11527     }
11528     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11529         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11530       ExitAnalyzeMode();
11531     gameMode = nextGameMode;
11532     ModeHighlight();
11533     endingGame = 0;  /* [HGM] crash */
11534     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11535         if(matchMode == TRUE) { // match through command line: exit with or without popup
11536             if(ranking) {
11537                 ToNrEvent(forwardMostMove);
11538                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11539                 else ExitEvent(0);
11540             } else DisplayFatalError(buf, 0, 0);
11541         } else { // match through menu; just stop, with or without popup
11542             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11543             ModeHighlight();
11544             if(ranking){
11545                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11546             } else DisplayNote(buf);
11547       }
11548       if(ranking) free(ranking);
11549     }
11550 }
11551
11552 /* Assumes program was just initialized (initString sent).
11553    Leaves program in force mode. */
11554 void
11555 FeedMovesToProgram (ChessProgramState *cps, int upto)
11556 {
11557     int i;
11558
11559     if (appData.debugMode)
11560       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11561               startedFromSetupPosition ? "position and " : "",
11562               backwardMostMove, upto, cps->which);
11563     if(currentlyInitializedVariant != gameInfo.variant) {
11564       char buf[MSG_SIZ];
11565         // [HGM] variantswitch: make engine aware of new variant
11566         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11567                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11568                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11569         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11570         SendToProgram(buf, cps);
11571         currentlyInitializedVariant = gameInfo.variant;
11572     }
11573     SendToProgram("force\n", cps);
11574     if (startedFromSetupPosition) {
11575         SendBoard(cps, backwardMostMove);
11576     if (appData.debugMode) {
11577         fprintf(debugFP, "feedMoves\n");
11578     }
11579     }
11580     for (i = backwardMostMove; i < upto; i++) {
11581         SendMoveToProgram(i, cps);
11582     }
11583 }
11584
11585
11586 int
11587 ResurrectChessProgram ()
11588 {
11589      /* The chess program may have exited.
11590         If so, restart it and feed it all the moves made so far. */
11591     static int doInit = 0;
11592
11593     if (appData.noChessProgram) return 1;
11594
11595     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11596         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11597         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11598         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11599     } else {
11600         if (first.pr != NoProc) return 1;
11601         StartChessProgram(&first);
11602     }
11603     InitChessProgram(&first, FALSE);
11604     FeedMovesToProgram(&first, currentMove);
11605
11606     if (!first.sendTime) {
11607         /* can't tell gnuchess what its clock should read,
11608            so we bow to its notion. */
11609         ResetClocks();
11610         timeRemaining[0][currentMove] = whiteTimeRemaining;
11611         timeRemaining[1][currentMove] = blackTimeRemaining;
11612     }
11613
11614     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11615                 appData.icsEngineAnalyze) && first.analysisSupport) {
11616       SendToProgram("analyze\n", &first);
11617       first.analyzing = TRUE;
11618     }
11619     return 1;
11620 }
11621
11622 /*
11623  * Button procedures
11624  */
11625 void
11626 Reset (int redraw, int init)
11627 {
11628     int i;
11629
11630     if (appData.debugMode) {
11631         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11632                 redraw, init, gameMode);
11633     }
11634     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11635     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11636     CleanupTail(); // [HGM] vari: delete any stored variations
11637     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11638     pausing = pauseExamInvalid = FALSE;
11639     startedFromSetupPosition = blackPlaysFirst = FALSE;
11640     firstMove = TRUE;
11641     whiteFlag = blackFlag = FALSE;
11642     userOfferedDraw = FALSE;
11643     hintRequested = bookRequested = FALSE;
11644     first.maybeThinking = FALSE;
11645     second.maybeThinking = FALSE;
11646     first.bookSuspend = FALSE; // [HGM] book
11647     second.bookSuspend = FALSE;
11648     thinkOutput[0] = NULLCHAR;
11649     lastHint[0] = NULLCHAR;
11650     ClearGameInfo(&gameInfo);
11651     gameInfo.variant = StringToVariant(appData.variant);
11652     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11653     ics_user_moved = ics_clock_paused = FALSE;
11654     ics_getting_history = H_FALSE;
11655     ics_gamenum = -1;
11656     white_holding[0] = black_holding[0] = NULLCHAR;
11657     ClearProgramStats();
11658     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11659
11660     ResetFrontEnd();
11661     ClearHighlights();
11662     flipView = appData.flipView;
11663     ClearPremoveHighlights();
11664     gotPremove = FALSE;
11665     alarmSounded = FALSE;
11666     killX = killY = -1; // [HGM] lion
11667
11668     GameEnds(EndOfFile, NULL, GE_PLAYER);
11669     if(appData.serverMovesName != NULL) {
11670         /* [HGM] prepare to make moves file for broadcasting */
11671         clock_t t = clock();
11672         if(serverMoves != NULL) fclose(serverMoves);
11673         serverMoves = fopen(appData.serverMovesName, "r");
11674         if(serverMoves != NULL) {
11675             fclose(serverMoves);
11676             /* delay 15 sec before overwriting, so all clients can see end */
11677             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11678         }
11679         serverMoves = fopen(appData.serverMovesName, "w");
11680     }
11681
11682     ExitAnalyzeMode();
11683     gameMode = BeginningOfGame;
11684     ModeHighlight();
11685     if(appData.icsActive) gameInfo.variant = VariantNormal;
11686     currentMove = forwardMostMove = backwardMostMove = 0;
11687     MarkTargetSquares(1);
11688     InitPosition(redraw);
11689     for (i = 0; i < MAX_MOVES; i++) {
11690         if (commentList[i] != NULL) {
11691             free(commentList[i]);
11692             commentList[i] = NULL;
11693         }
11694     }
11695     ResetClocks();
11696     timeRemaining[0][0] = whiteTimeRemaining;
11697     timeRemaining[1][0] = blackTimeRemaining;
11698
11699     if (first.pr == NoProc) {
11700         StartChessProgram(&first);
11701     }
11702     if (init) {
11703             InitChessProgram(&first, startedFromSetupPosition);
11704     }
11705     DisplayTitle("");
11706     DisplayMessage("", "");
11707     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11708     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11709     ClearMap();        // [HGM] exclude: invalidate map
11710 }
11711
11712 void
11713 AutoPlayGameLoop ()
11714 {
11715     for (;;) {
11716         if (!AutoPlayOneMove())
11717           return;
11718         if (matchMode || appData.timeDelay == 0)
11719           continue;
11720         if (appData.timeDelay < 0)
11721           return;
11722         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11723         break;
11724     }
11725 }
11726
11727 void
11728 AnalyzeNextGame()
11729 {
11730     ReloadGame(1); // next game
11731 }
11732
11733 int
11734 AutoPlayOneMove ()
11735 {
11736     int fromX, fromY, toX, toY;
11737
11738     if (appData.debugMode) {
11739       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11740     }
11741
11742     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11743       return FALSE;
11744
11745     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11746       pvInfoList[currentMove].depth = programStats.depth;
11747       pvInfoList[currentMove].score = programStats.score;
11748       pvInfoList[currentMove].time  = 0;
11749       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11750       else { // append analysis of final position as comment
11751         char buf[MSG_SIZ];
11752         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11753         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11754       }
11755       programStats.depth = 0;
11756     }
11757
11758     if (currentMove >= forwardMostMove) {
11759       if(gameMode == AnalyzeFile) {
11760           if(appData.loadGameIndex == -1) {
11761             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11762           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11763           } else {
11764           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11765         }
11766       }
11767 //      gameMode = EndOfGame;
11768 //      ModeHighlight();
11769
11770       /* [AS] Clear current move marker at the end of a game */
11771       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11772
11773       return FALSE;
11774     }
11775
11776     toX = moveList[currentMove][2] - AAA;
11777     toY = moveList[currentMove][3] - ONE;
11778
11779     if (moveList[currentMove][1] == '@') {
11780         if (appData.highlightLastMove) {
11781             SetHighlights(-1, -1, toX, toY);
11782         }
11783     } else {
11784         int viaX = moveList[currentMove][5] - AAA;
11785         int viaY = moveList[currentMove][6] - ONE;
11786         fromX = moveList[currentMove][0] - AAA;
11787         fromY = moveList[currentMove][1] - ONE;
11788
11789         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11790
11791         if(moveList[currentMove][4] == ';') { // multi-leg
11792             ChessSquare piece = boards[currentMove][viaY][viaX];
11793             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11794             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11795             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11796             boards[currentMove][viaY][viaX] = piece;
11797         } else
11798         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11799
11800         if (appData.highlightLastMove) {
11801             SetHighlights(fromX, fromY, toX, toY);
11802         }
11803     }
11804     DisplayMove(currentMove);
11805     SendMoveToProgram(currentMove++, &first);
11806     DisplayBothClocks();
11807     DrawPosition(FALSE, boards[currentMove]);
11808     // [HGM] PV info: always display, routine tests if empty
11809     DisplayComment(currentMove - 1, commentList[currentMove]);
11810     return TRUE;
11811 }
11812
11813
11814 int
11815 LoadGameOneMove (ChessMove readAhead)
11816 {
11817     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11818     char promoChar = NULLCHAR;
11819     ChessMove moveType;
11820     char move[MSG_SIZ];
11821     char *p, *q;
11822
11823     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11824         gameMode != AnalyzeMode && gameMode != Training) {
11825         gameFileFP = NULL;
11826         return FALSE;
11827     }
11828
11829     yyboardindex = forwardMostMove;
11830     if (readAhead != EndOfFile) {
11831       moveType = readAhead;
11832     } else {
11833       if (gameFileFP == NULL)
11834           return FALSE;
11835       moveType = (ChessMove) Myylex();
11836     }
11837
11838     done = FALSE;
11839     switch (moveType) {
11840       case Comment:
11841         if (appData.debugMode)
11842           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11843         p = yy_text;
11844
11845         /* append the comment but don't display it */
11846         AppendComment(currentMove, p, FALSE);
11847         return TRUE;
11848
11849       case WhiteCapturesEnPassant:
11850       case BlackCapturesEnPassant:
11851       case WhitePromotion:
11852       case BlackPromotion:
11853       case WhiteNonPromotion:
11854       case BlackNonPromotion:
11855       case NormalMove:
11856       case FirstLeg:
11857       case WhiteKingSideCastle:
11858       case WhiteQueenSideCastle:
11859       case BlackKingSideCastle:
11860       case BlackQueenSideCastle:
11861       case WhiteKingSideCastleWild:
11862       case WhiteQueenSideCastleWild:
11863       case BlackKingSideCastleWild:
11864       case BlackQueenSideCastleWild:
11865       /* PUSH Fabien */
11866       case WhiteHSideCastleFR:
11867       case WhiteASideCastleFR:
11868       case BlackHSideCastleFR:
11869       case BlackASideCastleFR:
11870       /* POP Fabien */
11871         if (appData.debugMode)
11872           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11873         fromX = currentMoveString[0] - AAA;
11874         fromY = currentMoveString[1] - ONE;
11875         toX = currentMoveString[2] - AAA;
11876         toY = currentMoveString[3] - ONE;
11877         promoChar = currentMoveString[4];
11878         if(promoChar == ';') promoChar = NULLCHAR;
11879         break;
11880
11881       case WhiteDrop:
11882       case BlackDrop:
11883         if (appData.debugMode)
11884           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11885         fromX = moveType == WhiteDrop ?
11886           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11887         (int) CharToPiece(ToLower(currentMoveString[0]));
11888         fromY = DROP_RANK;
11889         toX = currentMoveString[2] - AAA;
11890         toY = currentMoveString[3] - ONE;
11891         break;
11892
11893       case WhiteWins:
11894       case BlackWins:
11895       case GameIsDrawn:
11896       case GameUnfinished:
11897         if (appData.debugMode)
11898           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11899         p = strchr(yy_text, '{');
11900         if (p == NULL) p = strchr(yy_text, '(');
11901         if (p == NULL) {
11902             p = yy_text;
11903             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11904         } else {
11905             q = strchr(p, *p == '{' ? '}' : ')');
11906             if (q != NULL) *q = NULLCHAR;
11907             p++;
11908         }
11909         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11910         GameEnds(moveType, p, GE_FILE);
11911         done = TRUE;
11912         if (cmailMsgLoaded) {
11913             ClearHighlights();
11914             flipView = WhiteOnMove(currentMove);
11915             if (moveType == GameUnfinished) flipView = !flipView;
11916             if (appData.debugMode)
11917               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11918         }
11919         break;
11920
11921       case EndOfFile:
11922         if (appData.debugMode)
11923           fprintf(debugFP, "Parser hit end of file\n");
11924         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11925           case MT_NONE:
11926           case MT_CHECK:
11927             break;
11928           case MT_CHECKMATE:
11929           case MT_STAINMATE:
11930             if (WhiteOnMove(currentMove)) {
11931                 GameEnds(BlackWins, "Black mates", GE_FILE);
11932             } else {
11933                 GameEnds(WhiteWins, "White mates", GE_FILE);
11934             }
11935             break;
11936           case MT_STALEMATE:
11937             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11938             break;
11939         }
11940         done = TRUE;
11941         break;
11942
11943       case MoveNumberOne:
11944         if (lastLoadGameStart == GNUChessGame) {
11945             /* GNUChessGames have numbers, but they aren't move numbers */
11946             if (appData.debugMode)
11947               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11948                       yy_text, (int) moveType);
11949             return LoadGameOneMove(EndOfFile); /* tail recursion */
11950         }
11951         /* else fall thru */
11952
11953       case XBoardGame:
11954       case GNUChessGame:
11955       case PGNTag:
11956         /* Reached start of next game in file */
11957         if (appData.debugMode)
11958           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11959         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11960           case MT_NONE:
11961           case MT_CHECK:
11962             break;
11963           case MT_CHECKMATE:
11964           case MT_STAINMATE:
11965             if (WhiteOnMove(currentMove)) {
11966                 GameEnds(BlackWins, "Black mates", GE_FILE);
11967             } else {
11968                 GameEnds(WhiteWins, "White mates", GE_FILE);
11969             }
11970             break;
11971           case MT_STALEMATE:
11972             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11973             break;
11974         }
11975         done = TRUE;
11976         break;
11977
11978       case PositionDiagram:     /* should not happen; ignore */
11979       case ElapsedTime:         /* ignore */
11980       case NAG:                 /* ignore */
11981         if (appData.debugMode)
11982           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11983                   yy_text, (int) moveType);
11984         return LoadGameOneMove(EndOfFile); /* tail recursion */
11985
11986       case IllegalMove:
11987         if (appData.testLegality) {
11988             if (appData.debugMode)
11989               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11990             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11991                     (forwardMostMove / 2) + 1,
11992                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11993             DisplayError(move, 0);
11994             done = TRUE;
11995         } else {
11996             if (appData.debugMode)
11997               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11998                       yy_text, currentMoveString);
11999             fromX = currentMoveString[0] - AAA;
12000             fromY = currentMoveString[1] - ONE;
12001             toX = currentMoveString[2] - AAA;
12002             toY = currentMoveString[3] - ONE;
12003             promoChar = currentMoveString[4];
12004         }
12005         break;
12006
12007       case AmbiguousMove:
12008         if (appData.debugMode)
12009           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12010         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12011                 (forwardMostMove / 2) + 1,
12012                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12013         DisplayError(move, 0);
12014         done = TRUE;
12015         break;
12016
12017       default:
12018       case ImpossibleMove:
12019         if (appData.debugMode)
12020           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12021         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12022                 (forwardMostMove / 2) + 1,
12023                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12024         DisplayError(move, 0);
12025         done = TRUE;
12026         break;
12027     }
12028
12029     if (done) {
12030         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12031             DrawPosition(FALSE, boards[currentMove]);
12032             DisplayBothClocks();
12033             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12034               DisplayComment(currentMove - 1, commentList[currentMove]);
12035         }
12036         (void) StopLoadGameTimer();
12037         gameFileFP = NULL;
12038         cmailOldMove = forwardMostMove;
12039         return FALSE;
12040     } else {
12041         /* currentMoveString is set as a side-effect of yylex */
12042
12043         thinkOutput[0] = NULLCHAR;
12044         MakeMove(fromX, fromY, toX, toY, promoChar);
12045         killX = killY = -1; // [HGM] lion: used up
12046         currentMove = forwardMostMove;
12047         return TRUE;
12048     }
12049 }
12050
12051 /* Load the nth game from the given file */
12052 int
12053 LoadGameFromFile (char *filename, int n, char *title, int useList)
12054 {
12055     FILE *f;
12056     char buf[MSG_SIZ];
12057
12058     if (strcmp(filename, "-") == 0) {
12059         f = stdin;
12060         title = "stdin";
12061     } else {
12062         f = fopen(filename, "rb");
12063         if (f == NULL) {
12064           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12065             DisplayError(buf, errno);
12066             return FALSE;
12067         }
12068     }
12069     if (fseek(f, 0, 0) == -1) {
12070         /* f is not seekable; probably a pipe */
12071         useList = FALSE;
12072     }
12073     if (useList && n == 0) {
12074         int error = GameListBuild(f);
12075         if (error) {
12076             DisplayError(_("Cannot build game list"), error);
12077         } else if (!ListEmpty(&gameList) &&
12078                    ((ListGame *) gameList.tailPred)->number > 1) {
12079             GameListPopUp(f, title);
12080             return TRUE;
12081         }
12082         GameListDestroy();
12083         n = 1;
12084     }
12085     if (n == 0) n = 1;
12086     return LoadGame(f, n, title, FALSE);
12087 }
12088
12089
12090 void
12091 MakeRegisteredMove ()
12092 {
12093     int fromX, fromY, toX, toY;
12094     char promoChar;
12095     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12096         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12097           case CMAIL_MOVE:
12098           case CMAIL_DRAW:
12099             if (appData.debugMode)
12100               fprintf(debugFP, "Restoring %s for game %d\n",
12101                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12102
12103             thinkOutput[0] = NULLCHAR;
12104             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12105             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12106             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12107             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12108             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12109             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12110             MakeMove(fromX, fromY, toX, toY, promoChar);
12111             ShowMove(fromX, fromY, toX, toY);
12112
12113             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12114               case MT_NONE:
12115               case MT_CHECK:
12116                 break;
12117
12118               case MT_CHECKMATE:
12119               case MT_STAINMATE:
12120                 if (WhiteOnMove(currentMove)) {
12121                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12122                 } else {
12123                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12124                 }
12125                 break;
12126
12127               case MT_STALEMATE:
12128                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12129                 break;
12130             }
12131
12132             break;
12133
12134           case CMAIL_RESIGN:
12135             if (WhiteOnMove(currentMove)) {
12136                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12137             } else {
12138                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12139             }
12140             break;
12141
12142           case CMAIL_ACCEPT:
12143             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12144             break;
12145
12146           default:
12147             break;
12148         }
12149     }
12150
12151     return;
12152 }
12153
12154 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12155 int
12156 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12157 {
12158     int retVal;
12159
12160     if (gameNumber > nCmailGames) {
12161         DisplayError(_("No more games in this message"), 0);
12162         return FALSE;
12163     }
12164     if (f == lastLoadGameFP) {
12165         int offset = gameNumber - lastLoadGameNumber;
12166         if (offset == 0) {
12167             cmailMsg[0] = NULLCHAR;
12168             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12169                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12170                 nCmailMovesRegistered--;
12171             }
12172             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12173             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12174                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12175             }
12176         } else {
12177             if (! RegisterMove()) return FALSE;
12178         }
12179     }
12180
12181     retVal = LoadGame(f, gameNumber, title, useList);
12182
12183     /* Make move registered during previous look at this game, if any */
12184     MakeRegisteredMove();
12185
12186     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12187         commentList[currentMove]
12188           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12189         DisplayComment(currentMove - 1, commentList[currentMove]);
12190     }
12191
12192     return retVal;
12193 }
12194
12195 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12196 int
12197 ReloadGame (int offset)
12198 {
12199     int gameNumber = lastLoadGameNumber + offset;
12200     if (lastLoadGameFP == NULL) {
12201         DisplayError(_("No game has been loaded yet"), 0);
12202         return FALSE;
12203     }
12204     if (gameNumber <= 0) {
12205         DisplayError(_("Can't back up any further"), 0);
12206         return FALSE;
12207     }
12208     if (cmailMsgLoaded) {
12209         return CmailLoadGame(lastLoadGameFP, gameNumber,
12210                              lastLoadGameTitle, lastLoadGameUseList);
12211     } else {
12212         return LoadGame(lastLoadGameFP, gameNumber,
12213                         lastLoadGameTitle, lastLoadGameUseList);
12214     }
12215 }
12216
12217 int keys[EmptySquare+1];
12218
12219 int
12220 PositionMatches (Board b1, Board b2)
12221 {
12222     int r, f, sum=0;
12223     switch(appData.searchMode) {
12224         case 1: return CompareWithRights(b1, b2);
12225         case 2:
12226             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12227                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12228             }
12229             return TRUE;
12230         case 3:
12231             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12232               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12233                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12234             }
12235             return sum==0;
12236         case 4:
12237             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12238                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12239             }
12240             return sum==0;
12241     }
12242     return TRUE;
12243 }
12244
12245 #define Q_PROMO  4
12246 #define Q_EP     3
12247 #define Q_BCASTL 2
12248 #define Q_WCASTL 1
12249
12250 int pieceList[256], quickBoard[256];
12251 ChessSquare pieceType[256] = { EmptySquare };
12252 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12253 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12254 int soughtTotal, turn;
12255 Boolean epOK, flipSearch;
12256
12257 typedef struct {
12258     unsigned char piece, to;
12259 } Move;
12260
12261 #define DSIZE (250000)
12262
12263 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12264 Move *moveDatabase = initialSpace;
12265 unsigned int movePtr, dataSize = DSIZE;
12266
12267 int
12268 MakePieceList (Board board, int *counts)
12269 {
12270     int r, f, n=Q_PROMO, total=0;
12271     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12272     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12273         int sq = f + (r<<4);
12274         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12275             quickBoard[sq] = ++n;
12276             pieceList[n] = sq;
12277             pieceType[n] = board[r][f];
12278             counts[board[r][f]]++;
12279             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12280             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12281             total++;
12282         }
12283     }
12284     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12285     return total;
12286 }
12287
12288 void
12289 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12290 {
12291     int sq = fromX + (fromY<<4);
12292     int piece = quickBoard[sq], rook;
12293     quickBoard[sq] = 0;
12294     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12295     if(piece == pieceList[1] && fromY == toY) {
12296       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12297         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12298         moveDatabase[movePtr++].piece = Q_WCASTL;
12299         quickBoard[sq] = piece;
12300         piece = quickBoard[from]; quickBoard[from] = 0;
12301         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12302       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12303         quickBoard[sq] = 0; // remove Rook
12304         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12305         moveDatabase[movePtr++].piece = Q_WCASTL;
12306         quickBoard[sq] = pieceList[1]; // put King
12307         piece = rook;
12308         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12309       }
12310     } else
12311     if(piece == pieceList[2] && fromY == toY) {
12312       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12313         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12314         moveDatabase[movePtr++].piece = Q_BCASTL;
12315         quickBoard[sq] = piece;
12316         piece = quickBoard[from]; quickBoard[from] = 0;
12317         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12318       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12319         quickBoard[sq] = 0; // remove Rook
12320         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12321         moveDatabase[movePtr++].piece = Q_BCASTL;
12322         quickBoard[sq] = pieceList[2]; // put King
12323         piece = rook;
12324         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12325       }
12326     } else
12327     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12328         quickBoard[(fromY<<4)+toX] = 0;
12329         moveDatabase[movePtr].piece = Q_EP;
12330         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12331         moveDatabase[movePtr].to = sq;
12332     } else
12333     if(promoPiece != pieceType[piece]) {
12334         moveDatabase[movePtr++].piece = Q_PROMO;
12335         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12336     }
12337     moveDatabase[movePtr].piece = piece;
12338     quickBoard[sq] = piece;
12339     movePtr++;
12340 }
12341
12342 int
12343 PackGame (Board board)
12344 {
12345     Move *newSpace = NULL;
12346     moveDatabase[movePtr].piece = 0; // terminate previous game
12347     if(movePtr > dataSize) {
12348         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12349         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12350         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12351         if(newSpace) {
12352             int i;
12353             Move *p = moveDatabase, *q = newSpace;
12354             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12355             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12356             moveDatabase = newSpace;
12357         } else { // calloc failed, we must be out of memory. Too bad...
12358             dataSize = 0; // prevent calloc events for all subsequent games
12359             return 0;     // and signal this one isn't cached
12360         }
12361     }
12362     movePtr++;
12363     MakePieceList(board, counts);
12364     return movePtr;
12365 }
12366
12367 int
12368 QuickCompare (Board board, int *minCounts, int *maxCounts)
12369 {   // compare according to search mode
12370     int r, f;
12371     switch(appData.searchMode)
12372     {
12373       case 1: // exact position match
12374         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12375         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12376             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12377         }
12378         break;
12379       case 2: // can have extra material on empty squares
12380         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12381             if(board[r][f] == EmptySquare) continue;
12382             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12383         }
12384         break;
12385       case 3: // material with exact Pawn structure
12386         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12387             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12388             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12389         } // fall through to material comparison
12390       case 4: // exact material
12391         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12392         break;
12393       case 6: // material range with given imbalance
12394         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12395         // fall through to range comparison
12396       case 5: // material range
12397         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12398     }
12399     return TRUE;
12400 }
12401
12402 int
12403 QuickScan (Board board, Move *move)
12404 {   // reconstruct game,and compare all positions in it
12405     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12406     do {
12407         int piece = move->piece;
12408         int to = move->to, from = pieceList[piece];
12409         if(found < 0) { // if already found just scan to game end for final piece count
12410           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12411            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12412            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12413                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12414             ) {
12415             static int lastCounts[EmptySquare+1];
12416             int i;
12417             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12418             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12419           } else stretch = 0;
12420           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12421           if(found >= 0 && !appData.minPieces) return found;
12422         }
12423         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12424           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12425           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12426             piece = (++move)->piece;
12427             from = pieceList[piece];
12428             counts[pieceType[piece]]--;
12429             pieceType[piece] = (ChessSquare) move->to;
12430             counts[move->to]++;
12431           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12432             counts[pieceType[quickBoard[to]]]--;
12433             quickBoard[to] = 0; total--;
12434             move++;
12435             continue;
12436           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12437             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12438             from  = pieceList[piece]; // so this must be King
12439             quickBoard[from] = 0;
12440             pieceList[piece] = to;
12441             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12442             quickBoard[from] = 0; // rook
12443             quickBoard[to] = piece;
12444             to = move->to; piece = move->piece;
12445             goto aftercastle;
12446           }
12447         }
12448         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12449         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12450         quickBoard[from] = 0;
12451       aftercastle:
12452         quickBoard[to] = piece;
12453         pieceList[piece] = to;
12454         cnt++; turn ^= 3;
12455         move++;
12456     } while(1);
12457 }
12458
12459 void
12460 InitSearch ()
12461 {
12462     int r, f;
12463     flipSearch = FALSE;
12464     CopyBoard(soughtBoard, boards[currentMove]);
12465     soughtTotal = MakePieceList(soughtBoard, maxSought);
12466     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12467     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12468     CopyBoard(reverseBoard, boards[currentMove]);
12469     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12470         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12471         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12472         reverseBoard[r][f] = piece;
12473     }
12474     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12475     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12476     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12477                  || (boards[currentMove][CASTLING][2] == NoRights ||
12478                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12479                  && (boards[currentMove][CASTLING][5] == NoRights ||
12480                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12481       ) {
12482         flipSearch = TRUE;
12483         CopyBoard(flipBoard, soughtBoard);
12484         CopyBoard(rotateBoard, reverseBoard);
12485         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12486             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12487             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12488         }
12489     }
12490     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12491     if(appData.searchMode >= 5) {
12492         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12493         MakePieceList(soughtBoard, minSought);
12494         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12495     }
12496     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12497         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12498 }
12499
12500 GameInfo dummyInfo;
12501 static int creatingBook;
12502
12503 int
12504 GameContainsPosition (FILE *f, ListGame *lg)
12505 {
12506     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12507     int fromX, fromY, toX, toY;
12508     char promoChar;
12509     static int initDone=FALSE;
12510
12511     // weed out games based on numerical tag comparison
12512     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12513     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12514     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12515     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12516     if(!initDone) {
12517         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12518         initDone = TRUE;
12519     }
12520     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12521     else CopyBoard(boards[scratch], initialPosition); // default start position
12522     if(lg->moves) {
12523         turn = btm + 1;
12524         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12525         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12526     }
12527     if(btm) plyNr++;
12528     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12529     fseek(f, lg->offset, 0);
12530     yynewfile(f);
12531     while(1) {
12532         yyboardindex = scratch;
12533         quickFlag = plyNr+1;
12534         next = Myylex();
12535         quickFlag = 0;
12536         switch(next) {
12537             case PGNTag:
12538                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12539             default:
12540                 continue;
12541
12542             case XBoardGame:
12543             case GNUChessGame:
12544                 if(plyNr) return -1; // after we have seen moves, this is for new game
12545               continue;
12546
12547             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12548             case ImpossibleMove:
12549             case WhiteWins: // game ends here with these four
12550             case BlackWins:
12551             case GameIsDrawn:
12552             case GameUnfinished:
12553                 return -1;
12554
12555             case IllegalMove:
12556                 if(appData.testLegality) return -1;
12557             case WhiteCapturesEnPassant:
12558             case BlackCapturesEnPassant:
12559             case WhitePromotion:
12560             case BlackPromotion:
12561             case WhiteNonPromotion:
12562             case BlackNonPromotion:
12563             case NormalMove:
12564             case FirstLeg:
12565             case WhiteKingSideCastle:
12566             case WhiteQueenSideCastle:
12567             case BlackKingSideCastle:
12568             case BlackQueenSideCastle:
12569             case WhiteKingSideCastleWild:
12570             case WhiteQueenSideCastleWild:
12571             case BlackKingSideCastleWild:
12572             case BlackQueenSideCastleWild:
12573             case WhiteHSideCastleFR:
12574             case WhiteASideCastleFR:
12575             case BlackHSideCastleFR:
12576             case BlackASideCastleFR:
12577                 fromX = currentMoveString[0] - AAA;
12578                 fromY = currentMoveString[1] - ONE;
12579                 toX = currentMoveString[2] - AAA;
12580                 toY = currentMoveString[3] - ONE;
12581                 promoChar = currentMoveString[4];
12582                 break;
12583             case WhiteDrop:
12584             case BlackDrop:
12585                 fromX = next == WhiteDrop ?
12586                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12587                   (int) CharToPiece(ToLower(currentMoveString[0]));
12588                 fromY = DROP_RANK;
12589                 toX = currentMoveString[2] - AAA;
12590                 toY = currentMoveString[3] - ONE;
12591                 promoChar = 0;
12592                 break;
12593         }
12594         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12595         plyNr++;
12596         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12597         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12598         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12599         if(appData.findMirror) {
12600             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12601             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12602         }
12603     }
12604 }
12605
12606 /* Load the nth game from open file f */
12607 int
12608 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12609 {
12610     ChessMove cm;
12611     char buf[MSG_SIZ];
12612     int gn = gameNumber;
12613     ListGame *lg = NULL;
12614     int numPGNTags = 0;
12615     int err, pos = -1;
12616     GameMode oldGameMode;
12617     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12618
12619     if (appData.debugMode)
12620         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12621
12622     if (gameMode == Training )
12623         SetTrainingModeOff();
12624
12625     oldGameMode = gameMode;
12626     if (gameMode != BeginningOfGame) {
12627       Reset(FALSE, TRUE);
12628     }
12629     killX = killY = -1; // [HGM] lion: in case we did not Reset
12630
12631     gameFileFP = f;
12632     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12633         fclose(lastLoadGameFP);
12634     }
12635
12636     if (useList) {
12637         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12638
12639         if (lg) {
12640             fseek(f, lg->offset, 0);
12641             GameListHighlight(gameNumber);
12642             pos = lg->position;
12643             gn = 1;
12644         }
12645         else {
12646             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12647               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12648             else
12649             DisplayError(_("Game number out of range"), 0);
12650             return FALSE;
12651         }
12652     } else {
12653         GameListDestroy();
12654         if (fseek(f, 0, 0) == -1) {
12655             if (f == lastLoadGameFP ?
12656                 gameNumber == lastLoadGameNumber + 1 :
12657                 gameNumber == 1) {
12658                 gn = 1;
12659             } else {
12660                 DisplayError(_("Can't seek on game file"), 0);
12661                 return FALSE;
12662             }
12663         }
12664     }
12665     lastLoadGameFP = f;
12666     lastLoadGameNumber = gameNumber;
12667     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12668     lastLoadGameUseList = useList;
12669
12670     yynewfile(f);
12671
12672     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12673       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12674                 lg->gameInfo.black);
12675             DisplayTitle(buf);
12676     } else if (*title != NULLCHAR) {
12677         if (gameNumber > 1) {
12678           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12679             DisplayTitle(buf);
12680         } else {
12681             DisplayTitle(title);
12682         }
12683     }
12684
12685     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12686         gameMode = PlayFromGameFile;
12687         ModeHighlight();
12688     }
12689
12690     currentMove = forwardMostMove = backwardMostMove = 0;
12691     CopyBoard(boards[0], initialPosition);
12692     StopClocks();
12693
12694     /*
12695      * Skip the first gn-1 games in the file.
12696      * Also skip over anything that precedes an identifiable
12697      * start of game marker, to avoid being confused by
12698      * garbage at the start of the file.  Currently
12699      * recognized start of game markers are the move number "1",
12700      * the pattern "gnuchess .* game", the pattern
12701      * "^[#;%] [^ ]* game file", and a PGN tag block.
12702      * A game that starts with one of the latter two patterns
12703      * will also have a move number 1, possibly
12704      * following a position diagram.
12705      * 5-4-02: Let's try being more lenient and allowing a game to
12706      * start with an unnumbered move.  Does that break anything?
12707      */
12708     cm = lastLoadGameStart = EndOfFile;
12709     while (gn > 0) {
12710         yyboardindex = forwardMostMove;
12711         cm = (ChessMove) Myylex();
12712         switch (cm) {
12713           case EndOfFile:
12714             if (cmailMsgLoaded) {
12715                 nCmailGames = CMAIL_MAX_GAMES - gn;
12716             } else {
12717                 Reset(TRUE, TRUE);
12718                 DisplayError(_("Game not found in file"), 0);
12719             }
12720             return FALSE;
12721
12722           case GNUChessGame:
12723           case XBoardGame:
12724             gn--;
12725             lastLoadGameStart = cm;
12726             break;
12727
12728           case MoveNumberOne:
12729             switch (lastLoadGameStart) {
12730               case GNUChessGame:
12731               case XBoardGame:
12732               case PGNTag:
12733                 break;
12734               case MoveNumberOne:
12735               case EndOfFile:
12736                 gn--;           /* count this game */
12737                 lastLoadGameStart = cm;
12738                 break;
12739               default:
12740                 /* impossible */
12741                 break;
12742             }
12743             break;
12744
12745           case PGNTag:
12746             switch (lastLoadGameStart) {
12747               case GNUChessGame:
12748               case PGNTag:
12749               case MoveNumberOne:
12750               case EndOfFile:
12751                 gn--;           /* count this game */
12752                 lastLoadGameStart = cm;
12753                 break;
12754               case XBoardGame:
12755                 lastLoadGameStart = cm; /* game counted already */
12756                 break;
12757               default:
12758                 /* impossible */
12759                 break;
12760             }
12761             if (gn > 0) {
12762                 do {
12763                     yyboardindex = forwardMostMove;
12764                     cm = (ChessMove) Myylex();
12765                 } while (cm == PGNTag || cm == Comment);
12766             }
12767             break;
12768
12769           case WhiteWins:
12770           case BlackWins:
12771           case GameIsDrawn:
12772             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12773                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12774                     != CMAIL_OLD_RESULT) {
12775                     nCmailResults ++ ;
12776                     cmailResult[  CMAIL_MAX_GAMES
12777                                 - gn - 1] = CMAIL_OLD_RESULT;
12778                 }
12779             }
12780             break;
12781
12782           case NormalMove:
12783           case FirstLeg:
12784             /* Only a NormalMove can be at the start of a game
12785              * without a position diagram. */
12786             if (lastLoadGameStart == EndOfFile ) {
12787               gn--;
12788               lastLoadGameStart = MoveNumberOne;
12789             }
12790             break;
12791
12792           default:
12793             break;
12794         }
12795     }
12796
12797     if (appData.debugMode)
12798       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12799
12800     if (cm == XBoardGame) {
12801         /* Skip any header junk before position diagram and/or move 1 */
12802         for (;;) {
12803             yyboardindex = forwardMostMove;
12804             cm = (ChessMove) Myylex();
12805
12806             if (cm == EndOfFile ||
12807                 cm == GNUChessGame || cm == XBoardGame) {
12808                 /* Empty game; pretend end-of-file and handle later */
12809                 cm = EndOfFile;
12810                 break;
12811             }
12812
12813             if (cm == MoveNumberOne || cm == PositionDiagram ||
12814                 cm == PGNTag || cm == Comment)
12815               break;
12816         }
12817     } else if (cm == GNUChessGame) {
12818         if (gameInfo.event != NULL) {
12819             free(gameInfo.event);
12820         }
12821         gameInfo.event = StrSave(yy_text);
12822     }
12823
12824     startedFromSetupPosition = FALSE;
12825     while (cm == PGNTag) {
12826         if (appData.debugMode)
12827           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12828         err = ParsePGNTag(yy_text, &gameInfo);
12829         if (!err) numPGNTags++;
12830
12831         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12832         if(gameInfo.variant != oldVariant) {
12833             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12834             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12835             InitPosition(TRUE);
12836             oldVariant = gameInfo.variant;
12837             if (appData.debugMode)
12838               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12839         }
12840
12841
12842         if (gameInfo.fen != NULL) {
12843           Board initial_position;
12844           startedFromSetupPosition = TRUE;
12845           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12846             Reset(TRUE, TRUE);
12847             DisplayError(_("Bad FEN position in file"), 0);
12848             return FALSE;
12849           }
12850           CopyBoard(boards[0], initial_position);
12851           if (blackPlaysFirst) {
12852             currentMove = forwardMostMove = backwardMostMove = 1;
12853             CopyBoard(boards[1], initial_position);
12854             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12855             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12856             timeRemaining[0][1] = whiteTimeRemaining;
12857             timeRemaining[1][1] = blackTimeRemaining;
12858             if (commentList[0] != NULL) {
12859               commentList[1] = commentList[0];
12860               commentList[0] = NULL;
12861             }
12862           } else {
12863             currentMove = forwardMostMove = backwardMostMove = 0;
12864           }
12865           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12866           {   int i;
12867               initialRulePlies = FENrulePlies;
12868               for( i=0; i< nrCastlingRights; i++ )
12869                   initialRights[i] = initial_position[CASTLING][i];
12870           }
12871           yyboardindex = forwardMostMove;
12872           free(gameInfo.fen);
12873           gameInfo.fen = NULL;
12874         }
12875
12876         yyboardindex = forwardMostMove;
12877         cm = (ChessMove) Myylex();
12878
12879         /* Handle comments interspersed among the tags */
12880         while (cm == Comment) {
12881             char *p;
12882             if (appData.debugMode)
12883               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12884             p = yy_text;
12885             AppendComment(currentMove, p, FALSE);
12886             yyboardindex = forwardMostMove;
12887             cm = (ChessMove) Myylex();
12888         }
12889     }
12890
12891     /* don't rely on existence of Event tag since if game was
12892      * pasted from clipboard the Event tag may not exist
12893      */
12894     if (numPGNTags > 0){
12895         char *tags;
12896         if (gameInfo.variant == VariantNormal) {
12897           VariantClass v = StringToVariant(gameInfo.event);
12898           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12899           if(v < VariantShogi) gameInfo.variant = v;
12900         }
12901         if (!matchMode) {
12902           if( appData.autoDisplayTags ) {
12903             tags = PGNTags(&gameInfo);
12904             TagsPopUp(tags, CmailMsg());
12905             free(tags);
12906           }
12907         }
12908     } else {
12909         /* Make something up, but don't display it now */
12910         SetGameInfo();
12911         TagsPopDown();
12912     }
12913
12914     if (cm == PositionDiagram) {
12915         int i, j;
12916         char *p;
12917         Board initial_position;
12918
12919         if (appData.debugMode)
12920           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12921
12922         if (!startedFromSetupPosition) {
12923             p = yy_text;
12924             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12925               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12926                 switch (*p) {
12927                   case '{':
12928                   case '[':
12929                   case '-':
12930                   case ' ':
12931                   case '\t':
12932                   case '\n':
12933                   case '\r':
12934                     break;
12935                   default:
12936                     initial_position[i][j++] = CharToPiece(*p);
12937                     break;
12938                 }
12939             while (*p == ' ' || *p == '\t' ||
12940                    *p == '\n' || *p == '\r') p++;
12941
12942             if (strncmp(p, "black", strlen("black"))==0)
12943               blackPlaysFirst = TRUE;
12944             else
12945               blackPlaysFirst = FALSE;
12946             startedFromSetupPosition = TRUE;
12947
12948             CopyBoard(boards[0], initial_position);
12949             if (blackPlaysFirst) {
12950                 currentMove = forwardMostMove = backwardMostMove = 1;
12951                 CopyBoard(boards[1], initial_position);
12952                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12953                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12954                 timeRemaining[0][1] = whiteTimeRemaining;
12955                 timeRemaining[1][1] = blackTimeRemaining;
12956                 if (commentList[0] != NULL) {
12957                     commentList[1] = commentList[0];
12958                     commentList[0] = NULL;
12959                 }
12960             } else {
12961                 currentMove = forwardMostMove = backwardMostMove = 0;
12962             }
12963         }
12964         yyboardindex = forwardMostMove;
12965         cm = (ChessMove) Myylex();
12966     }
12967
12968   if(!creatingBook) {
12969     if (first.pr == NoProc) {
12970         StartChessProgram(&first);
12971     }
12972     InitChessProgram(&first, FALSE);
12973     SendToProgram("force\n", &first);
12974     if (startedFromSetupPosition) {
12975         SendBoard(&first, forwardMostMove);
12976     if (appData.debugMode) {
12977         fprintf(debugFP, "Load Game\n");
12978     }
12979         DisplayBothClocks();
12980     }
12981   }
12982
12983     /* [HGM] server: flag to write setup moves in broadcast file as one */
12984     loadFlag = appData.suppressLoadMoves;
12985
12986     while (cm == Comment) {
12987         char *p;
12988         if (appData.debugMode)
12989           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12990         p = yy_text;
12991         AppendComment(currentMove, p, FALSE);
12992         yyboardindex = forwardMostMove;
12993         cm = (ChessMove) Myylex();
12994     }
12995
12996     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12997         cm == WhiteWins || cm == BlackWins ||
12998         cm == GameIsDrawn || cm == GameUnfinished) {
12999         DisplayMessage("", _("No moves in game"));
13000         if (cmailMsgLoaded) {
13001             if (appData.debugMode)
13002               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13003             ClearHighlights();
13004             flipView = FALSE;
13005         }
13006         DrawPosition(FALSE, boards[currentMove]);
13007         DisplayBothClocks();
13008         gameMode = EditGame;
13009         ModeHighlight();
13010         gameFileFP = NULL;
13011         cmailOldMove = 0;
13012         return TRUE;
13013     }
13014
13015     // [HGM] PV info: routine tests if comment empty
13016     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13017         DisplayComment(currentMove - 1, commentList[currentMove]);
13018     }
13019     if (!matchMode && appData.timeDelay != 0)
13020       DrawPosition(FALSE, boards[currentMove]);
13021
13022     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13023       programStats.ok_to_send = 1;
13024     }
13025
13026     /* if the first token after the PGN tags is a move
13027      * and not move number 1, retrieve it from the parser
13028      */
13029     if (cm != MoveNumberOne)
13030         LoadGameOneMove(cm);
13031
13032     /* load the remaining moves from the file */
13033     while (LoadGameOneMove(EndOfFile)) {
13034       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13035       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13036     }
13037
13038     /* rewind to the start of the game */
13039     currentMove = backwardMostMove;
13040
13041     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13042
13043     if (oldGameMode == AnalyzeFile) {
13044       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13045       AnalyzeFileEvent();
13046     } else
13047     if (oldGameMode == AnalyzeMode) {
13048       AnalyzeFileEvent();
13049     }
13050
13051     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13052         long int w, b; // [HGM] adjourn: restore saved clock times
13053         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13054         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13055             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13056             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13057         }
13058     }
13059
13060     if(creatingBook) return TRUE;
13061     if (!matchMode && pos > 0) {
13062         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13063     } else
13064     if (matchMode || appData.timeDelay == 0) {
13065       ToEndEvent();
13066     } else if (appData.timeDelay > 0) {
13067       AutoPlayGameLoop();
13068     }
13069
13070     if (appData.debugMode)
13071         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13072
13073     loadFlag = 0; /* [HGM] true game starts */
13074     return TRUE;
13075 }
13076
13077 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13078 int
13079 ReloadPosition (int offset)
13080 {
13081     int positionNumber = lastLoadPositionNumber + offset;
13082     if (lastLoadPositionFP == NULL) {
13083         DisplayError(_("No position has been loaded yet"), 0);
13084         return FALSE;
13085     }
13086     if (positionNumber <= 0) {
13087         DisplayError(_("Can't back up any further"), 0);
13088         return FALSE;
13089     }
13090     return LoadPosition(lastLoadPositionFP, positionNumber,
13091                         lastLoadPositionTitle);
13092 }
13093
13094 /* Load the nth position from the given file */
13095 int
13096 LoadPositionFromFile (char *filename, int n, char *title)
13097 {
13098     FILE *f;
13099     char buf[MSG_SIZ];
13100
13101     if (strcmp(filename, "-") == 0) {
13102         return LoadPosition(stdin, n, "stdin");
13103     } else {
13104         f = fopen(filename, "rb");
13105         if (f == NULL) {
13106             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13107             DisplayError(buf, errno);
13108             return FALSE;
13109         } else {
13110             return LoadPosition(f, n, title);
13111         }
13112     }
13113 }
13114
13115 /* Load the nth position from the given open file, and close it */
13116 int
13117 LoadPosition (FILE *f, int positionNumber, char *title)
13118 {
13119     char *p, line[MSG_SIZ];
13120     Board initial_position;
13121     int i, j, fenMode, pn;
13122
13123     if (gameMode == Training )
13124         SetTrainingModeOff();
13125
13126     if (gameMode != BeginningOfGame) {
13127         Reset(FALSE, TRUE);
13128     }
13129     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13130         fclose(lastLoadPositionFP);
13131     }
13132     if (positionNumber == 0) positionNumber = 1;
13133     lastLoadPositionFP = f;
13134     lastLoadPositionNumber = positionNumber;
13135     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13136     if (first.pr == NoProc && !appData.noChessProgram) {
13137       StartChessProgram(&first);
13138       InitChessProgram(&first, FALSE);
13139     }
13140     pn = positionNumber;
13141     if (positionNumber < 0) {
13142         /* Negative position number means to seek to that byte offset */
13143         if (fseek(f, -positionNumber, 0) == -1) {
13144             DisplayError(_("Can't seek on position file"), 0);
13145             return FALSE;
13146         };
13147         pn = 1;
13148     } else {
13149         if (fseek(f, 0, 0) == -1) {
13150             if (f == lastLoadPositionFP ?
13151                 positionNumber == lastLoadPositionNumber + 1 :
13152                 positionNumber == 1) {
13153                 pn = 1;
13154             } else {
13155                 DisplayError(_("Can't seek on position file"), 0);
13156                 return FALSE;
13157             }
13158         }
13159     }
13160     /* See if this file is FEN or old-style xboard */
13161     if (fgets(line, MSG_SIZ, f) == NULL) {
13162         DisplayError(_("Position not found in file"), 0);
13163         return FALSE;
13164     }
13165     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13166     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13167
13168     if (pn >= 2) {
13169         if (fenMode || line[0] == '#') pn--;
13170         while (pn > 0) {
13171             /* skip positions before number pn */
13172             if (fgets(line, MSG_SIZ, f) == NULL) {
13173                 Reset(TRUE, TRUE);
13174                 DisplayError(_("Position not found in file"), 0);
13175                 return FALSE;
13176             }
13177             if (fenMode || line[0] == '#') pn--;
13178         }
13179     }
13180
13181     if (fenMode) {
13182         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13183             DisplayError(_("Bad FEN position in file"), 0);
13184             return FALSE;
13185         }
13186     } else {
13187         (void) fgets(line, MSG_SIZ, f);
13188         (void) fgets(line, MSG_SIZ, f);
13189
13190         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13191             (void) fgets(line, MSG_SIZ, f);
13192             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13193                 if (*p == ' ')
13194                   continue;
13195                 initial_position[i][j++] = CharToPiece(*p);
13196             }
13197         }
13198
13199         blackPlaysFirst = FALSE;
13200         if (!feof(f)) {
13201             (void) fgets(line, MSG_SIZ, f);
13202             if (strncmp(line, "black", strlen("black"))==0)
13203               blackPlaysFirst = TRUE;
13204         }
13205     }
13206     startedFromSetupPosition = TRUE;
13207
13208     CopyBoard(boards[0], initial_position);
13209     if (blackPlaysFirst) {
13210         currentMove = forwardMostMove = backwardMostMove = 1;
13211         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13212         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13213         CopyBoard(boards[1], initial_position);
13214         DisplayMessage("", _("Black to play"));
13215     } else {
13216         currentMove = forwardMostMove = backwardMostMove = 0;
13217         DisplayMessage("", _("White to play"));
13218     }
13219     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13220     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13221         SendToProgram("force\n", &first);
13222         SendBoard(&first, forwardMostMove);
13223     }
13224     if (appData.debugMode) {
13225 int i, j;
13226   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13227   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13228         fprintf(debugFP, "Load Position\n");
13229     }
13230
13231     if (positionNumber > 1) {
13232       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13233         DisplayTitle(line);
13234     } else {
13235         DisplayTitle(title);
13236     }
13237     gameMode = EditGame;
13238     ModeHighlight();
13239     ResetClocks();
13240     timeRemaining[0][1] = whiteTimeRemaining;
13241     timeRemaining[1][1] = blackTimeRemaining;
13242     DrawPosition(FALSE, boards[currentMove]);
13243
13244     return TRUE;
13245 }
13246
13247
13248 void
13249 CopyPlayerNameIntoFileName (char **dest, char *src)
13250 {
13251     while (*src != NULLCHAR && *src != ',') {
13252         if (*src == ' ') {
13253             *(*dest)++ = '_';
13254             src++;
13255         } else {
13256             *(*dest)++ = *src++;
13257         }
13258     }
13259 }
13260
13261 char *
13262 DefaultFileName (char *ext)
13263 {
13264     static char def[MSG_SIZ];
13265     char *p;
13266
13267     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13268         p = def;
13269         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13270         *p++ = '-';
13271         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13272         *p++ = '.';
13273         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13274     } else {
13275         def[0] = NULLCHAR;
13276     }
13277     return def;
13278 }
13279
13280 /* Save the current game to the given file */
13281 int
13282 SaveGameToFile (char *filename, int append)
13283 {
13284     FILE *f;
13285     char buf[MSG_SIZ];
13286     int result, i, t,tot=0;
13287
13288     if (strcmp(filename, "-") == 0) {
13289         return SaveGame(stdout, 0, NULL);
13290     } else {
13291         for(i=0; i<10; i++) { // upto 10 tries
13292              f = fopen(filename, append ? "a" : "w");
13293              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13294              if(f || errno != 13) break;
13295              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13296              tot += t;
13297         }
13298         if (f == NULL) {
13299             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13300             DisplayError(buf, errno);
13301             return FALSE;
13302         } else {
13303             safeStrCpy(buf, lastMsg, MSG_SIZ);
13304             DisplayMessage(_("Waiting for access to save file"), "");
13305             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13306             DisplayMessage(_("Saving game"), "");
13307             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13308             result = SaveGame(f, 0, NULL);
13309             DisplayMessage(buf, "");
13310             return result;
13311         }
13312     }
13313 }
13314
13315 char *
13316 SavePart (char *str)
13317 {
13318     static char buf[MSG_SIZ];
13319     char *p;
13320
13321     p = strchr(str, ' ');
13322     if (p == NULL) return str;
13323     strncpy(buf, str, p - str);
13324     buf[p - str] = NULLCHAR;
13325     return buf;
13326 }
13327
13328 #define PGN_MAX_LINE 75
13329
13330 #define PGN_SIDE_WHITE  0
13331 #define PGN_SIDE_BLACK  1
13332
13333 static int
13334 FindFirstMoveOutOfBook (int side)
13335 {
13336     int result = -1;
13337
13338     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13339         int index = backwardMostMove;
13340         int has_book_hit = 0;
13341
13342         if( (index % 2) != side ) {
13343             index++;
13344         }
13345
13346         while( index < forwardMostMove ) {
13347             /* Check to see if engine is in book */
13348             int depth = pvInfoList[index].depth;
13349             int score = pvInfoList[index].score;
13350             int in_book = 0;
13351
13352             if( depth <= 2 ) {
13353                 in_book = 1;
13354             }
13355             else if( score == 0 && depth == 63 ) {
13356                 in_book = 1; /* Zappa */
13357             }
13358             else if( score == 2 && depth == 99 ) {
13359                 in_book = 1; /* Abrok */
13360             }
13361
13362             has_book_hit += in_book;
13363
13364             if( ! in_book ) {
13365                 result = index;
13366
13367                 break;
13368             }
13369
13370             index += 2;
13371         }
13372     }
13373
13374     return result;
13375 }
13376
13377 void
13378 GetOutOfBookInfo (char * buf)
13379 {
13380     int oob[2];
13381     int i;
13382     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13383
13384     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13385     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13386
13387     *buf = '\0';
13388
13389     if( oob[0] >= 0 || oob[1] >= 0 ) {
13390         for( i=0; i<2; i++ ) {
13391             int idx = oob[i];
13392
13393             if( idx >= 0 ) {
13394                 if( i > 0 && oob[0] >= 0 ) {
13395                     strcat( buf, "   " );
13396                 }
13397
13398                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13399                 sprintf( buf+strlen(buf), "%s%.2f",
13400                     pvInfoList[idx].score >= 0 ? "+" : "",
13401                     pvInfoList[idx].score / 100.0 );
13402             }
13403         }
13404     }
13405 }
13406
13407 /* Save game in PGN style */
13408 static void
13409 SaveGamePGN2 (FILE *f)
13410 {
13411     int i, offset, linelen, newblock;
13412 //    char *movetext;
13413     char numtext[32];
13414     int movelen, numlen, blank;
13415     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13416
13417     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13418
13419     PrintPGNTags(f, &gameInfo);
13420
13421     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13422
13423     if (backwardMostMove > 0 || startedFromSetupPosition) {
13424         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13425         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13426         fprintf(f, "\n{--------------\n");
13427         PrintPosition(f, backwardMostMove);
13428         fprintf(f, "--------------}\n");
13429         free(fen);
13430     }
13431     else {
13432         /* [AS] Out of book annotation */
13433         if( appData.saveOutOfBookInfo ) {
13434             char buf[64];
13435
13436             GetOutOfBookInfo( buf );
13437
13438             if( buf[0] != '\0' ) {
13439                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13440             }
13441         }
13442
13443         fprintf(f, "\n");
13444     }
13445
13446     i = backwardMostMove;
13447     linelen = 0;
13448     newblock = TRUE;
13449
13450     while (i < forwardMostMove) {
13451         /* Print comments preceding this move */
13452         if (commentList[i] != NULL) {
13453             if (linelen > 0) fprintf(f, "\n");
13454             fprintf(f, "%s", commentList[i]);
13455             linelen = 0;
13456             newblock = TRUE;
13457         }
13458
13459         /* Format move number */
13460         if ((i % 2) == 0)
13461           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13462         else
13463           if (newblock)
13464             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13465           else
13466             numtext[0] = NULLCHAR;
13467
13468         numlen = strlen(numtext);
13469         newblock = FALSE;
13470
13471         /* Print move number */
13472         blank = linelen > 0 && numlen > 0;
13473         if (linelen + (blank ? 1 : 0) + numlen > 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", numtext);
13483         linelen += numlen;
13484
13485         /* Get move */
13486         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13487         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13488
13489         /* Print move */
13490         blank = linelen > 0 && movelen > 0;
13491         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13492             fprintf(f, "\n");
13493             linelen = 0;
13494             blank = 0;
13495         }
13496         if (blank) {
13497             fprintf(f, " ");
13498             linelen++;
13499         }
13500         fprintf(f, "%s", move_buffer);
13501         linelen += movelen;
13502
13503         /* [AS] Add PV info if present */
13504         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13505             /* [HGM] add time */
13506             char buf[MSG_SIZ]; int seconds;
13507
13508             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13509
13510             if( seconds <= 0)
13511               buf[0] = 0;
13512             else
13513               if( seconds < 30 )
13514                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13515               else
13516                 {
13517                   seconds = (seconds + 4)/10; // round to full seconds
13518                   if( seconds < 60 )
13519                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13520                   else
13521                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13522                 }
13523
13524             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13525                       pvInfoList[i].score >= 0 ? "+" : "",
13526                       pvInfoList[i].score / 100.0,
13527                       pvInfoList[i].depth,
13528                       buf );
13529
13530             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13531
13532             /* Print score/depth */
13533             blank = linelen > 0 && movelen > 0;
13534             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13535                 fprintf(f, "\n");
13536                 linelen = 0;
13537                 blank = 0;
13538             }
13539             if (blank) {
13540                 fprintf(f, " ");
13541                 linelen++;
13542             }
13543             fprintf(f, "%s", move_buffer);
13544             linelen += movelen;
13545         }
13546
13547         i++;
13548     }
13549
13550     /* Start a new line */
13551     if (linelen > 0) fprintf(f, "\n");
13552
13553     /* Print comments after last move */
13554     if (commentList[i] != NULL) {
13555         fprintf(f, "%s\n", commentList[i]);
13556     }
13557
13558     /* Print result */
13559     if (gameInfo.resultDetails != NULL &&
13560         gameInfo.resultDetails[0] != NULLCHAR) {
13561         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13562         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13563            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13564             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13565         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13566     } else {
13567         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13568     }
13569 }
13570
13571 /* Save game in PGN style and close the file */
13572 int
13573 SaveGamePGN (FILE *f)
13574 {
13575     SaveGamePGN2(f);
13576     fclose(f);
13577     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13578     return TRUE;
13579 }
13580
13581 /* Save game in old style and close the file */
13582 int
13583 SaveGameOldStyle (FILE *f)
13584 {
13585     int i, offset;
13586     time_t tm;
13587
13588     tm = time((time_t *) NULL);
13589
13590     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13591     PrintOpponents(f);
13592
13593     if (backwardMostMove > 0 || startedFromSetupPosition) {
13594         fprintf(f, "\n[--------------\n");
13595         PrintPosition(f, backwardMostMove);
13596         fprintf(f, "--------------]\n");
13597     } else {
13598         fprintf(f, "\n");
13599     }
13600
13601     i = backwardMostMove;
13602     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13603
13604     while (i < forwardMostMove) {
13605         if (commentList[i] != NULL) {
13606             fprintf(f, "[%s]\n", commentList[i]);
13607         }
13608
13609         if ((i % 2) == 1) {
13610             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13611             i++;
13612         } else {
13613             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13614             i++;
13615             if (commentList[i] != NULL) {
13616                 fprintf(f, "\n");
13617                 continue;
13618             }
13619             if (i >= forwardMostMove) {
13620                 fprintf(f, "\n");
13621                 break;
13622             }
13623             fprintf(f, "%s\n", parseList[i]);
13624             i++;
13625         }
13626     }
13627
13628     if (commentList[i] != NULL) {
13629         fprintf(f, "[%s]\n", commentList[i]);
13630     }
13631
13632     /* This isn't really the old style, but it's close enough */
13633     if (gameInfo.resultDetails != NULL &&
13634         gameInfo.resultDetails[0] != NULLCHAR) {
13635         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13636                 gameInfo.resultDetails);
13637     } else {
13638         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13639     }
13640
13641     fclose(f);
13642     return TRUE;
13643 }
13644
13645 /* Save the current game to open file f and close the file */
13646 int
13647 SaveGame (FILE *f, int dummy, char *dummy2)
13648 {
13649     if (gameMode == EditPosition) EditPositionDone(TRUE);
13650     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13651     if (appData.oldSaveStyle)
13652       return SaveGameOldStyle(f);
13653     else
13654       return SaveGamePGN(f);
13655 }
13656
13657 /* Save the current position to the given file */
13658 int
13659 SavePositionToFile (char *filename)
13660 {
13661     FILE *f;
13662     char buf[MSG_SIZ];
13663
13664     if (strcmp(filename, "-") == 0) {
13665         return SavePosition(stdout, 0, NULL);
13666     } else {
13667         f = fopen(filename, "a");
13668         if (f == NULL) {
13669             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13670             DisplayError(buf, errno);
13671             return FALSE;
13672         } else {
13673             safeStrCpy(buf, lastMsg, MSG_SIZ);
13674             DisplayMessage(_("Waiting for access to save file"), "");
13675             flock(fileno(f), LOCK_EX); // [HGM] lock
13676             DisplayMessage(_("Saving position"), "");
13677             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13678             SavePosition(f, 0, NULL);
13679             DisplayMessage(buf, "");
13680             return TRUE;
13681         }
13682     }
13683 }
13684
13685 /* Save the current position to the given open file and close the file */
13686 int
13687 SavePosition (FILE *f, int dummy, char *dummy2)
13688 {
13689     time_t tm;
13690     char *fen;
13691
13692     if (gameMode == EditPosition) EditPositionDone(TRUE);
13693     if (appData.oldSaveStyle) {
13694         tm = time((time_t *) NULL);
13695
13696         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13697         PrintOpponents(f);
13698         fprintf(f, "[--------------\n");
13699         PrintPosition(f, currentMove);
13700         fprintf(f, "--------------]\n");
13701     } else {
13702         fen = PositionToFEN(currentMove, NULL, 1);
13703         fprintf(f, "%s\n", fen);
13704         free(fen);
13705     }
13706     fclose(f);
13707     return TRUE;
13708 }
13709
13710 void
13711 ReloadCmailMsgEvent (int unregister)
13712 {
13713 #if !WIN32
13714     static char *inFilename = NULL;
13715     static char *outFilename;
13716     int i;
13717     struct stat inbuf, outbuf;
13718     int status;
13719
13720     /* Any registered moves are unregistered if unregister is set, */
13721     /* i.e. invoked by the signal handler */
13722     if (unregister) {
13723         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13724             cmailMoveRegistered[i] = FALSE;
13725             if (cmailCommentList[i] != NULL) {
13726                 free(cmailCommentList[i]);
13727                 cmailCommentList[i] = NULL;
13728             }
13729         }
13730         nCmailMovesRegistered = 0;
13731     }
13732
13733     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13734         cmailResult[i] = CMAIL_NOT_RESULT;
13735     }
13736     nCmailResults = 0;
13737
13738     if (inFilename == NULL) {
13739         /* Because the filenames are static they only get malloced once  */
13740         /* and they never get freed                                      */
13741         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13742         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13743
13744         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13745         sprintf(outFilename, "%s.out", appData.cmailGameName);
13746     }
13747
13748     status = stat(outFilename, &outbuf);
13749     if (status < 0) {
13750         cmailMailedMove = FALSE;
13751     } else {
13752         status = stat(inFilename, &inbuf);
13753         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13754     }
13755
13756     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13757        counts the games, notes how each one terminated, etc.
13758
13759        It would be nice to remove this kludge and instead gather all
13760        the information while building the game list.  (And to keep it
13761        in the game list nodes instead of having a bunch of fixed-size
13762        parallel arrays.)  Note this will require getting each game's
13763        termination from the PGN tags, as the game list builder does
13764        not process the game moves.  --mann
13765        */
13766     cmailMsgLoaded = TRUE;
13767     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13768
13769     /* Load first game in the file or popup game menu */
13770     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13771
13772 #endif /* !WIN32 */
13773     return;
13774 }
13775
13776 int
13777 RegisterMove ()
13778 {
13779     FILE *f;
13780     char string[MSG_SIZ];
13781
13782     if (   cmailMailedMove
13783         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13784         return TRUE;            /* Allow free viewing  */
13785     }
13786
13787     /* Unregister move to ensure that we don't leave RegisterMove        */
13788     /* with the move registered when the conditions for registering no   */
13789     /* longer hold                                                       */
13790     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13791         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13792         nCmailMovesRegistered --;
13793
13794         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13795           {
13796               free(cmailCommentList[lastLoadGameNumber - 1]);
13797               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13798           }
13799     }
13800
13801     if (cmailOldMove == -1) {
13802         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13803         return FALSE;
13804     }
13805
13806     if (currentMove > cmailOldMove + 1) {
13807         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13808         return FALSE;
13809     }
13810
13811     if (currentMove < cmailOldMove) {
13812         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13813         return FALSE;
13814     }
13815
13816     if (forwardMostMove > currentMove) {
13817         /* Silently truncate extra moves */
13818         TruncateGame();
13819     }
13820
13821     if (   (currentMove == cmailOldMove + 1)
13822         || (   (currentMove == cmailOldMove)
13823             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13824                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13825         if (gameInfo.result != GameUnfinished) {
13826             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13827         }
13828
13829         if (commentList[currentMove] != NULL) {
13830             cmailCommentList[lastLoadGameNumber - 1]
13831               = StrSave(commentList[currentMove]);
13832         }
13833         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13834
13835         if (appData.debugMode)
13836           fprintf(debugFP, "Saving %s for game %d\n",
13837                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13838
13839         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13840
13841         f = fopen(string, "w");
13842         if (appData.oldSaveStyle) {
13843             SaveGameOldStyle(f); /* also closes the file */
13844
13845             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13846             f = fopen(string, "w");
13847             SavePosition(f, 0, NULL); /* also closes the file */
13848         } else {
13849             fprintf(f, "{--------------\n");
13850             PrintPosition(f, currentMove);
13851             fprintf(f, "--------------}\n\n");
13852
13853             SaveGame(f, 0, NULL); /* also closes the file*/
13854         }
13855
13856         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13857         nCmailMovesRegistered ++;
13858     } else if (nCmailGames == 1) {
13859         DisplayError(_("You have not made a move yet"), 0);
13860         return FALSE;
13861     }
13862
13863     return TRUE;
13864 }
13865
13866 void
13867 MailMoveEvent ()
13868 {
13869 #if !WIN32
13870     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13871     FILE *commandOutput;
13872     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13873     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13874     int nBuffers;
13875     int i;
13876     int archived;
13877     char *arcDir;
13878
13879     if (! cmailMsgLoaded) {
13880         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13881         return;
13882     }
13883
13884     if (nCmailGames == nCmailResults) {
13885         DisplayError(_("No unfinished games"), 0);
13886         return;
13887     }
13888
13889 #if CMAIL_PROHIBIT_REMAIL
13890     if (cmailMailedMove) {
13891       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);
13892         DisplayError(msg, 0);
13893         return;
13894     }
13895 #endif
13896
13897     if (! (cmailMailedMove || RegisterMove())) return;
13898
13899     if (   cmailMailedMove
13900         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13901       snprintf(string, MSG_SIZ, partCommandString,
13902                appData.debugMode ? " -v" : "", appData.cmailGameName);
13903         commandOutput = popen(string, "r");
13904
13905         if (commandOutput == NULL) {
13906             DisplayError(_("Failed to invoke cmail"), 0);
13907         } else {
13908             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13909                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13910             }
13911             if (nBuffers > 1) {
13912                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13913                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13914                 nBytes = MSG_SIZ - 1;
13915             } else {
13916                 (void) memcpy(msg, buffer, nBytes);
13917             }
13918             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13919
13920             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13921                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13922
13923                 archived = TRUE;
13924                 for (i = 0; i < nCmailGames; i ++) {
13925                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13926                         archived = FALSE;
13927                     }
13928                 }
13929                 if (   archived
13930                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13931                         != NULL)) {
13932                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13933                            arcDir,
13934                            appData.cmailGameName,
13935                            gameInfo.date);
13936                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13937                     cmailMsgLoaded = FALSE;
13938                 }
13939             }
13940
13941             DisplayInformation(msg);
13942             pclose(commandOutput);
13943         }
13944     } else {
13945         if ((*cmailMsg) != '\0') {
13946             DisplayInformation(cmailMsg);
13947         }
13948     }
13949
13950     return;
13951 #endif /* !WIN32 */
13952 }
13953
13954 char *
13955 CmailMsg ()
13956 {
13957 #if WIN32
13958     return NULL;
13959 #else
13960     int  prependComma = 0;
13961     char number[5];
13962     char string[MSG_SIZ];       /* Space for game-list */
13963     int  i;
13964
13965     if (!cmailMsgLoaded) return "";
13966
13967     if (cmailMailedMove) {
13968       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13969     } else {
13970         /* Create a list of games left */
13971       snprintf(string, MSG_SIZ, "[");
13972         for (i = 0; i < nCmailGames; i ++) {
13973             if (! (   cmailMoveRegistered[i]
13974                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13975                 if (prependComma) {
13976                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13977                 } else {
13978                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13979                     prependComma = 1;
13980                 }
13981
13982                 strcat(string, number);
13983             }
13984         }
13985         strcat(string, "]");
13986
13987         if (nCmailMovesRegistered + nCmailResults == 0) {
13988             switch (nCmailGames) {
13989               case 1:
13990                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13991                 break;
13992
13993               case 2:
13994                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13995                 break;
13996
13997               default:
13998                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13999                          nCmailGames);
14000                 break;
14001             }
14002         } else {
14003             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14004               case 1:
14005                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14006                          string);
14007                 break;
14008
14009               case 0:
14010                 if (nCmailResults == nCmailGames) {
14011                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14012                 } else {
14013                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14014                 }
14015                 break;
14016
14017               default:
14018                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14019                          string);
14020             }
14021         }
14022     }
14023     return cmailMsg;
14024 #endif /* WIN32 */
14025 }
14026
14027 void
14028 ResetGameEvent ()
14029 {
14030     if (gameMode == Training)
14031       SetTrainingModeOff();
14032
14033     Reset(TRUE, TRUE);
14034     cmailMsgLoaded = FALSE;
14035     if (appData.icsActive) {
14036       SendToICS(ics_prefix);
14037       SendToICS("refresh\n");
14038     }
14039 }
14040
14041 void
14042 ExitEvent (int status)
14043 {
14044     exiting++;
14045     if (exiting > 2) {
14046       /* Give up on clean exit */
14047       exit(status);
14048     }
14049     if (exiting > 1) {
14050       /* Keep trying for clean exit */
14051       return;
14052     }
14053
14054     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14055     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14056
14057     if (telnetISR != NULL) {
14058       RemoveInputSource(telnetISR);
14059     }
14060     if (icsPR != NoProc) {
14061       DestroyChildProcess(icsPR, TRUE);
14062     }
14063
14064     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14065     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14066
14067     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14068     /* make sure this other one finishes before killing it!                  */
14069     if(endingGame) { int count = 0;
14070         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14071         while(endingGame && count++ < 10) DoSleep(1);
14072         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14073     }
14074
14075     /* Kill off chess programs */
14076     if (first.pr != NoProc) {
14077         ExitAnalyzeMode();
14078
14079         DoSleep( appData.delayBeforeQuit );
14080         SendToProgram("quit\n", &first);
14081         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14082     }
14083     if (second.pr != NoProc) {
14084         DoSleep( appData.delayBeforeQuit );
14085         SendToProgram("quit\n", &second);
14086         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14087     }
14088     if (first.isr != NULL) {
14089         RemoveInputSource(first.isr);
14090     }
14091     if (second.isr != NULL) {
14092         RemoveInputSource(second.isr);
14093     }
14094
14095     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14096     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14097
14098     ShutDownFrontEnd();
14099     exit(status);
14100 }
14101
14102 void
14103 PauseEngine (ChessProgramState *cps)
14104 {
14105     SendToProgram("pause\n", cps);
14106     cps->pause = 2;
14107 }
14108
14109 void
14110 UnPauseEngine (ChessProgramState *cps)
14111 {
14112     SendToProgram("resume\n", cps);
14113     cps->pause = 1;
14114 }
14115
14116 void
14117 PauseEvent ()
14118 {
14119     if (appData.debugMode)
14120         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14121     if (pausing) {
14122         pausing = FALSE;
14123         ModeHighlight();
14124         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14125             StartClocks();
14126             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14127                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14128                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14129             }
14130             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14131             HandleMachineMove(stashedInputMove, stalledEngine);
14132             stalledEngine = NULL;
14133             return;
14134         }
14135         if (gameMode == MachinePlaysWhite ||
14136             gameMode == TwoMachinesPlay   ||
14137             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14138             if(first.pause)  UnPauseEngine(&first);
14139             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14140             if(second.pause) UnPauseEngine(&second);
14141             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14142             StartClocks();
14143         } else {
14144             DisplayBothClocks();
14145         }
14146         if (gameMode == PlayFromGameFile) {
14147             if (appData.timeDelay >= 0)
14148                 AutoPlayGameLoop();
14149         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14150             Reset(FALSE, TRUE);
14151             SendToICS(ics_prefix);
14152             SendToICS("refresh\n");
14153         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14154             ForwardInner(forwardMostMove);
14155         }
14156         pauseExamInvalid = FALSE;
14157     } else {
14158         switch (gameMode) {
14159           default:
14160             return;
14161           case IcsExamining:
14162             pauseExamForwardMostMove = forwardMostMove;
14163             pauseExamInvalid = FALSE;
14164             /* fall through */
14165           case IcsObserving:
14166           case IcsPlayingWhite:
14167           case IcsPlayingBlack:
14168             pausing = TRUE;
14169             ModeHighlight();
14170             return;
14171           case PlayFromGameFile:
14172             (void) StopLoadGameTimer();
14173             pausing = TRUE;
14174             ModeHighlight();
14175             break;
14176           case BeginningOfGame:
14177             if (appData.icsActive) return;
14178             /* else fall through */
14179           case MachinePlaysWhite:
14180           case MachinePlaysBlack:
14181           case TwoMachinesPlay:
14182             if (forwardMostMove == 0)
14183               return;           /* don't pause if no one has moved */
14184             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14185                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14186                 if(onMove->pause) {           // thinking engine can be paused
14187                     PauseEngine(onMove);      // do it
14188                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14189                         PauseEngine(onMove->other);
14190                     else
14191                         SendToProgram("easy\n", onMove->other);
14192                     StopClocks();
14193                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14194             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14195                 if(first.pause) {
14196                     PauseEngine(&first);
14197                     StopClocks();
14198                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14199             } else { // human on move, pause pondering by either method
14200                 if(first.pause)
14201                     PauseEngine(&first);
14202                 else if(appData.ponderNextMove)
14203                     SendToProgram("easy\n", &first);
14204                 StopClocks();
14205             }
14206             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14207           case AnalyzeMode:
14208             pausing = TRUE;
14209             ModeHighlight();
14210             break;
14211         }
14212     }
14213 }
14214
14215 void
14216 EditCommentEvent ()
14217 {
14218     char title[MSG_SIZ];
14219
14220     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14221       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14222     } else {
14223       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14224                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14225                parseList[currentMove - 1]);
14226     }
14227
14228     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14229 }
14230
14231
14232 void
14233 EditTagsEvent ()
14234 {
14235     char *tags = PGNTags(&gameInfo);
14236     bookUp = FALSE;
14237     EditTagsPopUp(tags, NULL);
14238     free(tags);
14239 }
14240
14241 void
14242 ToggleSecond ()
14243 {
14244   if(second.analyzing) {
14245     SendToProgram("exit\n", &second);
14246     second.analyzing = FALSE;
14247   } else {
14248     if (second.pr == NoProc) StartChessProgram(&second);
14249     InitChessProgram(&second, FALSE);
14250     FeedMovesToProgram(&second, currentMove);
14251
14252     SendToProgram("analyze\n", &second);
14253     second.analyzing = TRUE;
14254   }
14255 }
14256
14257 /* Toggle ShowThinking */
14258 void
14259 ToggleShowThinking()
14260 {
14261   appData.showThinking = !appData.showThinking;
14262   ShowThinkingEvent();
14263 }
14264
14265 int
14266 AnalyzeModeEvent ()
14267 {
14268     char buf[MSG_SIZ];
14269
14270     if (!first.analysisSupport) {
14271       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14272       DisplayError(buf, 0);
14273       return 0;
14274     }
14275     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14276     if (appData.icsActive) {
14277         if (gameMode != IcsObserving) {
14278           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14279             DisplayError(buf, 0);
14280             /* secure check */
14281             if (appData.icsEngineAnalyze) {
14282                 if (appData.debugMode)
14283                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14284                 ExitAnalyzeMode();
14285                 ModeHighlight();
14286             }
14287             return 0;
14288         }
14289         /* if enable, user wants to disable icsEngineAnalyze */
14290         if (appData.icsEngineAnalyze) {
14291                 ExitAnalyzeMode();
14292                 ModeHighlight();
14293                 return 0;
14294         }
14295         appData.icsEngineAnalyze = TRUE;
14296         if (appData.debugMode)
14297             fprintf(debugFP, "ICS engine analyze starting... \n");
14298     }
14299
14300     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14301     if (appData.noChessProgram || gameMode == AnalyzeMode)
14302       return 0;
14303
14304     if (gameMode != AnalyzeFile) {
14305         if (!appData.icsEngineAnalyze) {
14306                EditGameEvent();
14307                if (gameMode != EditGame) return 0;
14308         }
14309         if (!appData.showThinking) ToggleShowThinking();
14310         ResurrectChessProgram();
14311         SendToProgram("analyze\n", &first);
14312         first.analyzing = TRUE;
14313         /*first.maybeThinking = TRUE;*/
14314         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14315         EngineOutputPopUp();
14316     }
14317     if (!appData.icsEngineAnalyze) {
14318         gameMode = AnalyzeMode;
14319         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14320     }
14321     pausing = FALSE;
14322     ModeHighlight();
14323     SetGameInfo();
14324
14325     StartAnalysisClock();
14326     GetTimeMark(&lastNodeCountTime);
14327     lastNodeCount = 0;
14328     return 1;
14329 }
14330
14331 void
14332 AnalyzeFileEvent ()
14333 {
14334     if (appData.noChessProgram || gameMode == AnalyzeFile)
14335       return;
14336
14337     if (!first.analysisSupport) {
14338       char buf[MSG_SIZ];
14339       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14340       DisplayError(buf, 0);
14341       return;
14342     }
14343
14344     if (gameMode != AnalyzeMode) {
14345         keepInfo = 1; // mere annotating should not alter PGN tags
14346         EditGameEvent();
14347         keepInfo = 0;
14348         if (gameMode != EditGame) return;
14349         if (!appData.showThinking) ToggleShowThinking();
14350         ResurrectChessProgram();
14351         SendToProgram("analyze\n", &first);
14352         first.analyzing = TRUE;
14353         /*first.maybeThinking = TRUE;*/
14354         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14355         EngineOutputPopUp();
14356     }
14357     gameMode = AnalyzeFile;
14358     pausing = FALSE;
14359     ModeHighlight();
14360
14361     StartAnalysisClock();
14362     GetTimeMark(&lastNodeCountTime);
14363     lastNodeCount = 0;
14364     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14365     AnalysisPeriodicEvent(1);
14366 }
14367
14368 void
14369 MachineWhiteEvent ()
14370 {
14371     char buf[MSG_SIZ];
14372     char *bookHit = NULL;
14373
14374     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14375       return;
14376
14377
14378     if (gameMode == PlayFromGameFile ||
14379         gameMode == TwoMachinesPlay  ||
14380         gameMode == Training         ||
14381         gameMode == AnalyzeMode      ||
14382         gameMode == EndOfGame)
14383         EditGameEvent();
14384
14385     if (gameMode == EditPosition)
14386         EditPositionDone(TRUE);
14387
14388     if (!WhiteOnMove(currentMove)) {
14389         DisplayError(_("It is not White's turn"), 0);
14390         return;
14391     }
14392
14393     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14394       ExitAnalyzeMode();
14395
14396     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14397         gameMode == AnalyzeFile)
14398         TruncateGame();
14399
14400     ResurrectChessProgram();    /* in case it isn't running */
14401     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14402         gameMode = MachinePlaysWhite;
14403         ResetClocks();
14404     } else
14405     gameMode = MachinePlaysWhite;
14406     pausing = FALSE;
14407     ModeHighlight();
14408     SetGameInfo();
14409     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14410     DisplayTitle(buf);
14411     if (first.sendName) {
14412       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14413       SendToProgram(buf, &first);
14414     }
14415     if (first.sendTime) {
14416       if (first.useColors) {
14417         SendToProgram("black\n", &first); /*gnu kludge*/
14418       }
14419       SendTimeRemaining(&first, TRUE);
14420     }
14421     if (first.useColors) {
14422       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14423     }
14424     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14425     SetMachineThinkingEnables();
14426     first.maybeThinking = TRUE;
14427     StartClocks();
14428     firstMove = FALSE;
14429
14430     if (appData.autoFlipView && !flipView) {
14431       flipView = !flipView;
14432       DrawPosition(FALSE, NULL);
14433       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14434     }
14435
14436     if(bookHit) { // [HGM] book: simulate book reply
14437         static char bookMove[MSG_SIZ]; // a bit generous?
14438
14439         programStats.nodes = programStats.depth = programStats.time =
14440         programStats.score = programStats.got_only_move = 0;
14441         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14442
14443         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14444         strcat(bookMove, bookHit);
14445         HandleMachineMove(bookMove, &first);
14446     }
14447 }
14448
14449 void
14450 MachineBlackEvent ()
14451 {
14452   char buf[MSG_SIZ];
14453   char *bookHit = NULL;
14454
14455     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14456         return;
14457
14458
14459     if (gameMode == PlayFromGameFile ||
14460         gameMode == TwoMachinesPlay  ||
14461         gameMode == Training         ||
14462         gameMode == AnalyzeMode      ||
14463         gameMode == EndOfGame)
14464         EditGameEvent();
14465
14466     if (gameMode == EditPosition)
14467         EditPositionDone(TRUE);
14468
14469     if (WhiteOnMove(currentMove)) {
14470         DisplayError(_("It is not Black's turn"), 0);
14471         return;
14472     }
14473
14474     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14475       ExitAnalyzeMode();
14476
14477     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14478         gameMode == AnalyzeFile)
14479         TruncateGame();
14480
14481     ResurrectChessProgram();    /* in case it isn't running */
14482     gameMode = MachinePlaysBlack;
14483     pausing = FALSE;
14484     ModeHighlight();
14485     SetGameInfo();
14486     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14487     DisplayTitle(buf);
14488     if (first.sendName) {
14489       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14490       SendToProgram(buf, &first);
14491     }
14492     if (first.sendTime) {
14493       if (first.useColors) {
14494         SendToProgram("white\n", &first); /*gnu kludge*/
14495       }
14496       SendTimeRemaining(&first, FALSE);
14497     }
14498     if (first.useColors) {
14499       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14500     }
14501     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14502     SetMachineThinkingEnables();
14503     first.maybeThinking = TRUE;
14504     StartClocks();
14505
14506     if (appData.autoFlipView && flipView) {
14507       flipView = !flipView;
14508       DrawPosition(FALSE, NULL);
14509       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14510     }
14511     if(bookHit) { // [HGM] book: simulate book reply
14512         static char bookMove[MSG_SIZ]; // a bit generous?
14513
14514         programStats.nodes = programStats.depth = programStats.time =
14515         programStats.score = programStats.got_only_move = 0;
14516         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14517
14518         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14519         strcat(bookMove, bookHit);
14520         HandleMachineMove(bookMove, &first);
14521     }
14522 }
14523
14524
14525 void
14526 DisplayTwoMachinesTitle ()
14527 {
14528     char buf[MSG_SIZ];
14529     if (appData.matchGames > 0) {
14530         if(appData.tourneyFile[0]) {
14531           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14532                    gameInfo.white, _("vs."), gameInfo.black,
14533                    nextGame+1, appData.matchGames+1,
14534                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14535         } else
14536         if (first.twoMachinesColor[0] == 'w') {
14537           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14538                    gameInfo.white, _("vs."),  gameInfo.black,
14539                    first.matchWins, second.matchWins,
14540                    matchGame - 1 - (first.matchWins + second.matchWins));
14541         } else {
14542           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14543                    gameInfo.white, _("vs."), gameInfo.black,
14544                    second.matchWins, first.matchWins,
14545                    matchGame - 1 - (first.matchWins + second.matchWins));
14546         }
14547     } else {
14548       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14549     }
14550     DisplayTitle(buf);
14551 }
14552
14553 void
14554 SettingsMenuIfReady ()
14555 {
14556   if (second.lastPing != second.lastPong) {
14557     DisplayMessage("", _("Waiting for second chess program"));
14558     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14559     return;
14560   }
14561   ThawUI();
14562   DisplayMessage("", "");
14563   SettingsPopUp(&second);
14564 }
14565
14566 int
14567 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14568 {
14569     char buf[MSG_SIZ];
14570     if (cps->pr == NoProc) {
14571         StartChessProgram(cps);
14572         if (cps->protocolVersion == 1) {
14573           retry();
14574           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14575         } else {
14576           /* kludge: allow timeout for initial "feature" command */
14577           if(retry != TwoMachinesEventIfReady) FreezeUI();
14578           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14579           DisplayMessage("", buf);
14580           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14581         }
14582         return 1;
14583     }
14584     return 0;
14585 }
14586
14587 void
14588 TwoMachinesEvent P((void))
14589 {
14590     int i;
14591     char buf[MSG_SIZ];
14592     ChessProgramState *onmove;
14593     char *bookHit = NULL;
14594     static int stalling = 0;
14595     TimeMark now;
14596     long wait;
14597
14598     if (appData.noChessProgram) return;
14599
14600     switch (gameMode) {
14601       case TwoMachinesPlay:
14602         return;
14603       case MachinePlaysWhite:
14604       case MachinePlaysBlack:
14605         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14606             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14607             return;
14608         }
14609         /* fall through */
14610       case BeginningOfGame:
14611       case PlayFromGameFile:
14612       case EndOfGame:
14613         EditGameEvent();
14614         if (gameMode != EditGame) return;
14615         break;
14616       case EditPosition:
14617         EditPositionDone(TRUE);
14618         break;
14619       case AnalyzeMode:
14620       case AnalyzeFile:
14621         ExitAnalyzeMode();
14622         break;
14623       case EditGame:
14624       default:
14625         break;
14626     }
14627
14628 //    forwardMostMove = currentMove;
14629     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14630     startingEngine = TRUE;
14631
14632     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14633
14634     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14635     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14636       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14637       return;
14638     }
14639     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14640
14641     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14642                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14643         startingEngine = FALSE;
14644         DisplayError("second engine does not play this", 0);
14645         return;
14646     }
14647
14648     if(!stalling) {
14649       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14650       SendToProgram("force\n", &second);
14651       stalling = 1;
14652       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14653       return;
14654     }
14655     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14656     if(appData.matchPause>10000 || appData.matchPause<10)
14657                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14658     wait = SubtractTimeMarks(&now, &pauseStart);
14659     if(wait < appData.matchPause) {
14660         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14661         return;
14662     }
14663     // we are now committed to starting the game
14664     stalling = 0;
14665     DisplayMessage("", "");
14666     if (startedFromSetupPosition) {
14667         SendBoard(&second, backwardMostMove);
14668     if (appData.debugMode) {
14669         fprintf(debugFP, "Two Machines\n");
14670     }
14671     }
14672     for (i = backwardMostMove; i < forwardMostMove; i++) {
14673         SendMoveToProgram(i, &second);
14674     }
14675
14676     gameMode = TwoMachinesPlay;
14677     pausing = startingEngine = FALSE;
14678     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14679     SetGameInfo();
14680     DisplayTwoMachinesTitle();
14681     firstMove = TRUE;
14682     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14683         onmove = &first;
14684     } else {
14685         onmove = &second;
14686     }
14687     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14688     SendToProgram(first.computerString, &first);
14689     if (first.sendName) {
14690       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14691       SendToProgram(buf, &first);
14692     }
14693     SendToProgram(second.computerString, &second);
14694     if (second.sendName) {
14695       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14696       SendToProgram(buf, &second);
14697     }
14698
14699     ResetClocks();
14700     if (!first.sendTime || !second.sendTime) {
14701         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14702         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14703     }
14704     if (onmove->sendTime) {
14705       if (onmove->useColors) {
14706         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14707       }
14708       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14709     }
14710     if (onmove->useColors) {
14711       SendToProgram(onmove->twoMachinesColor, onmove);
14712     }
14713     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14714 //    SendToProgram("go\n", onmove);
14715     onmove->maybeThinking = TRUE;
14716     SetMachineThinkingEnables();
14717
14718     StartClocks();
14719
14720     if(bookHit) { // [HGM] book: simulate book reply
14721         static char bookMove[MSG_SIZ]; // a bit generous?
14722
14723         programStats.nodes = programStats.depth = programStats.time =
14724         programStats.score = programStats.got_only_move = 0;
14725         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14726
14727         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14728         strcat(bookMove, bookHit);
14729         savedMessage = bookMove; // args for deferred call
14730         savedState = onmove;
14731         ScheduleDelayedEvent(DeferredBookMove, 1);
14732     }
14733 }
14734
14735 void
14736 TrainingEvent ()
14737 {
14738     if (gameMode == Training) {
14739       SetTrainingModeOff();
14740       gameMode = PlayFromGameFile;
14741       DisplayMessage("", _("Training mode off"));
14742     } else {
14743       gameMode = Training;
14744       animateTraining = appData.animate;
14745
14746       /* make sure we are not already at the end of the game */
14747       if (currentMove < forwardMostMove) {
14748         SetTrainingModeOn();
14749         DisplayMessage("", _("Training mode on"));
14750       } else {
14751         gameMode = PlayFromGameFile;
14752         DisplayError(_("Already at end of game"), 0);
14753       }
14754     }
14755     ModeHighlight();
14756 }
14757
14758 void
14759 IcsClientEvent ()
14760 {
14761     if (!appData.icsActive) return;
14762     switch (gameMode) {
14763       case IcsPlayingWhite:
14764       case IcsPlayingBlack:
14765       case IcsObserving:
14766       case IcsIdle:
14767       case BeginningOfGame:
14768       case IcsExamining:
14769         return;
14770
14771       case EditGame:
14772         break;
14773
14774       case EditPosition:
14775         EditPositionDone(TRUE);
14776         break;
14777
14778       case AnalyzeMode:
14779       case AnalyzeFile:
14780         ExitAnalyzeMode();
14781         break;
14782
14783       default:
14784         EditGameEvent();
14785         break;
14786     }
14787
14788     gameMode = IcsIdle;
14789     ModeHighlight();
14790     return;
14791 }
14792
14793 void
14794 EditGameEvent ()
14795 {
14796     int i;
14797
14798     switch (gameMode) {
14799       case Training:
14800         SetTrainingModeOff();
14801         break;
14802       case MachinePlaysWhite:
14803       case MachinePlaysBlack:
14804       case BeginningOfGame:
14805         SendToProgram("force\n", &first);
14806         SetUserThinkingEnables();
14807         break;
14808       case PlayFromGameFile:
14809         (void) StopLoadGameTimer();
14810         if (gameFileFP != NULL) {
14811             gameFileFP = NULL;
14812         }
14813         break;
14814       case EditPosition:
14815         EditPositionDone(TRUE);
14816         break;
14817       case AnalyzeMode:
14818       case AnalyzeFile:
14819         ExitAnalyzeMode();
14820         SendToProgram("force\n", &first);
14821         break;
14822       case TwoMachinesPlay:
14823         GameEnds(EndOfFile, NULL, GE_PLAYER);
14824         ResurrectChessProgram();
14825         SetUserThinkingEnables();
14826         break;
14827       case EndOfGame:
14828         ResurrectChessProgram();
14829         break;
14830       case IcsPlayingBlack:
14831       case IcsPlayingWhite:
14832         DisplayError(_("Warning: You are still playing a game"), 0);
14833         break;
14834       case IcsObserving:
14835         DisplayError(_("Warning: You are still observing a game"), 0);
14836         break;
14837       case IcsExamining:
14838         DisplayError(_("Warning: You are still examining a game"), 0);
14839         break;
14840       case IcsIdle:
14841         break;
14842       case EditGame:
14843       default:
14844         return;
14845     }
14846
14847     pausing = FALSE;
14848     StopClocks();
14849     first.offeredDraw = second.offeredDraw = 0;
14850
14851     if (gameMode == PlayFromGameFile) {
14852         whiteTimeRemaining = timeRemaining[0][currentMove];
14853         blackTimeRemaining = timeRemaining[1][currentMove];
14854         DisplayTitle("");
14855     }
14856
14857     if (gameMode == MachinePlaysWhite ||
14858         gameMode == MachinePlaysBlack ||
14859         gameMode == TwoMachinesPlay ||
14860         gameMode == EndOfGame) {
14861         i = forwardMostMove;
14862         while (i > currentMove) {
14863             SendToProgram("undo\n", &first);
14864             i--;
14865         }
14866         if(!adjustedClock) {
14867         whiteTimeRemaining = timeRemaining[0][currentMove];
14868         blackTimeRemaining = timeRemaining[1][currentMove];
14869         DisplayBothClocks();
14870         }
14871         if (whiteFlag || blackFlag) {
14872             whiteFlag = blackFlag = 0;
14873         }
14874         DisplayTitle("");
14875     }
14876
14877     gameMode = EditGame;
14878     ModeHighlight();
14879     SetGameInfo();
14880 }
14881
14882
14883 void
14884 EditPositionEvent ()
14885 {
14886     if (gameMode == EditPosition) {
14887         EditGameEvent();
14888         return;
14889     }
14890
14891     EditGameEvent();
14892     if (gameMode != EditGame) return;
14893
14894     gameMode = EditPosition;
14895     ModeHighlight();
14896     SetGameInfo();
14897     if (currentMove > 0)
14898       CopyBoard(boards[0], boards[currentMove]);
14899
14900     blackPlaysFirst = !WhiteOnMove(currentMove);
14901     ResetClocks();
14902     currentMove = forwardMostMove = backwardMostMove = 0;
14903     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14904     DisplayMove(-1);
14905     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14906 }
14907
14908 void
14909 ExitAnalyzeMode ()
14910 {
14911     /* [DM] icsEngineAnalyze - possible call from other functions */
14912     if (appData.icsEngineAnalyze) {
14913         appData.icsEngineAnalyze = FALSE;
14914
14915         DisplayMessage("",_("Close ICS engine analyze..."));
14916     }
14917     if (first.analysisSupport && first.analyzing) {
14918       SendToBoth("exit\n");
14919       first.analyzing = second.analyzing = FALSE;
14920     }
14921     thinkOutput[0] = NULLCHAR;
14922 }
14923
14924 void
14925 EditPositionDone (Boolean fakeRights)
14926 {
14927     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14928
14929     startedFromSetupPosition = TRUE;
14930     InitChessProgram(&first, FALSE);
14931     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14932       boards[0][EP_STATUS] = EP_NONE;
14933       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14934       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14935         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14936         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14937       } else boards[0][CASTLING][2] = NoRights;
14938       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14939         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14940         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14941       } else boards[0][CASTLING][5] = NoRights;
14942       if(gameInfo.variant == VariantSChess) {
14943         int i;
14944         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14945           boards[0][VIRGIN][i] = 0;
14946           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14947           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14948         }
14949       }
14950     }
14951     SendToProgram("force\n", &first);
14952     if (blackPlaysFirst) {
14953         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14954         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14955         currentMove = forwardMostMove = backwardMostMove = 1;
14956         CopyBoard(boards[1], boards[0]);
14957     } else {
14958         currentMove = forwardMostMove = backwardMostMove = 0;
14959     }
14960     SendBoard(&first, forwardMostMove);
14961     if (appData.debugMode) {
14962         fprintf(debugFP, "EditPosDone\n");
14963     }
14964     DisplayTitle("");
14965     DisplayMessage("", "");
14966     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14967     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14968     gameMode = EditGame;
14969     ModeHighlight();
14970     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14971     ClearHighlights(); /* [AS] */
14972 }
14973
14974 /* Pause for `ms' milliseconds */
14975 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14976 void
14977 TimeDelay (long ms)
14978 {
14979     TimeMark m1, m2;
14980
14981     GetTimeMark(&m1);
14982     do {
14983         GetTimeMark(&m2);
14984     } while (SubtractTimeMarks(&m2, &m1) < ms);
14985 }
14986
14987 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14988 void
14989 SendMultiLineToICS (char *buf)
14990 {
14991     char temp[MSG_SIZ+1], *p;
14992     int len;
14993
14994     len = strlen(buf);
14995     if (len > MSG_SIZ)
14996       len = MSG_SIZ;
14997
14998     strncpy(temp, buf, len);
14999     temp[len] = 0;
15000
15001     p = temp;
15002     while (*p) {
15003         if (*p == '\n' || *p == '\r')
15004           *p = ' ';
15005         ++p;
15006     }
15007
15008     strcat(temp, "\n");
15009     SendToICS(temp);
15010     SendToPlayer(temp, strlen(temp));
15011 }
15012
15013 void
15014 SetWhiteToPlayEvent ()
15015 {
15016     if (gameMode == EditPosition) {
15017         blackPlaysFirst = FALSE;
15018         DisplayBothClocks();    /* works because currentMove is 0 */
15019     } else if (gameMode == IcsExamining) {
15020         SendToICS(ics_prefix);
15021         SendToICS("tomove white\n");
15022     }
15023 }
15024
15025 void
15026 SetBlackToPlayEvent ()
15027 {
15028     if (gameMode == EditPosition) {
15029         blackPlaysFirst = TRUE;
15030         currentMove = 1;        /* kludge */
15031         DisplayBothClocks();
15032         currentMove = 0;
15033     } else if (gameMode == IcsExamining) {
15034         SendToICS(ics_prefix);
15035         SendToICS("tomove black\n");
15036     }
15037 }
15038
15039 void
15040 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15041 {
15042     char buf[MSG_SIZ];
15043     ChessSquare piece = boards[0][y][x];
15044     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15045     static int lastVariant;
15046
15047     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15048
15049     switch (selection) {
15050       case ClearBoard:
15051         CopyBoard(currentBoard, boards[0]);
15052         CopyBoard(menuBoard, initialPosition);
15053         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15054             SendToICS(ics_prefix);
15055             SendToICS("bsetup clear\n");
15056         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15057             SendToICS(ics_prefix);
15058             SendToICS("clearboard\n");
15059         } else {
15060             int nonEmpty = 0;
15061             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15062                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15063                 for (y = 0; y < BOARD_HEIGHT; y++) {
15064                     if (gameMode == IcsExamining) {
15065                         if (boards[currentMove][y][x] != EmptySquare) {
15066                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15067                                     AAA + x, ONE + y);
15068                             SendToICS(buf);
15069                         }
15070                     } else {
15071                         if(boards[0][y][x] != p) nonEmpty++;
15072                         boards[0][y][x] = p;
15073                     }
15074                 }
15075             }
15076             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15077                 int r;
15078                 for(r = 0; r < BOARD_HEIGHT; r++) {
15079                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15080                     ChessSquare p = menuBoard[r][x];
15081                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15082                   }
15083                 }
15084                 DisplayMessage("Clicking clock again restores position", "");
15085                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15086                 if(!nonEmpty) { // asked to clear an empty board
15087                     CopyBoard(boards[0], menuBoard);
15088                 } else
15089                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15090                     CopyBoard(boards[0], initialPosition);
15091                 } else
15092                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15093                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15094                     CopyBoard(boards[0], erasedBoard);
15095                 } else
15096                     CopyBoard(erasedBoard, currentBoard);
15097
15098             }
15099         }
15100         if (gameMode == EditPosition) {
15101             DrawPosition(FALSE, boards[0]);
15102         }
15103         break;
15104
15105       case WhitePlay:
15106         SetWhiteToPlayEvent();
15107         break;
15108
15109       case BlackPlay:
15110         SetBlackToPlayEvent();
15111         break;
15112
15113       case EmptySquare:
15114         if (gameMode == IcsExamining) {
15115             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15116             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15117             SendToICS(buf);
15118         } else {
15119             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15120                 if(x == BOARD_LEFT-2) {
15121                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15122                     boards[0][y][1] = 0;
15123                 } else
15124                 if(x == BOARD_RGHT+1) {
15125                     if(y >= gameInfo.holdingsSize) break;
15126                     boards[0][y][BOARD_WIDTH-2] = 0;
15127                 } else break;
15128             }
15129             boards[0][y][x] = EmptySquare;
15130             DrawPosition(FALSE, boards[0]);
15131         }
15132         break;
15133
15134       case PromotePiece:
15135         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15136            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15137             selection = (ChessSquare) (PROMOTED piece);
15138         } else if(piece == EmptySquare) selection = WhiteSilver;
15139         else selection = (ChessSquare)((int)piece - 1);
15140         goto defaultlabel;
15141
15142       case DemotePiece:
15143         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15144            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15145             selection = (ChessSquare) (DEMOTED piece);
15146         } else if(piece == EmptySquare) selection = BlackSilver;
15147         else selection = (ChessSquare)((int)piece + 1);
15148         goto defaultlabel;
15149
15150       case WhiteQueen:
15151       case BlackQueen:
15152         if(gameInfo.variant == VariantShatranj ||
15153            gameInfo.variant == VariantXiangqi  ||
15154            gameInfo.variant == VariantCourier  ||
15155            gameInfo.variant == VariantASEAN    ||
15156            gameInfo.variant == VariantMakruk     )
15157             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15158         goto defaultlabel;
15159
15160       case WhiteKing:
15161       case BlackKing:
15162         if(gameInfo.variant == VariantXiangqi)
15163             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15164         if(gameInfo.variant == VariantKnightmate)
15165             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15166       default:
15167         defaultlabel:
15168         if (gameMode == IcsExamining) {
15169             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15170             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15171                      PieceToChar(selection), AAA + x, ONE + y);
15172             SendToICS(buf);
15173         } else {
15174             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15175                 int n;
15176                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15177                     n = PieceToNumber(selection - BlackPawn);
15178                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15179                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15180                     boards[0][BOARD_HEIGHT-1-n][1]++;
15181                 } else
15182                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15183                     n = PieceToNumber(selection);
15184                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15185                     boards[0][n][BOARD_WIDTH-1] = selection;
15186                     boards[0][n][BOARD_WIDTH-2]++;
15187                 }
15188             } else
15189             boards[0][y][x] = selection;
15190             DrawPosition(TRUE, boards[0]);
15191             ClearHighlights();
15192             fromX = fromY = -1;
15193         }
15194         break;
15195     }
15196 }
15197
15198
15199 void
15200 DropMenuEvent (ChessSquare selection, int x, int y)
15201 {
15202     ChessMove moveType;
15203
15204     switch (gameMode) {
15205       case IcsPlayingWhite:
15206       case MachinePlaysBlack:
15207         if (!WhiteOnMove(currentMove)) {
15208             DisplayMoveError(_("It is Black's turn"));
15209             return;
15210         }
15211         moveType = WhiteDrop;
15212         break;
15213       case IcsPlayingBlack:
15214       case MachinePlaysWhite:
15215         if (WhiteOnMove(currentMove)) {
15216             DisplayMoveError(_("It is White's turn"));
15217             return;
15218         }
15219         moveType = BlackDrop;
15220         break;
15221       case EditGame:
15222         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15223         break;
15224       default:
15225         return;
15226     }
15227
15228     if (moveType == BlackDrop && selection < BlackPawn) {
15229       selection = (ChessSquare) ((int) selection
15230                                  + (int) BlackPawn - (int) WhitePawn);
15231     }
15232     if (boards[currentMove][y][x] != EmptySquare) {
15233         DisplayMoveError(_("That square is occupied"));
15234         return;
15235     }
15236
15237     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15238 }
15239
15240 void
15241 AcceptEvent ()
15242 {
15243     /* Accept a pending offer of any kind from opponent */
15244
15245     if (appData.icsActive) {
15246         SendToICS(ics_prefix);
15247         SendToICS("accept\n");
15248     } else if (cmailMsgLoaded) {
15249         if (currentMove == cmailOldMove &&
15250             commentList[cmailOldMove] != NULL &&
15251             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15252                    "Black offers a draw" : "White offers a draw")) {
15253             TruncateGame();
15254             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15255             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15256         } else {
15257             DisplayError(_("There is no pending offer on this move"), 0);
15258             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15259         }
15260     } else {
15261         /* Not used for offers from chess program */
15262     }
15263 }
15264
15265 void
15266 DeclineEvent ()
15267 {
15268     /* Decline a pending offer of any kind from opponent */
15269
15270     if (appData.icsActive) {
15271         SendToICS(ics_prefix);
15272         SendToICS("decline\n");
15273     } else if (cmailMsgLoaded) {
15274         if (currentMove == cmailOldMove &&
15275             commentList[cmailOldMove] != NULL &&
15276             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15277                    "Black offers a draw" : "White offers a draw")) {
15278 #ifdef NOTDEF
15279             AppendComment(cmailOldMove, "Draw declined", TRUE);
15280             DisplayComment(cmailOldMove - 1, "Draw declined");
15281 #endif /*NOTDEF*/
15282         } else {
15283             DisplayError(_("There is no pending offer on this move"), 0);
15284         }
15285     } else {
15286         /* Not used for offers from chess program */
15287     }
15288 }
15289
15290 void
15291 RematchEvent ()
15292 {
15293     /* Issue ICS rematch command */
15294     if (appData.icsActive) {
15295         SendToICS(ics_prefix);
15296         SendToICS("rematch\n");
15297     }
15298 }
15299
15300 void
15301 CallFlagEvent ()
15302 {
15303     /* Call your opponent's flag (claim a win on time) */
15304     if (appData.icsActive) {
15305         SendToICS(ics_prefix);
15306         SendToICS("flag\n");
15307     } else {
15308         switch (gameMode) {
15309           default:
15310             return;
15311           case MachinePlaysWhite:
15312             if (whiteFlag) {
15313                 if (blackFlag)
15314                   GameEnds(GameIsDrawn, "Both players ran out of time",
15315                            GE_PLAYER);
15316                 else
15317                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15318             } else {
15319                 DisplayError(_("Your opponent is not out of time"), 0);
15320             }
15321             break;
15322           case MachinePlaysBlack:
15323             if (blackFlag) {
15324                 if (whiteFlag)
15325                   GameEnds(GameIsDrawn, "Both players ran out of time",
15326                            GE_PLAYER);
15327                 else
15328                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15329             } else {
15330                 DisplayError(_("Your opponent is not out of time"), 0);
15331             }
15332             break;
15333         }
15334     }
15335 }
15336
15337 void
15338 ClockClick (int which)
15339 {       // [HGM] code moved to back-end from winboard.c
15340         if(which) { // black clock
15341           if (gameMode == EditPosition || gameMode == IcsExamining) {
15342             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15343             SetBlackToPlayEvent();
15344           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15345                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15346           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15347           } else if (shiftKey) {
15348             AdjustClock(which, -1);
15349           } else if (gameMode == IcsPlayingWhite ||
15350                      gameMode == MachinePlaysBlack) {
15351             CallFlagEvent();
15352           }
15353         } else { // white clock
15354           if (gameMode == EditPosition || gameMode == IcsExamining) {
15355             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15356             SetWhiteToPlayEvent();
15357           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15358                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15359           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15360           } else if (shiftKey) {
15361             AdjustClock(which, -1);
15362           } else if (gameMode == IcsPlayingBlack ||
15363                    gameMode == MachinePlaysWhite) {
15364             CallFlagEvent();
15365           }
15366         }
15367 }
15368
15369 void
15370 DrawEvent ()
15371 {
15372     /* Offer draw or accept pending draw offer from opponent */
15373
15374     if (appData.icsActive) {
15375         /* Note: tournament rules require draw offers to be
15376            made after you make your move but before you punch
15377            your clock.  Currently ICS doesn't let you do that;
15378            instead, you immediately punch your clock after making
15379            a move, but you can offer a draw at any time. */
15380
15381         SendToICS(ics_prefix);
15382         SendToICS("draw\n");
15383         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15384     } else if (cmailMsgLoaded) {
15385         if (currentMove == cmailOldMove &&
15386             commentList[cmailOldMove] != NULL &&
15387             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15388                    "Black offers a draw" : "White offers a draw")) {
15389             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15390             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15391         } else if (currentMove == cmailOldMove + 1) {
15392             char *offer = WhiteOnMove(cmailOldMove) ?
15393               "White offers a draw" : "Black offers a draw";
15394             AppendComment(currentMove, offer, TRUE);
15395             DisplayComment(currentMove - 1, offer);
15396             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15397         } else {
15398             DisplayError(_("You must make your move before offering a draw"), 0);
15399             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15400         }
15401     } else if (first.offeredDraw) {
15402         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15403     } else {
15404         if (first.sendDrawOffers) {
15405             SendToProgram("draw\n", &first);
15406             userOfferedDraw = TRUE;
15407         }
15408     }
15409 }
15410
15411 void
15412 AdjournEvent ()
15413 {
15414     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15415
15416     if (appData.icsActive) {
15417         SendToICS(ics_prefix);
15418         SendToICS("adjourn\n");
15419     } else {
15420         /* Currently GNU Chess doesn't offer or accept Adjourns */
15421     }
15422 }
15423
15424
15425 void
15426 AbortEvent ()
15427 {
15428     /* Offer Abort or accept pending Abort offer from opponent */
15429
15430     if (appData.icsActive) {
15431         SendToICS(ics_prefix);
15432         SendToICS("abort\n");
15433     } else {
15434         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15435     }
15436 }
15437
15438 void
15439 ResignEvent ()
15440 {
15441     /* Resign.  You can do this even if it's not your turn. */
15442
15443     if (appData.icsActive) {
15444         SendToICS(ics_prefix);
15445         SendToICS("resign\n");
15446     } else {
15447         switch (gameMode) {
15448           case MachinePlaysWhite:
15449             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15450             break;
15451           case MachinePlaysBlack:
15452             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15453             break;
15454           case EditGame:
15455             if (cmailMsgLoaded) {
15456                 TruncateGame();
15457                 if (WhiteOnMove(cmailOldMove)) {
15458                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15459                 } else {
15460                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15461                 }
15462                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15463             }
15464             break;
15465           default:
15466             break;
15467         }
15468     }
15469 }
15470
15471
15472 void
15473 StopObservingEvent ()
15474 {
15475     /* Stop observing current games */
15476     SendToICS(ics_prefix);
15477     SendToICS("unobserve\n");
15478 }
15479
15480 void
15481 StopExaminingEvent ()
15482 {
15483     /* Stop observing current game */
15484     SendToICS(ics_prefix);
15485     SendToICS("unexamine\n");
15486 }
15487
15488 void
15489 ForwardInner (int target)
15490 {
15491     int limit; int oldSeekGraphUp = seekGraphUp;
15492
15493     if (appData.debugMode)
15494         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15495                 target, currentMove, forwardMostMove);
15496
15497     if (gameMode == EditPosition)
15498       return;
15499
15500     seekGraphUp = FALSE;
15501     MarkTargetSquares(1);
15502
15503     if (gameMode == PlayFromGameFile && !pausing)
15504       PauseEvent();
15505
15506     if (gameMode == IcsExamining && pausing)
15507       limit = pauseExamForwardMostMove;
15508     else
15509       limit = forwardMostMove;
15510
15511     if (target > limit) target = limit;
15512
15513     if (target > 0 && moveList[target - 1][0]) {
15514         int fromX, fromY, toX, toY;
15515         toX = moveList[target - 1][2] - AAA;
15516         toY = moveList[target - 1][3] - ONE;
15517         if (moveList[target - 1][1] == '@') {
15518             if (appData.highlightLastMove) {
15519                 SetHighlights(-1, -1, toX, toY);
15520             }
15521         } else {
15522             int viaX = moveList[target - 1][5] - AAA;
15523             int viaY = moveList[target - 1][6] - ONE;
15524             fromX = moveList[target - 1][0] - AAA;
15525             fromY = moveList[target - 1][1] - ONE;
15526             if (target == currentMove + 1) {
15527                 if(moveList[target - 1][4] == ';') { // multi-leg
15528                     ChessSquare piece = boards[currentMove][viaY][viaX];
15529                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15530                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15531                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15532                     boards[currentMove][viaY][viaX] = piece;
15533                 } else
15534                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15535             }
15536             if (appData.highlightLastMove) {
15537                 SetHighlights(fromX, fromY, toX, toY);
15538             }
15539         }
15540     }
15541     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15542         gameMode == Training || gameMode == PlayFromGameFile ||
15543         gameMode == AnalyzeFile) {
15544         while (currentMove < target) {
15545             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15546             SendMoveToProgram(currentMove++, &first);
15547         }
15548     } else {
15549         currentMove = target;
15550     }
15551
15552     if (gameMode == EditGame || gameMode == EndOfGame) {
15553         whiteTimeRemaining = timeRemaining[0][currentMove];
15554         blackTimeRemaining = timeRemaining[1][currentMove];
15555     }
15556     DisplayBothClocks();
15557     DisplayMove(currentMove - 1);
15558     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15559     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15560     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15561         DisplayComment(currentMove - 1, commentList[currentMove]);
15562     }
15563     ClearMap(); // [HGM] exclude: invalidate map
15564 }
15565
15566
15567 void
15568 ForwardEvent ()
15569 {
15570     if (gameMode == IcsExamining && !pausing) {
15571         SendToICS(ics_prefix);
15572         SendToICS("forward\n");
15573     } else {
15574         ForwardInner(currentMove + 1);
15575     }
15576 }
15577
15578 void
15579 ToEndEvent ()
15580 {
15581     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15582         /* to optimze, we temporarily turn off analysis mode while we feed
15583          * the remaining moves to the engine. Otherwise we get analysis output
15584          * after each move.
15585          */
15586         if (first.analysisSupport) {
15587           SendToProgram("exit\nforce\n", &first);
15588           first.analyzing = FALSE;
15589         }
15590     }
15591
15592     if (gameMode == IcsExamining && !pausing) {
15593         SendToICS(ics_prefix);
15594         SendToICS("forward 999999\n");
15595     } else {
15596         ForwardInner(forwardMostMove);
15597     }
15598
15599     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15600         /* we have fed all the moves, so reactivate analysis mode */
15601         SendToProgram("analyze\n", &first);
15602         first.analyzing = TRUE;
15603         /*first.maybeThinking = TRUE;*/
15604         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15605     }
15606 }
15607
15608 void
15609 BackwardInner (int target)
15610 {
15611     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15612
15613     if (appData.debugMode)
15614         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15615                 target, currentMove, forwardMostMove);
15616
15617     if (gameMode == EditPosition) return;
15618     seekGraphUp = FALSE;
15619     MarkTargetSquares(1);
15620     if (currentMove <= backwardMostMove) {
15621         ClearHighlights();
15622         DrawPosition(full_redraw, boards[currentMove]);
15623         return;
15624     }
15625     if (gameMode == PlayFromGameFile && !pausing)
15626       PauseEvent();
15627
15628     if (moveList[target][0]) {
15629         int fromX, fromY, toX, toY;
15630         toX = moveList[target][2] - AAA;
15631         toY = moveList[target][3] - ONE;
15632         if (moveList[target][1] == '@') {
15633             if (appData.highlightLastMove) {
15634                 SetHighlights(-1, -1, toX, toY);
15635             }
15636         } else {
15637             fromX = moveList[target][0] - AAA;
15638             fromY = moveList[target][1] - ONE;
15639             if (target == currentMove - 1) {
15640                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15641             }
15642             if (appData.highlightLastMove) {
15643                 SetHighlights(fromX, fromY, toX, toY);
15644             }
15645         }
15646     }
15647     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15648         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15649         while (currentMove > target) {
15650             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15651                 // null move cannot be undone. Reload program with move history before it.
15652                 int i;
15653                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15654                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15655                 }
15656                 SendBoard(&first, i);
15657               if(second.analyzing) SendBoard(&second, i);
15658                 for(currentMove=i; currentMove<target; currentMove++) {
15659                     SendMoveToProgram(currentMove, &first);
15660                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15661                 }
15662                 break;
15663             }
15664             SendToBoth("undo\n");
15665             currentMove--;
15666         }
15667     } else {
15668         currentMove = target;
15669     }
15670
15671     if (gameMode == EditGame || gameMode == EndOfGame) {
15672         whiteTimeRemaining = timeRemaining[0][currentMove];
15673         blackTimeRemaining = timeRemaining[1][currentMove];
15674     }
15675     DisplayBothClocks();
15676     DisplayMove(currentMove - 1);
15677     DrawPosition(full_redraw, boards[currentMove]);
15678     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15679     // [HGM] PV info: routine tests if comment empty
15680     DisplayComment(currentMove - 1, commentList[currentMove]);
15681     ClearMap(); // [HGM] exclude: invalidate map
15682 }
15683
15684 void
15685 BackwardEvent ()
15686 {
15687     if (gameMode == IcsExamining && !pausing) {
15688         SendToICS(ics_prefix);
15689         SendToICS("backward\n");
15690     } else {
15691         BackwardInner(currentMove - 1);
15692     }
15693 }
15694
15695 void
15696 ToStartEvent ()
15697 {
15698     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15699         /* to optimize, we temporarily turn off analysis mode while we undo
15700          * all the moves. Otherwise we get analysis output after each undo.
15701          */
15702         if (first.analysisSupport) {
15703           SendToProgram("exit\nforce\n", &first);
15704           first.analyzing = FALSE;
15705         }
15706     }
15707
15708     if (gameMode == IcsExamining && !pausing) {
15709         SendToICS(ics_prefix);
15710         SendToICS("backward 999999\n");
15711     } else {
15712         BackwardInner(backwardMostMove);
15713     }
15714
15715     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15716         /* we have fed all the moves, so reactivate analysis mode */
15717         SendToProgram("analyze\n", &first);
15718         first.analyzing = TRUE;
15719         /*first.maybeThinking = TRUE;*/
15720         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15721     }
15722 }
15723
15724 void
15725 ToNrEvent (int to)
15726 {
15727   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15728   if (to >= forwardMostMove) to = forwardMostMove;
15729   if (to <= backwardMostMove) to = backwardMostMove;
15730   if (to < currentMove) {
15731     BackwardInner(to);
15732   } else {
15733     ForwardInner(to);
15734   }
15735 }
15736
15737 void
15738 RevertEvent (Boolean annotate)
15739 {
15740     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15741         return;
15742     }
15743     if (gameMode != IcsExamining) {
15744         DisplayError(_("You are not examining a game"), 0);
15745         return;
15746     }
15747     if (pausing) {
15748         DisplayError(_("You can't revert while pausing"), 0);
15749         return;
15750     }
15751     SendToICS(ics_prefix);
15752     SendToICS("revert\n");
15753 }
15754
15755 void
15756 RetractMoveEvent ()
15757 {
15758     switch (gameMode) {
15759       case MachinePlaysWhite:
15760       case MachinePlaysBlack:
15761         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15762             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15763             return;
15764         }
15765         if (forwardMostMove < 2) return;
15766         currentMove = forwardMostMove = forwardMostMove - 2;
15767         whiteTimeRemaining = timeRemaining[0][currentMove];
15768         blackTimeRemaining = timeRemaining[1][currentMove];
15769         DisplayBothClocks();
15770         DisplayMove(currentMove - 1);
15771         ClearHighlights();/*!! could figure this out*/
15772         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15773         SendToProgram("remove\n", &first);
15774         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15775         break;
15776
15777       case BeginningOfGame:
15778       default:
15779         break;
15780
15781       case IcsPlayingWhite:
15782       case IcsPlayingBlack:
15783         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15784             SendToICS(ics_prefix);
15785             SendToICS("takeback 2\n");
15786         } else {
15787             SendToICS(ics_prefix);
15788             SendToICS("takeback 1\n");
15789         }
15790         break;
15791     }
15792 }
15793
15794 void
15795 MoveNowEvent ()
15796 {
15797     ChessProgramState *cps;
15798
15799     switch (gameMode) {
15800       case MachinePlaysWhite:
15801         if (!WhiteOnMove(forwardMostMove)) {
15802             DisplayError(_("It is your turn"), 0);
15803             return;
15804         }
15805         cps = &first;
15806         break;
15807       case MachinePlaysBlack:
15808         if (WhiteOnMove(forwardMostMove)) {
15809             DisplayError(_("It is your turn"), 0);
15810             return;
15811         }
15812         cps = &first;
15813         break;
15814       case TwoMachinesPlay:
15815         if (WhiteOnMove(forwardMostMove) ==
15816             (first.twoMachinesColor[0] == 'w')) {
15817             cps = &first;
15818         } else {
15819             cps = &second;
15820         }
15821         break;
15822       case BeginningOfGame:
15823       default:
15824         return;
15825     }
15826     SendToProgram("?\n", cps);
15827 }
15828
15829 void
15830 TruncateGameEvent ()
15831 {
15832     EditGameEvent();
15833     if (gameMode != EditGame) return;
15834     TruncateGame();
15835 }
15836
15837 void
15838 TruncateGame ()
15839 {
15840     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15841     if (forwardMostMove > currentMove) {
15842         if (gameInfo.resultDetails != NULL) {
15843             free(gameInfo.resultDetails);
15844             gameInfo.resultDetails = NULL;
15845             gameInfo.result = GameUnfinished;
15846         }
15847         forwardMostMove = currentMove;
15848         HistorySet(parseList, backwardMostMove, forwardMostMove,
15849                    currentMove-1);
15850     }
15851 }
15852
15853 void
15854 HintEvent ()
15855 {
15856     if (appData.noChessProgram) return;
15857     switch (gameMode) {
15858       case MachinePlaysWhite:
15859         if (WhiteOnMove(forwardMostMove)) {
15860             DisplayError(_("Wait until your turn."), 0);
15861             return;
15862         }
15863         break;
15864       case BeginningOfGame:
15865       case MachinePlaysBlack:
15866         if (!WhiteOnMove(forwardMostMove)) {
15867             DisplayError(_("Wait until your turn."), 0);
15868             return;
15869         }
15870         break;
15871       default:
15872         DisplayError(_("No hint available"), 0);
15873         return;
15874     }
15875     SendToProgram("hint\n", &first);
15876     hintRequested = TRUE;
15877 }
15878
15879 int
15880 SaveSelected (FILE *g, int dummy, char *dummy2)
15881 {
15882     ListGame * lg = (ListGame *) gameList.head;
15883     int nItem, cnt=0;
15884     FILE *f;
15885
15886     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15887         DisplayError(_("Game list not loaded or empty"), 0);
15888         return 0;
15889     }
15890
15891     creatingBook = TRUE; // suppresses stuff during load game
15892
15893     /* Get list size */
15894     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15895         if(lg->position >= 0) { // selected?
15896             LoadGame(f, nItem, "", TRUE);
15897             SaveGamePGN2(g); // leaves g open
15898             cnt++; DoEvents();
15899         }
15900         lg = (ListGame *) lg->node.succ;
15901     }
15902
15903     fclose(g);
15904     creatingBook = FALSE;
15905
15906     return cnt;
15907 }
15908
15909 void
15910 CreateBookEvent ()
15911 {
15912     ListGame * lg = (ListGame *) gameList.head;
15913     FILE *f, *g;
15914     int nItem;
15915     static int secondTime = FALSE;
15916
15917     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15918         DisplayError(_("Game list not loaded or empty"), 0);
15919         return;
15920     }
15921
15922     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15923         fclose(g);
15924         secondTime++;
15925         DisplayNote(_("Book file exists! Try again for overwrite."));
15926         return;
15927     }
15928
15929     creatingBook = TRUE;
15930     secondTime = FALSE;
15931
15932     /* Get list size */
15933     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15934         if(lg->position >= 0) {
15935             LoadGame(f, nItem, "", TRUE);
15936             AddGameToBook(TRUE);
15937             DoEvents();
15938         }
15939         lg = (ListGame *) lg->node.succ;
15940     }
15941
15942     creatingBook = FALSE;
15943     FlushBook();
15944 }
15945
15946 void
15947 BookEvent ()
15948 {
15949     if (appData.noChessProgram) return;
15950     switch (gameMode) {
15951       case MachinePlaysWhite:
15952         if (WhiteOnMove(forwardMostMove)) {
15953             DisplayError(_("Wait until your turn."), 0);
15954             return;
15955         }
15956         break;
15957       case BeginningOfGame:
15958       case MachinePlaysBlack:
15959         if (!WhiteOnMove(forwardMostMove)) {
15960             DisplayError(_("Wait until your turn."), 0);
15961             return;
15962         }
15963         break;
15964       case EditPosition:
15965         EditPositionDone(TRUE);
15966         break;
15967       case TwoMachinesPlay:
15968         return;
15969       default:
15970         break;
15971     }
15972     SendToProgram("bk\n", &first);
15973     bookOutput[0] = NULLCHAR;
15974     bookRequested = TRUE;
15975 }
15976
15977 void
15978 AboutGameEvent ()
15979 {
15980     char *tags = PGNTags(&gameInfo);
15981     TagsPopUp(tags, CmailMsg());
15982     free(tags);
15983 }
15984
15985 /* end button procedures */
15986
15987 void
15988 PrintPosition (FILE *fp, int move)
15989 {
15990     int i, j;
15991
15992     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15993         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15994             char c = PieceToChar(boards[move][i][j]);
15995             fputc(c == 'x' ? '.' : c, fp);
15996             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15997         }
15998     }
15999     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16000       fprintf(fp, "white to play\n");
16001     else
16002       fprintf(fp, "black to play\n");
16003 }
16004
16005 void
16006 PrintOpponents (FILE *fp)
16007 {
16008     if (gameInfo.white != NULL) {
16009         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16010     } else {
16011         fprintf(fp, "\n");
16012     }
16013 }
16014
16015 /* Find last component of program's own name, using some heuristics */
16016 void
16017 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16018 {
16019     char *p, *q, c;
16020     int local = (strcmp(host, "localhost") == 0);
16021     while (!local && (p = strchr(prog, ';')) != NULL) {
16022         p++;
16023         while (*p == ' ') p++;
16024         prog = p;
16025     }
16026     if (*prog == '"' || *prog == '\'') {
16027         q = strchr(prog + 1, *prog);
16028     } else {
16029         q = strchr(prog, ' ');
16030     }
16031     if (q == NULL) q = prog + strlen(prog);
16032     p = q;
16033     while (p >= prog && *p != '/' && *p != '\\') p--;
16034     p++;
16035     if(p == prog && *p == '"') p++;
16036     c = *q; *q = 0;
16037     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16038     memcpy(buf, p, q - p);
16039     buf[q - p] = NULLCHAR;
16040     if (!local) {
16041         strcat(buf, "@");
16042         strcat(buf, host);
16043     }
16044 }
16045
16046 char *
16047 TimeControlTagValue ()
16048 {
16049     char buf[MSG_SIZ];
16050     if (!appData.clockMode) {
16051       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16052     } else if (movesPerSession > 0) {
16053       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16054     } else if (timeIncrement == 0) {
16055       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16056     } else {
16057       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16058     }
16059     return StrSave(buf);
16060 }
16061
16062 void
16063 SetGameInfo ()
16064 {
16065     /* This routine is used only for certain modes */
16066     VariantClass v = gameInfo.variant;
16067     ChessMove r = GameUnfinished;
16068     char *p = NULL;
16069
16070     if(keepInfo) return;
16071
16072     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16073         r = gameInfo.result;
16074         p = gameInfo.resultDetails;
16075         gameInfo.resultDetails = NULL;
16076     }
16077     ClearGameInfo(&gameInfo);
16078     gameInfo.variant = v;
16079
16080     switch (gameMode) {
16081       case MachinePlaysWhite:
16082         gameInfo.event = StrSave( appData.pgnEventHeader );
16083         gameInfo.site = StrSave(HostName());
16084         gameInfo.date = PGNDate();
16085         gameInfo.round = StrSave("-");
16086         gameInfo.white = StrSave(first.tidy);
16087         gameInfo.black = StrSave(UserName());
16088         gameInfo.timeControl = TimeControlTagValue();
16089         break;
16090
16091       case MachinePlaysBlack:
16092         gameInfo.event = StrSave( appData.pgnEventHeader );
16093         gameInfo.site = StrSave(HostName());
16094         gameInfo.date = PGNDate();
16095         gameInfo.round = StrSave("-");
16096         gameInfo.white = StrSave(UserName());
16097         gameInfo.black = StrSave(first.tidy);
16098         gameInfo.timeControl = TimeControlTagValue();
16099         break;
16100
16101       case TwoMachinesPlay:
16102         gameInfo.event = StrSave( appData.pgnEventHeader );
16103         gameInfo.site = StrSave(HostName());
16104         gameInfo.date = PGNDate();
16105         if (roundNr > 0) {
16106             char buf[MSG_SIZ];
16107             snprintf(buf, MSG_SIZ, "%d", roundNr);
16108             gameInfo.round = StrSave(buf);
16109         } else {
16110             gameInfo.round = StrSave("-");
16111         }
16112         if (first.twoMachinesColor[0] == 'w') {
16113             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16114             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16115         } else {
16116             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16117             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16118         }
16119         gameInfo.timeControl = TimeControlTagValue();
16120         break;
16121
16122       case EditGame:
16123         gameInfo.event = StrSave("Edited game");
16124         gameInfo.site = StrSave(HostName());
16125         gameInfo.date = PGNDate();
16126         gameInfo.round = StrSave("-");
16127         gameInfo.white = StrSave("-");
16128         gameInfo.black = StrSave("-");
16129         gameInfo.result = r;
16130         gameInfo.resultDetails = p;
16131         break;
16132
16133       case EditPosition:
16134         gameInfo.event = StrSave("Edited position");
16135         gameInfo.site = StrSave(HostName());
16136         gameInfo.date = PGNDate();
16137         gameInfo.round = StrSave("-");
16138         gameInfo.white = StrSave("-");
16139         gameInfo.black = StrSave("-");
16140         break;
16141
16142       case IcsPlayingWhite:
16143       case IcsPlayingBlack:
16144       case IcsObserving:
16145       case IcsExamining:
16146         break;
16147
16148       case PlayFromGameFile:
16149         gameInfo.event = StrSave("Game from non-PGN file");
16150         gameInfo.site = StrSave(HostName());
16151         gameInfo.date = PGNDate();
16152         gameInfo.round = StrSave("-");
16153         gameInfo.white = StrSave("?");
16154         gameInfo.black = StrSave("?");
16155         break;
16156
16157       default:
16158         break;
16159     }
16160 }
16161
16162 void
16163 ReplaceComment (int index, char *text)
16164 {
16165     int len;
16166     char *p;
16167     float score;
16168
16169     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16170        pvInfoList[index-1].depth == len &&
16171        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16172        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16173     while (*text == '\n') text++;
16174     len = strlen(text);
16175     while (len > 0 && text[len - 1] == '\n') len--;
16176
16177     if (commentList[index] != NULL)
16178       free(commentList[index]);
16179
16180     if (len == 0) {
16181         commentList[index] = NULL;
16182         return;
16183     }
16184   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16185       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16186       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16187     commentList[index] = (char *) malloc(len + 2);
16188     strncpy(commentList[index], text, len);
16189     commentList[index][len] = '\n';
16190     commentList[index][len + 1] = NULLCHAR;
16191   } else {
16192     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16193     char *p;
16194     commentList[index] = (char *) malloc(len + 7);
16195     safeStrCpy(commentList[index], "{\n", 3);
16196     safeStrCpy(commentList[index]+2, text, len+1);
16197     commentList[index][len+2] = NULLCHAR;
16198     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16199     strcat(commentList[index], "\n}\n");
16200   }
16201 }
16202
16203 void
16204 CrushCRs (char *text)
16205 {
16206   char *p = text;
16207   char *q = text;
16208   char ch;
16209
16210   do {
16211     ch = *p++;
16212     if (ch == '\r') continue;
16213     *q++ = ch;
16214   } while (ch != '\0');
16215 }
16216
16217 void
16218 AppendComment (int index, char *text, Boolean addBraces)
16219 /* addBraces  tells if we should add {} */
16220 {
16221     int oldlen, len;
16222     char *old;
16223
16224 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16225     if(addBraces == 3) addBraces = 0; else // force appending literally
16226     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16227
16228     CrushCRs(text);
16229     while (*text == '\n') text++;
16230     len = strlen(text);
16231     while (len > 0 && text[len - 1] == '\n') len--;
16232     text[len] = NULLCHAR;
16233
16234     if (len == 0) return;
16235
16236     if (commentList[index] != NULL) {
16237       Boolean addClosingBrace = addBraces;
16238         old = commentList[index];
16239         oldlen = strlen(old);
16240         while(commentList[index][oldlen-1] ==  '\n')
16241           commentList[index][--oldlen] = NULLCHAR;
16242         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16243         safeStrCpy(commentList[index], old, oldlen + len + 6);
16244         free(old);
16245         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16246         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16247           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16248           while (*text == '\n') { text++; len--; }
16249           commentList[index][--oldlen] = NULLCHAR;
16250       }
16251         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16252         else          strcat(commentList[index], "\n");
16253         strcat(commentList[index], text);
16254         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16255         else          strcat(commentList[index], "\n");
16256     } else {
16257         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16258         if(addBraces)
16259           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16260         else commentList[index][0] = NULLCHAR;
16261         strcat(commentList[index], text);
16262         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16263         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16264     }
16265 }
16266
16267 static char *
16268 FindStr (char * text, char * sub_text)
16269 {
16270     char * result = strstr( text, sub_text );
16271
16272     if( result != NULL ) {
16273         result += strlen( sub_text );
16274     }
16275
16276     return result;
16277 }
16278
16279 /* [AS] Try to extract PV info from PGN comment */
16280 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16281 char *
16282 GetInfoFromComment (int index, char * text)
16283 {
16284     char * sep = text, *p;
16285
16286     if( text != NULL && index > 0 ) {
16287         int score = 0;
16288         int depth = 0;
16289         int time = -1, sec = 0, deci;
16290         char * s_eval = FindStr( text, "[%eval " );
16291         char * s_emt = FindStr( text, "[%emt " );
16292 #if 0
16293         if( s_eval != NULL || s_emt != NULL ) {
16294 #else
16295         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16296 #endif
16297             /* New style */
16298             char delim;
16299
16300             if( s_eval != NULL ) {
16301                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16302                     return text;
16303                 }
16304
16305                 if( delim != ']' ) {
16306                     return text;
16307                 }
16308             }
16309
16310             if( s_emt != NULL ) {
16311             }
16312                 return text;
16313         }
16314         else {
16315             /* We expect something like: [+|-]nnn.nn/dd */
16316             int score_lo = 0;
16317
16318             if(*text != '{') return text; // [HGM] braces: must be normal comment
16319
16320             sep = strchr( text, '/' );
16321             if( sep == NULL || sep < (text+4) ) {
16322                 return text;
16323             }
16324
16325             p = text;
16326             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16327             if(p[1] == '(') { // comment starts with PV
16328                p = strchr(p, ')'); // locate end of PV
16329                if(p == NULL || sep < p+5) return text;
16330                // at this point we have something like "{(.*) +0.23/6 ..."
16331                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16332                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16333                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16334             }
16335             time = -1; sec = -1; deci = -1;
16336             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16337                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16338                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16339                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16340                 return text;
16341             }
16342
16343             if( score_lo < 0 || score_lo >= 100 ) {
16344                 return text;
16345             }
16346
16347             if(sec >= 0) time = 600*time + 10*sec; else
16348             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16349
16350             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16351
16352             /* [HGM] PV time: now locate end of PV info */
16353             while( *++sep >= '0' && *sep <= '9'); // strip depth
16354             if(time >= 0)
16355             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16356             if(sec >= 0)
16357             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16358             if(deci >= 0)
16359             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16360             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16361         }
16362
16363         if( depth <= 0 ) {
16364             return text;
16365         }
16366
16367         if( time < 0 ) {
16368             time = -1;
16369         }
16370
16371         pvInfoList[index-1].depth = depth;
16372         pvInfoList[index-1].score = score;
16373         pvInfoList[index-1].time  = 10*time; // centi-sec
16374         if(*sep == '}') *sep = 0; else *--sep = '{';
16375         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16376     }
16377     return sep;
16378 }
16379
16380 void
16381 SendToProgram (char *message, ChessProgramState *cps)
16382 {
16383     int count, outCount, error;
16384     char buf[MSG_SIZ];
16385
16386     if (cps->pr == NoProc) return;
16387     Attention(cps);
16388
16389     if (appData.debugMode) {
16390         TimeMark now;
16391         GetTimeMark(&now);
16392         fprintf(debugFP, "%ld >%-6s: %s",
16393                 SubtractTimeMarks(&now, &programStartTime),
16394                 cps->which, message);
16395         if(serverFP)
16396             fprintf(serverFP, "%ld >%-6s: %s",
16397                 SubtractTimeMarks(&now, &programStartTime),
16398                 cps->which, message), fflush(serverFP);
16399     }
16400
16401     count = strlen(message);
16402     outCount = OutputToProcess(cps->pr, message, count, &error);
16403     if (outCount < count && !exiting
16404                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16405       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16406       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16407         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16408             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16409                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16410                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16411                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16412             } else {
16413                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16414                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16415                 gameInfo.result = res;
16416             }
16417             gameInfo.resultDetails = StrSave(buf);
16418         }
16419         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16420         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16421     }
16422 }
16423
16424 void
16425 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16426 {
16427     char *end_str;
16428     char buf[MSG_SIZ];
16429     ChessProgramState *cps = (ChessProgramState *)closure;
16430
16431     if (isr != cps->isr) return; /* Killed intentionally */
16432     if (count <= 0) {
16433         if (count == 0) {
16434             RemoveInputSource(cps->isr);
16435             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16436                     _(cps->which), cps->program);
16437             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16438             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16439                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16440                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16441                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16442                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16443                 } else {
16444                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16445                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16446                     gameInfo.result = res;
16447                 }
16448                 gameInfo.resultDetails = StrSave(buf);
16449             }
16450             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16451             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16452         } else {
16453             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16454                     _(cps->which), cps->program);
16455             RemoveInputSource(cps->isr);
16456
16457             /* [AS] Program is misbehaving badly... kill it */
16458             if( count == -2 ) {
16459                 DestroyChildProcess( cps->pr, 9 );
16460                 cps->pr = NoProc;
16461             }
16462
16463             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16464         }
16465         return;
16466     }
16467
16468     if ((end_str = strchr(message, '\r')) != NULL)
16469       *end_str = NULLCHAR;
16470     if ((end_str = strchr(message, '\n')) != NULL)
16471       *end_str = NULLCHAR;
16472
16473     if (appData.debugMode) {
16474         TimeMark now; int print = 1;
16475         char *quote = ""; char c; int i;
16476
16477         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16478                 char start = message[0];
16479                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16480                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16481                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16482                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16483                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16484                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16485                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16486                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16487                    sscanf(message, "hint: %c", &c)!=1 &&
16488                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16489                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16490                     print = (appData.engineComments >= 2);
16491                 }
16492                 message[0] = start; // restore original message
16493         }
16494         if(print) {
16495                 GetTimeMark(&now);
16496                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16497                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16498                         quote,
16499                         message);
16500                 if(serverFP)
16501                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16502                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16503                         quote,
16504                         message), fflush(serverFP);
16505         }
16506     }
16507
16508     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16509     if (appData.icsEngineAnalyze) {
16510         if (strstr(message, "whisper") != NULL ||
16511              strstr(message, "kibitz") != NULL ||
16512             strstr(message, "tellics") != NULL) return;
16513     }
16514
16515     HandleMachineMove(message, cps);
16516 }
16517
16518
16519 void
16520 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16521 {
16522     char buf[MSG_SIZ];
16523     int seconds;
16524
16525     if( timeControl_2 > 0 ) {
16526         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16527             tc = timeControl_2;
16528         }
16529     }
16530     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16531     inc /= cps->timeOdds;
16532     st  /= cps->timeOdds;
16533
16534     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16535
16536     if (st > 0) {
16537       /* Set exact time per move, normally using st command */
16538       if (cps->stKludge) {
16539         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16540         seconds = st % 60;
16541         if (seconds == 0) {
16542           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16543         } else {
16544           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16545         }
16546       } else {
16547         snprintf(buf, MSG_SIZ, "st %d\n", st);
16548       }
16549     } else {
16550       /* Set conventional or incremental time control, using level command */
16551       if (seconds == 0) {
16552         /* Note old gnuchess bug -- minutes:seconds used to not work.
16553            Fixed in later versions, but still avoid :seconds
16554            when seconds is 0. */
16555         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16556       } else {
16557         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16558                  seconds, inc/1000.);
16559       }
16560     }
16561     SendToProgram(buf, cps);
16562
16563     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16564     /* Orthogonally, limit search to given depth */
16565     if (sd > 0) {
16566       if (cps->sdKludge) {
16567         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16568       } else {
16569         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16570       }
16571       SendToProgram(buf, cps);
16572     }
16573
16574     if(cps->nps >= 0) { /* [HGM] nps */
16575         if(cps->supportsNPS == FALSE)
16576           cps->nps = -1; // don't use if engine explicitly says not supported!
16577         else {
16578           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16579           SendToProgram(buf, cps);
16580         }
16581     }
16582 }
16583
16584 ChessProgramState *
16585 WhitePlayer ()
16586 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16587 {
16588     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16589        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16590         return &second;
16591     return &first;
16592 }
16593
16594 void
16595 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16596 {
16597     char message[MSG_SIZ];
16598     long time, otime;
16599
16600     /* Note: this routine must be called when the clocks are stopped
16601        or when they have *just* been set or switched; otherwise
16602        it will be off by the time since the current tick started.
16603     */
16604     if (machineWhite) {
16605         time = whiteTimeRemaining / 10;
16606         otime = blackTimeRemaining / 10;
16607     } else {
16608         time = blackTimeRemaining / 10;
16609         otime = whiteTimeRemaining / 10;
16610     }
16611     /* [HGM] translate opponent's time by time-odds factor */
16612     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16613
16614     if (time <= 0) time = 1;
16615     if (otime <= 0) otime = 1;
16616
16617     snprintf(message, MSG_SIZ, "time %ld\n", time);
16618     SendToProgram(message, cps);
16619
16620     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16621     SendToProgram(message, cps);
16622 }
16623
16624 char *
16625 EngineDefinedVariant (ChessProgramState *cps, int n)
16626 {   // return name of n-th unknown variant that engine supports
16627     static char buf[MSG_SIZ];
16628     char *p, *s = cps->variants;
16629     if(!s) return NULL;
16630     do { // parse string from variants feature
16631       VariantClass v;
16632         p = strchr(s, ',');
16633         if(p) *p = NULLCHAR;
16634       v = StringToVariant(s);
16635       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16636         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16637             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16638                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16639                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16640                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16641             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16642         }
16643         if(p) *p++ = ',';
16644         if(n < 0) return buf;
16645     } while(s = p);
16646     return NULL;
16647 }
16648
16649 int
16650 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16651 {
16652   char buf[MSG_SIZ];
16653   int len = strlen(name);
16654   int val;
16655
16656   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16657     (*p) += len + 1;
16658     sscanf(*p, "%d", &val);
16659     *loc = (val != 0);
16660     while (**p && **p != ' ')
16661       (*p)++;
16662     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16663     SendToProgram(buf, cps);
16664     return TRUE;
16665   }
16666   return FALSE;
16667 }
16668
16669 int
16670 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16671 {
16672   char buf[MSG_SIZ];
16673   int len = strlen(name);
16674   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16675     (*p) += len + 1;
16676     sscanf(*p, "%d", loc);
16677     while (**p && **p != ' ') (*p)++;
16678     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16679     SendToProgram(buf, cps);
16680     return TRUE;
16681   }
16682   return FALSE;
16683 }
16684
16685 int
16686 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16687 {
16688   char buf[MSG_SIZ];
16689   int len = strlen(name);
16690   if (strncmp((*p), name, len) == 0
16691       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16692     (*p) += len + 2;
16693     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16694     sscanf(*p, "%[^\"]", *loc);
16695     while (**p && **p != '\"') (*p)++;
16696     if (**p == '\"') (*p)++;
16697     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16698     SendToProgram(buf, cps);
16699     return TRUE;
16700   }
16701   return FALSE;
16702 }
16703
16704 int
16705 ParseOption (Option *opt, ChessProgramState *cps)
16706 // [HGM] options: process the string that defines an engine option, and determine
16707 // name, type, default value, and allowed value range
16708 {
16709         char *p, *q, buf[MSG_SIZ];
16710         int n, min = (-1)<<31, max = 1<<31, def;
16711
16712         if(p = strstr(opt->name, " -spin ")) {
16713             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16714             if(max < min) max = min; // enforce consistency
16715             if(def < min) def = min;
16716             if(def > max) def = max;
16717             opt->value = def;
16718             opt->min = min;
16719             opt->max = max;
16720             opt->type = Spin;
16721         } else if((p = strstr(opt->name, " -slider "))) {
16722             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16723             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16724             if(max < min) max = min; // enforce consistency
16725             if(def < min) def = min;
16726             if(def > max) def = max;
16727             opt->value = def;
16728             opt->min = min;
16729             opt->max = max;
16730             opt->type = Spin; // Slider;
16731         } else if((p = strstr(opt->name, " -string "))) {
16732             opt->textValue = p+9;
16733             opt->type = TextBox;
16734         } else if((p = strstr(opt->name, " -file "))) {
16735             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16736             opt->textValue = p+7;
16737             opt->type = FileName; // FileName;
16738         } else if((p = strstr(opt->name, " -path "))) {
16739             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16740             opt->textValue = p+7;
16741             opt->type = PathName; // PathName;
16742         } else if(p = strstr(opt->name, " -check ")) {
16743             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16744             opt->value = (def != 0);
16745             opt->type = CheckBox;
16746         } else if(p = strstr(opt->name, " -combo ")) {
16747             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16748             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16749             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16750             opt->value = n = 0;
16751             while(q = StrStr(q, " /// ")) {
16752                 n++; *q = 0;    // count choices, and null-terminate each of them
16753                 q += 5;
16754                 if(*q == '*') { // remember default, which is marked with * prefix
16755                     q++;
16756                     opt->value = n;
16757                 }
16758                 cps->comboList[cps->comboCnt++] = q;
16759             }
16760             cps->comboList[cps->comboCnt++] = NULL;
16761             opt->max = n + 1;
16762             opt->type = ComboBox;
16763         } else if(p = strstr(opt->name, " -button")) {
16764             opt->type = Button;
16765         } else if(p = strstr(opt->name, " -save")) {
16766             opt->type = SaveButton;
16767         } else return FALSE;
16768         *p = 0; // terminate option name
16769         // now look if the command-line options define a setting for this engine option.
16770         if(cps->optionSettings && cps->optionSettings[0])
16771             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16772         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16773           snprintf(buf, MSG_SIZ, "option %s", p);
16774                 if(p = strstr(buf, ",")) *p = 0;
16775                 if(q = strchr(buf, '=')) switch(opt->type) {
16776                     case ComboBox:
16777                         for(n=0; n<opt->max; n++)
16778                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16779                         break;
16780                     case TextBox:
16781                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16782                         break;
16783                     case Spin:
16784                     case CheckBox:
16785                         opt->value = atoi(q+1);
16786                     default:
16787                         break;
16788                 }
16789                 strcat(buf, "\n");
16790                 SendToProgram(buf, cps);
16791         }
16792         return TRUE;
16793 }
16794
16795 void
16796 FeatureDone (ChessProgramState *cps, int val)
16797 {
16798   DelayedEventCallback cb = GetDelayedEvent();
16799   if ((cb == InitBackEnd3 && cps == &first) ||
16800       (cb == SettingsMenuIfReady && cps == &second) ||
16801       (cb == LoadEngine) ||
16802       (cb == TwoMachinesEventIfReady)) {
16803     CancelDelayedEvent();
16804     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16805   }
16806   cps->initDone = val;
16807   if(val) cps->reload = FALSE;
16808 }
16809
16810 /* Parse feature command from engine */
16811 void
16812 ParseFeatures (char *args, ChessProgramState *cps)
16813 {
16814   char *p = args;
16815   char *q = NULL;
16816   int val;
16817   char buf[MSG_SIZ];
16818
16819   for (;;) {
16820     while (*p == ' ') p++;
16821     if (*p == NULLCHAR) return;
16822
16823     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16824     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16825     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16826     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16827     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16828     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16829     if (BoolFeature(&p, "reuse", &val, cps)) {
16830       /* Engine can disable reuse, but can't enable it if user said no */
16831       if (!val) cps->reuse = FALSE;
16832       continue;
16833     }
16834     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16835     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16836       if (gameMode == TwoMachinesPlay) {
16837         DisplayTwoMachinesTitle();
16838       } else {
16839         DisplayTitle("");
16840       }
16841       continue;
16842     }
16843     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16844     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16845     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16846     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16847     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16848     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16849     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16850     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16851     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16852     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16853     if (IntFeature(&p, "done", &val, cps)) {
16854       FeatureDone(cps, val);
16855       continue;
16856     }
16857     /* Added by Tord: */
16858     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16859     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16860     /* End of additions by Tord */
16861
16862     /* [HGM] added features: */
16863     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16864     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16865     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16866     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16867     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16868     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16869     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16870     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16871         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16872         FREE(cps->option[cps->nrOptions].name);
16873         cps->option[cps->nrOptions].name = q; q = NULL;
16874         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16875           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16876             SendToProgram(buf, cps);
16877             continue;
16878         }
16879         if(cps->nrOptions >= MAX_OPTIONS) {
16880             cps->nrOptions--;
16881             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16882             DisplayError(buf, 0);
16883         }
16884         continue;
16885     }
16886     /* End of additions by HGM */
16887
16888     /* unknown feature: complain and skip */
16889     q = p;
16890     while (*q && *q != '=') q++;
16891     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16892     SendToProgram(buf, cps);
16893     p = q;
16894     if (*p == '=') {
16895       p++;
16896       if (*p == '\"') {
16897         p++;
16898         while (*p && *p != '\"') p++;
16899         if (*p == '\"') p++;
16900       } else {
16901         while (*p && *p != ' ') p++;
16902       }
16903     }
16904   }
16905
16906 }
16907
16908 void
16909 PeriodicUpdatesEvent (int newState)
16910 {
16911     if (newState == appData.periodicUpdates)
16912       return;
16913
16914     appData.periodicUpdates=newState;
16915
16916     /* Display type changes, so update it now */
16917 //    DisplayAnalysis();
16918
16919     /* Get the ball rolling again... */
16920     if (newState) {
16921         AnalysisPeriodicEvent(1);
16922         StartAnalysisClock();
16923     }
16924 }
16925
16926 void
16927 PonderNextMoveEvent (int newState)
16928 {
16929     if (newState == appData.ponderNextMove) return;
16930     if (gameMode == EditPosition) EditPositionDone(TRUE);
16931     if (newState) {
16932         SendToProgram("hard\n", &first);
16933         if (gameMode == TwoMachinesPlay) {
16934             SendToProgram("hard\n", &second);
16935         }
16936     } else {
16937         SendToProgram("easy\n", &first);
16938         thinkOutput[0] = NULLCHAR;
16939         if (gameMode == TwoMachinesPlay) {
16940             SendToProgram("easy\n", &second);
16941         }
16942     }
16943     appData.ponderNextMove = newState;
16944 }
16945
16946 void
16947 NewSettingEvent (int option, int *feature, char *command, int value)
16948 {
16949     char buf[MSG_SIZ];
16950
16951     if (gameMode == EditPosition) EditPositionDone(TRUE);
16952     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16953     if(feature == NULL || *feature) SendToProgram(buf, &first);
16954     if (gameMode == TwoMachinesPlay) {
16955         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16956     }
16957 }
16958
16959 void
16960 ShowThinkingEvent ()
16961 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16962 {
16963     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16964     int newState = appData.showThinking
16965         // [HGM] thinking: other features now need thinking output as well
16966         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16967
16968     if (oldState == newState) return;
16969     oldState = newState;
16970     if (gameMode == EditPosition) EditPositionDone(TRUE);
16971     if (oldState) {
16972         SendToProgram("post\n", &first);
16973         if (gameMode == TwoMachinesPlay) {
16974             SendToProgram("post\n", &second);
16975         }
16976     } else {
16977         SendToProgram("nopost\n", &first);
16978         thinkOutput[0] = NULLCHAR;
16979         if (gameMode == TwoMachinesPlay) {
16980             SendToProgram("nopost\n", &second);
16981         }
16982     }
16983 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16984 }
16985
16986 void
16987 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16988 {
16989   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16990   if (pr == NoProc) return;
16991   AskQuestion(title, question, replyPrefix, pr);
16992 }
16993
16994 void
16995 TypeInEvent (char firstChar)
16996 {
16997     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16998         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16999         gameMode == AnalyzeMode || gameMode == EditGame ||
17000         gameMode == EditPosition || gameMode == IcsExamining ||
17001         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17002         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17003                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17004                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17005         gameMode == Training) PopUpMoveDialog(firstChar);
17006 }
17007
17008 void
17009 TypeInDoneEvent (char *move)
17010 {
17011         Board board;
17012         int n, fromX, fromY, toX, toY;
17013         char promoChar;
17014         ChessMove moveType;
17015
17016         // [HGM] FENedit
17017         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17018                 EditPositionPasteFEN(move);
17019                 return;
17020         }
17021         // [HGM] movenum: allow move number to be typed in any mode
17022         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17023           ToNrEvent(2*n-1);
17024           return;
17025         }
17026         // undocumented kludge: allow command-line option to be typed in!
17027         // (potentially fatal, and does not implement the effect of the option.)
17028         // should only be used for options that are values on which future decisions will be made,
17029         // and definitely not on options that would be used during initialization.
17030         if(strstr(move, "!!! -") == move) {
17031             ParseArgsFromString(move+4);
17032             return;
17033         }
17034
17035       if (gameMode != EditGame && currentMove != forwardMostMove &&
17036         gameMode != Training) {
17037         DisplayMoveError(_("Displayed move is not current"));
17038       } else {
17039         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17040           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17041         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17042         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17043           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17044           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17045         } else {
17046           DisplayMoveError(_("Could not parse move"));
17047         }
17048       }
17049 }
17050
17051 void
17052 DisplayMove (int moveNumber)
17053 {
17054     char message[MSG_SIZ];
17055     char res[MSG_SIZ];
17056     char cpThinkOutput[MSG_SIZ];
17057
17058     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17059
17060     if (moveNumber == forwardMostMove - 1 ||
17061         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17062
17063         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17064
17065         if (strchr(cpThinkOutput, '\n')) {
17066             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17067         }
17068     } else {
17069         *cpThinkOutput = NULLCHAR;
17070     }
17071
17072     /* [AS] Hide thinking from human user */
17073     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17074         *cpThinkOutput = NULLCHAR;
17075         if( thinkOutput[0] != NULLCHAR ) {
17076             int i;
17077
17078             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17079                 cpThinkOutput[i] = '.';
17080             }
17081             cpThinkOutput[i] = NULLCHAR;
17082             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17083         }
17084     }
17085
17086     if (moveNumber == forwardMostMove - 1 &&
17087         gameInfo.resultDetails != NULL) {
17088         if (gameInfo.resultDetails[0] == NULLCHAR) {
17089           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17090         } else {
17091           snprintf(res, MSG_SIZ, " {%s} %s",
17092                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17093         }
17094     } else {
17095         res[0] = NULLCHAR;
17096     }
17097
17098     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17099         DisplayMessage(res, cpThinkOutput);
17100     } else {
17101       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17102                 WhiteOnMove(moveNumber) ? " " : ".. ",
17103                 parseList[moveNumber], res);
17104         DisplayMessage(message, cpThinkOutput);
17105     }
17106 }
17107
17108 void
17109 DisplayComment (int moveNumber, char *text)
17110 {
17111     char title[MSG_SIZ];
17112
17113     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17114       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17115     } else {
17116       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17117               WhiteOnMove(moveNumber) ? " " : ".. ",
17118               parseList[moveNumber]);
17119     }
17120     if (text != NULL && (appData.autoDisplayComment || commentUp))
17121         CommentPopUp(title, text);
17122 }
17123
17124 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17125  * might be busy thinking or pondering.  It can be omitted if your
17126  * gnuchess is configured to stop thinking immediately on any user
17127  * input.  However, that gnuchess feature depends on the FIONREAD
17128  * ioctl, which does not work properly on some flavors of Unix.
17129  */
17130 void
17131 Attention (ChessProgramState *cps)
17132 {
17133 #if ATTENTION
17134     if (!cps->useSigint) return;
17135     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17136     switch (gameMode) {
17137       case MachinePlaysWhite:
17138       case MachinePlaysBlack:
17139       case TwoMachinesPlay:
17140       case IcsPlayingWhite:
17141       case IcsPlayingBlack:
17142       case AnalyzeMode:
17143       case AnalyzeFile:
17144         /* Skip if we know it isn't thinking */
17145         if (!cps->maybeThinking) return;
17146         if (appData.debugMode)
17147           fprintf(debugFP, "Interrupting %s\n", cps->which);
17148         InterruptChildProcess(cps->pr);
17149         cps->maybeThinking = FALSE;
17150         break;
17151       default:
17152         break;
17153     }
17154 #endif /*ATTENTION*/
17155 }
17156
17157 int
17158 CheckFlags ()
17159 {
17160     if (whiteTimeRemaining <= 0) {
17161         if (!whiteFlag) {
17162             whiteFlag = TRUE;
17163             if (appData.icsActive) {
17164                 if (appData.autoCallFlag &&
17165                     gameMode == IcsPlayingBlack && !blackFlag) {
17166                   SendToICS(ics_prefix);
17167                   SendToICS("flag\n");
17168                 }
17169             } else {
17170                 if (blackFlag) {
17171                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17172                 } else {
17173                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17174                     if (appData.autoCallFlag) {
17175                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17176                         return TRUE;
17177                     }
17178                 }
17179             }
17180         }
17181     }
17182     if (blackTimeRemaining <= 0) {
17183         if (!blackFlag) {
17184             blackFlag = TRUE;
17185             if (appData.icsActive) {
17186                 if (appData.autoCallFlag &&
17187                     gameMode == IcsPlayingWhite && !whiteFlag) {
17188                   SendToICS(ics_prefix);
17189                   SendToICS("flag\n");
17190                 }
17191             } else {
17192                 if (whiteFlag) {
17193                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17194                 } else {
17195                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17196                     if (appData.autoCallFlag) {
17197                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17198                         return TRUE;
17199                     }
17200                 }
17201             }
17202         }
17203     }
17204     return FALSE;
17205 }
17206
17207 void
17208 CheckTimeControl ()
17209 {
17210     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17211         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17212
17213     /*
17214      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17215      */
17216     if ( !WhiteOnMove(forwardMostMove) ) {
17217         /* White made time control */
17218         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17219         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17220         /* [HGM] time odds: correct new time quota for time odds! */
17221                                             / WhitePlayer()->timeOdds;
17222         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17223     } else {
17224         lastBlack -= blackTimeRemaining;
17225         /* Black made time control */
17226         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17227                                             / WhitePlayer()->other->timeOdds;
17228         lastWhite = whiteTimeRemaining;
17229     }
17230 }
17231
17232 void
17233 DisplayBothClocks ()
17234 {
17235     int wom = gameMode == EditPosition ?
17236       !blackPlaysFirst : WhiteOnMove(currentMove);
17237     DisplayWhiteClock(whiteTimeRemaining, wom);
17238     DisplayBlackClock(blackTimeRemaining, !wom);
17239 }
17240
17241
17242 /* Timekeeping seems to be a portability nightmare.  I think everyone
17243    has ftime(), but I'm really not sure, so I'm including some ifdefs
17244    to use other calls if you don't.  Clocks will be less accurate if
17245    you have neither ftime nor gettimeofday.
17246 */
17247
17248 /* VS 2008 requires the #include outside of the function */
17249 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17250 #include <sys/timeb.h>
17251 #endif
17252
17253 /* Get the current time as a TimeMark */
17254 void
17255 GetTimeMark (TimeMark *tm)
17256 {
17257 #if HAVE_GETTIMEOFDAY
17258
17259     struct timeval timeVal;
17260     struct timezone timeZone;
17261
17262     gettimeofday(&timeVal, &timeZone);
17263     tm->sec = (long) timeVal.tv_sec;
17264     tm->ms = (int) (timeVal.tv_usec / 1000L);
17265
17266 #else /*!HAVE_GETTIMEOFDAY*/
17267 #if HAVE_FTIME
17268
17269 // include <sys/timeb.h> / moved to just above start of function
17270     struct timeb timeB;
17271
17272     ftime(&timeB);
17273     tm->sec = (long) timeB.time;
17274     tm->ms = (int) timeB.millitm;
17275
17276 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17277     tm->sec = (long) time(NULL);
17278     tm->ms = 0;
17279 #endif
17280 #endif
17281 }
17282
17283 /* Return the difference in milliseconds between two
17284    time marks.  We assume the difference will fit in a long!
17285 */
17286 long
17287 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17288 {
17289     return 1000L*(tm2->sec - tm1->sec) +
17290            (long) (tm2->ms - tm1->ms);
17291 }
17292
17293
17294 /*
17295  * Code to manage the game clocks.
17296  *
17297  * In tournament play, black starts the clock and then white makes a move.
17298  * We give the human user a slight advantage if he is playing white---the
17299  * clocks don't run until he makes his first move, so it takes zero time.
17300  * Also, we don't account for network lag, so we could get out of sync
17301  * with GNU Chess's clock -- but then, referees are always right.
17302  */
17303
17304 static TimeMark tickStartTM;
17305 static long intendedTickLength;
17306
17307 long
17308 NextTickLength (long timeRemaining)
17309 {
17310     long nominalTickLength, nextTickLength;
17311
17312     if (timeRemaining > 0L && timeRemaining <= 10000L)
17313       nominalTickLength = 100L;
17314     else
17315       nominalTickLength = 1000L;
17316     nextTickLength = timeRemaining % nominalTickLength;
17317     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17318
17319     return nextTickLength;
17320 }
17321
17322 /* Adjust clock one minute up or down */
17323 void
17324 AdjustClock (Boolean which, int dir)
17325 {
17326     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17327     if(which) blackTimeRemaining += 60000*dir;
17328     else      whiteTimeRemaining += 60000*dir;
17329     DisplayBothClocks();
17330     adjustedClock = TRUE;
17331 }
17332
17333 /* Stop clocks and reset to a fresh time control */
17334 void
17335 ResetClocks ()
17336 {
17337     (void) StopClockTimer();
17338     if (appData.icsActive) {
17339         whiteTimeRemaining = blackTimeRemaining = 0;
17340     } else if (searchTime) {
17341         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17342         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17343     } else { /* [HGM] correct new time quote for time odds */
17344         whiteTC = blackTC = fullTimeControlString;
17345         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17346         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17347     }
17348     if (whiteFlag || blackFlag) {
17349         DisplayTitle("");
17350         whiteFlag = blackFlag = FALSE;
17351     }
17352     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17353     DisplayBothClocks();
17354     adjustedClock = FALSE;
17355 }
17356
17357 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17358
17359 /* Decrement running clock by amount of time that has passed */
17360 void
17361 DecrementClocks ()
17362 {
17363     long timeRemaining;
17364     long lastTickLength, fudge;
17365     TimeMark now;
17366
17367     if (!appData.clockMode) return;
17368     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17369
17370     GetTimeMark(&now);
17371
17372     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17373
17374     /* Fudge if we woke up a little too soon */
17375     fudge = intendedTickLength - lastTickLength;
17376     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17377
17378     if (WhiteOnMove(forwardMostMove)) {
17379         if(whiteNPS >= 0) lastTickLength = 0;
17380         timeRemaining = whiteTimeRemaining -= lastTickLength;
17381         if(timeRemaining < 0 && !appData.icsActive) {
17382             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17383             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17384                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17385                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17386             }
17387         }
17388         DisplayWhiteClock(whiteTimeRemaining - fudge,
17389                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17390     } else {
17391         if(blackNPS >= 0) lastTickLength = 0;
17392         timeRemaining = blackTimeRemaining -= lastTickLength;
17393         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17394             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17395             if(suddenDeath) {
17396                 blackStartMove = forwardMostMove;
17397                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17398             }
17399         }
17400         DisplayBlackClock(blackTimeRemaining - fudge,
17401                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17402     }
17403     if (CheckFlags()) return;
17404
17405     if(twoBoards) { // count down secondary board's clocks as well
17406         activePartnerTime -= lastTickLength;
17407         partnerUp = 1;
17408         if(activePartner == 'W')
17409             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17410         else
17411             DisplayBlackClock(activePartnerTime, TRUE);
17412         partnerUp = 0;
17413     }
17414
17415     tickStartTM = now;
17416     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17417     StartClockTimer(intendedTickLength);
17418
17419     /* if the time remaining has fallen below the alarm threshold, sound the
17420      * alarm. if the alarm has sounded and (due to a takeback or time control
17421      * with increment) the time remaining has increased to a level above the
17422      * threshold, reset the alarm so it can sound again.
17423      */
17424
17425     if (appData.icsActive && appData.icsAlarm) {
17426
17427         /* make sure we are dealing with the user's clock */
17428         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17429                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17430            )) return;
17431
17432         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17433             alarmSounded = FALSE;
17434         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17435             PlayAlarmSound();
17436             alarmSounded = TRUE;
17437         }
17438     }
17439 }
17440
17441
17442 /* A player has just moved, so stop the previously running
17443    clock and (if in clock mode) start the other one.
17444    We redisplay both clocks in case we're in ICS mode, because
17445    ICS gives us an update to both clocks after every move.
17446    Note that this routine is called *after* forwardMostMove
17447    is updated, so the last fractional tick must be subtracted
17448    from the color that is *not* on move now.
17449 */
17450 void
17451 SwitchClocks (int newMoveNr)
17452 {
17453     long lastTickLength;
17454     TimeMark now;
17455     int flagged = FALSE;
17456
17457     GetTimeMark(&now);
17458
17459     if (StopClockTimer() && appData.clockMode) {
17460         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17461         if (!WhiteOnMove(forwardMostMove)) {
17462             if(blackNPS >= 0) lastTickLength = 0;
17463             blackTimeRemaining -= lastTickLength;
17464            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17465 //         if(pvInfoList[forwardMostMove].time == -1)
17466                  pvInfoList[forwardMostMove].time =               // use GUI time
17467                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17468         } else {
17469            if(whiteNPS >= 0) lastTickLength = 0;
17470            whiteTimeRemaining -= lastTickLength;
17471            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17472 //         if(pvInfoList[forwardMostMove].time == -1)
17473                  pvInfoList[forwardMostMove].time =
17474                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17475         }
17476         flagged = CheckFlags();
17477     }
17478     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17479     CheckTimeControl();
17480
17481     if (flagged || !appData.clockMode) return;
17482
17483     switch (gameMode) {
17484       case MachinePlaysBlack:
17485       case MachinePlaysWhite:
17486       case BeginningOfGame:
17487         if (pausing) return;
17488         break;
17489
17490       case EditGame:
17491       case PlayFromGameFile:
17492       case IcsExamining:
17493         return;
17494
17495       default:
17496         break;
17497     }
17498
17499     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17500         if(WhiteOnMove(forwardMostMove))
17501              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17502         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17503     }
17504
17505     tickStartTM = now;
17506     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17507       whiteTimeRemaining : blackTimeRemaining);
17508     StartClockTimer(intendedTickLength);
17509 }
17510
17511
17512 /* Stop both clocks */
17513 void
17514 StopClocks ()
17515 {
17516     long lastTickLength;
17517     TimeMark now;
17518
17519     if (!StopClockTimer()) return;
17520     if (!appData.clockMode) return;
17521
17522     GetTimeMark(&now);
17523
17524     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17525     if (WhiteOnMove(forwardMostMove)) {
17526         if(whiteNPS >= 0) lastTickLength = 0;
17527         whiteTimeRemaining -= lastTickLength;
17528         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17529     } else {
17530         if(blackNPS >= 0) lastTickLength = 0;
17531         blackTimeRemaining -= lastTickLength;
17532         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17533     }
17534     CheckFlags();
17535 }
17536
17537 /* Start clock of player on move.  Time may have been reset, so
17538    if clock is already running, stop and restart it. */
17539 void
17540 StartClocks ()
17541 {
17542     (void) StopClockTimer(); /* in case it was running already */
17543     DisplayBothClocks();
17544     if (CheckFlags()) return;
17545
17546     if (!appData.clockMode) return;
17547     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17548
17549     GetTimeMark(&tickStartTM);
17550     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17551       whiteTimeRemaining : blackTimeRemaining);
17552
17553    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17554     whiteNPS = blackNPS = -1;
17555     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17556        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17557         whiteNPS = first.nps;
17558     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17559        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17560         blackNPS = first.nps;
17561     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17562         whiteNPS = second.nps;
17563     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17564         blackNPS = second.nps;
17565     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17566
17567     StartClockTimer(intendedTickLength);
17568 }
17569
17570 char *
17571 TimeString (long ms)
17572 {
17573     long second, minute, hour, day;
17574     char *sign = "";
17575     static char buf[32];
17576
17577     if (ms > 0 && ms <= 9900) {
17578       /* convert milliseconds to tenths, rounding up */
17579       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17580
17581       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17582       return buf;
17583     }
17584
17585     /* convert milliseconds to seconds, rounding up */
17586     /* use floating point to avoid strangeness of integer division
17587        with negative dividends on many machines */
17588     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17589
17590     if (second < 0) {
17591         sign = "-";
17592         second = -second;
17593     }
17594
17595     day = second / (60 * 60 * 24);
17596     second = second % (60 * 60 * 24);
17597     hour = second / (60 * 60);
17598     second = second % (60 * 60);
17599     minute = second / 60;
17600     second = second % 60;
17601
17602     if (day > 0)
17603       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17604               sign, day, hour, minute, second);
17605     else if (hour > 0)
17606       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17607     else
17608       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17609
17610     return buf;
17611 }
17612
17613
17614 /*
17615  * This is necessary because some C libraries aren't ANSI C compliant yet.
17616  */
17617 char *
17618 StrStr (char *string, char *match)
17619 {
17620     int i, length;
17621
17622     length = strlen(match);
17623
17624     for (i = strlen(string) - length; i >= 0; i--, string++)
17625       if (!strncmp(match, string, length))
17626         return string;
17627
17628     return NULL;
17629 }
17630
17631 char *
17632 StrCaseStr (char *string, char *match)
17633 {
17634     int i, j, length;
17635
17636     length = strlen(match);
17637
17638     for (i = strlen(string) - length; i >= 0; i--, string++) {
17639         for (j = 0; j < length; j++) {
17640             if (ToLower(match[j]) != ToLower(string[j]))
17641               break;
17642         }
17643         if (j == length) return string;
17644     }
17645
17646     return NULL;
17647 }
17648
17649 #ifndef _amigados
17650 int
17651 StrCaseCmp (char *s1, char *s2)
17652 {
17653     char c1, c2;
17654
17655     for (;;) {
17656         c1 = ToLower(*s1++);
17657         c2 = ToLower(*s2++);
17658         if (c1 > c2) return 1;
17659         if (c1 < c2) return -1;
17660         if (c1 == NULLCHAR) return 0;
17661     }
17662 }
17663
17664
17665 int
17666 ToLower (int c)
17667 {
17668     return isupper(c) ? tolower(c) : c;
17669 }
17670
17671
17672 int
17673 ToUpper (int c)
17674 {
17675     return islower(c) ? toupper(c) : c;
17676 }
17677 #endif /* !_amigados    */
17678
17679 char *
17680 StrSave (char *s)
17681 {
17682   char *ret;
17683
17684   if ((ret = (char *) malloc(strlen(s) + 1)))
17685     {
17686       safeStrCpy(ret, s, strlen(s)+1);
17687     }
17688   return ret;
17689 }
17690
17691 char *
17692 StrSavePtr (char *s, char **savePtr)
17693 {
17694     if (*savePtr) {
17695         free(*savePtr);
17696     }
17697     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17698       safeStrCpy(*savePtr, s, strlen(s)+1);
17699     }
17700     return(*savePtr);
17701 }
17702
17703 char *
17704 PGNDate ()
17705 {
17706     time_t clock;
17707     struct tm *tm;
17708     char buf[MSG_SIZ];
17709
17710     clock = time((time_t *)NULL);
17711     tm = localtime(&clock);
17712     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17713             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17714     return StrSave(buf);
17715 }
17716
17717
17718 char *
17719 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17720 {
17721     int i, j, fromX, fromY, toX, toY;
17722     int whiteToPlay;
17723     char buf[MSG_SIZ];
17724     char *p, *q;
17725     int emptycount;
17726     ChessSquare piece;
17727
17728     whiteToPlay = (gameMode == EditPosition) ?
17729       !blackPlaysFirst : (move % 2 == 0);
17730     p = buf;
17731
17732     /* Piece placement data */
17733     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17734         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17735         emptycount = 0;
17736         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17737             if (boards[move][i][j] == EmptySquare) {
17738                 emptycount++;
17739             } else { ChessSquare piece = boards[move][i][j];
17740                 if (emptycount > 0) {
17741                     if(emptycount<10) /* [HGM] can be >= 10 */
17742                         *p++ = '0' + emptycount;
17743                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17744                     emptycount = 0;
17745                 }
17746                 if(PieceToChar(piece) == '+') {
17747                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17748                     *p++ = '+';
17749                     piece = (ChessSquare)(CHUDEMOTED piece);
17750                 }
17751                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17752                 if(p[-1] == '~') {
17753                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17754                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17755                     *p++ = '~';
17756                 }
17757             }
17758         }
17759         if (emptycount > 0) {
17760             if(emptycount<10) /* [HGM] can be >= 10 */
17761                 *p++ = '0' + emptycount;
17762             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17763             emptycount = 0;
17764         }
17765         *p++ = '/';
17766     }
17767     *(p - 1) = ' ';
17768
17769     /* [HGM] print Crazyhouse or Shogi holdings */
17770     if( gameInfo.holdingsWidth ) {
17771         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17772         q = p;
17773         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17774             piece = boards[move][i][BOARD_WIDTH-1];
17775             if( piece != EmptySquare )
17776               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17777                   *p++ = PieceToChar(piece);
17778         }
17779         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17780             piece = boards[move][BOARD_HEIGHT-i-1][0];
17781             if( piece != EmptySquare )
17782               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17783                   *p++ = PieceToChar(piece);
17784         }
17785
17786         if( q == p ) *p++ = '-';
17787         *p++ = ']';
17788         *p++ = ' ';
17789     }
17790
17791     /* Active color */
17792     *p++ = whiteToPlay ? 'w' : 'b';
17793     *p++ = ' ';
17794
17795   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17796     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17797   } else {
17798   if(nrCastlingRights) {
17799      q = p;
17800      if(appData.fischerCastling) {
17801        /* [HGM] write directly from rights */
17802            if(boards[move][CASTLING][2] != NoRights &&
17803               boards[move][CASTLING][0] != NoRights   )
17804                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17805            if(boards[move][CASTLING][2] != NoRights &&
17806               boards[move][CASTLING][1] != NoRights   )
17807                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17808            if(boards[move][CASTLING][5] != NoRights &&
17809               boards[move][CASTLING][3] != NoRights   )
17810                 *p++ = boards[move][CASTLING][3] + AAA;
17811            if(boards[move][CASTLING][5] != NoRights &&
17812               boards[move][CASTLING][4] != NoRights   )
17813                 *p++ = boards[move][CASTLING][4] + AAA;
17814      } else {
17815
17816         /* [HGM] write true castling rights */
17817         if( nrCastlingRights == 6 ) {
17818             int q, k=0;
17819             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17820                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17821             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17822                  boards[move][CASTLING][2] != NoRights  );
17823             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17824                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17825                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17826                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17827                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17828             }
17829             if(q) *p++ = 'Q';
17830             k = 0;
17831             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17832                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17833             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17834                  boards[move][CASTLING][5] != NoRights  );
17835             if(gameInfo.variant == VariantSChess) {
17836                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17837                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17838                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17839                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17840             }
17841             if(q) *p++ = 'q';
17842         }
17843      }
17844      if (q == p) *p++ = '-'; /* No castling rights */
17845      *p++ = ' ';
17846   }
17847
17848   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17849      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17850      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17851     /* En passant target square */
17852     if (move > backwardMostMove) {
17853         fromX = moveList[move - 1][0] - AAA;
17854         fromY = moveList[move - 1][1] - ONE;
17855         toX = moveList[move - 1][2] - AAA;
17856         toY = moveList[move - 1][3] - ONE;
17857         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17858             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17859             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17860             fromX == toX) {
17861             /* 2-square pawn move just happened */
17862             *p++ = toX + AAA;
17863             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17864         } else {
17865             *p++ = '-';
17866         }
17867     } else if(move == backwardMostMove) {
17868         // [HGM] perhaps we should always do it like this, and forget the above?
17869         if((signed char)boards[move][EP_STATUS] >= 0) {
17870             *p++ = boards[move][EP_STATUS] + AAA;
17871             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17872         } else {
17873             *p++ = '-';
17874         }
17875     } else {
17876         *p++ = '-';
17877     }
17878     *p++ = ' ';
17879   }
17880   }
17881
17882     if(moveCounts)
17883     {   int i = 0, j=move;
17884
17885         /* [HGM] find reversible plies */
17886         if (appData.debugMode) { int k;
17887             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17888             for(k=backwardMostMove; k<=forwardMostMove; k++)
17889                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17890
17891         }
17892
17893         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17894         if( j == backwardMostMove ) i += initialRulePlies;
17895         sprintf(p, "%d ", i);
17896         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17897
17898         /* Fullmove number */
17899         sprintf(p, "%d", (move / 2) + 1);
17900     } else *--p = NULLCHAR;
17901
17902     return StrSave(buf);
17903 }
17904
17905 Boolean
17906 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17907 {
17908     int i, j, k, w=0, subst=0, shuffle=0;
17909     char *p, c;
17910     int emptycount, virgin[BOARD_FILES];
17911     ChessSquare piece;
17912
17913     p = fen;
17914
17915     /* Piece placement data */
17916     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17917         j = 0;
17918         for (;;) {
17919             if (*p == '/' || *p == ' ' || *p == '[' ) {
17920                 if(j > w) w = j;
17921                 emptycount = gameInfo.boardWidth - j;
17922                 while (emptycount--)
17923                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17924                 if (*p == '/') p++;
17925                 else if(autoSize) { // we stumbled unexpectedly into end of board
17926                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17927                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17928                     }
17929                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17930                 }
17931                 break;
17932 #if(BOARD_FILES >= 10)*0
17933             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17934                 p++; emptycount=10;
17935                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17936                 while (emptycount--)
17937                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17938 #endif
17939             } else if (*p == '*') {
17940                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17941             } else if (isdigit(*p)) {
17942                 emptycount = *p++ - '0';
17943                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17944                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17945                 while (emptycount--)
17946                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17947             } else if (*p == '<') {
17948                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17949                 else if (i != 0 || !shuffle) return FALSE;
17950                 p++;
17951             } else if (shuffle && *p == '>') {
17952                 p++; // for now ignore closing shuffle range, and assume rank-end
17953             } else if (*p == '?') {
17954                 if (j >= gameInfo.boardWidth) return FALSE;
17955                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17956                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17957             } else if (*p == '+' || isalpha(*p)) {
17958                 if (j >= gameInfo.boardWidth) return FALSE;
17959                 if(*p=='+') {
17960                     piece = CharToPiece(*++p);
17961                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17962                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17963                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17964                 } else piece = CharToPiece(*p++);
17965
17966                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17967                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17968                     piece = (ChessSquare) (PROMOTED piece);
17969                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17970                     p++;
17971                 }
17972                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17973             } else {
17974                 return FALSE;
17975             }
17976         }
17977     }
17978     while (*p == '/' || *p == ' ') p++;
17979
17980     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17981
17982     /* [HGM] by default clear Crazyhouse holdings, if present */
17983     if(gameInfo.holdingsWidth) {
17984        for(i=0; i<BOARD_HEIGHT; i++) {
17985            board[i][0]             = EmptySquare; /* black holdings */
17986            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17987            board[i][1]             = (ChessSquare) 0; /* black counts */
17988            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17989        }
17990     }
17991
17992     /* [HGM] look for Crazyhouse holdings here */
17993     while(*p==' ') p++;
17994     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17995         int swap=0, wcnt=0, bcnt=0;
17996         if(*p == '[') p++;
17997         if(*p == '<') swap++, p++;
17998         if(*p == '-' ) p++; /* empty holdings */ else {
17999             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18000             /* if we would allow FEN reading to set board size, we would   */
18001             /* have to add holdings and shift the board read so far here   */
18002             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18003                 p++;
18004                 if((int) piece >= (int) BlackPawn ) {
18005                     i = (int)piece - (int)BlackPawn;
18006                     i = PieceToNumber((ChessSquare)i);
18007                     if( i >= gameInfo.holdingsSize ) return FALSE;
18008                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18009                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18010                     bcnt++;
18011                 } else {
18012                     i = (int)piece - (int)WhitePawn;
18013                     i = PieceToNumber((ChessSquare)i);
18014                     if( i >= gameInfo.holdingsSize ) return FALSE;
18015                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18016                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18017                     wcnt++;
18018                 }
18019             }
18020             if(subst) { // substitute back-rank question marks by holdings pieces
18021                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18022                     int k, m, n = bcnt + 1;
18023                     if(board[0][j] == ClearBoard) {
18024                         if(!wcnt) return FALSE;
18025                         n = rand() % wcnt;
18026                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18027                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18028                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18029                             break;
18030                         }
18031                     }
18032                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18033                         if(!bcnt) return FALSE;
18034                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18035                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18036                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18037                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18038                             break;
18039                         }
18040                     }
18041                 }
18042                 subst = 0;
18043             }
18044         }
18045         if(*p == ']') p++;
18046     }
18047
18048     if(subst) return FALSE; // substitution requested, but no holdings
18049
18050     while(*p == ' ') p++;
18051
18052     /* Active color */
18053     c = *p++;
18054     if(appData.colorNickNames) {
18055       if( c == appData.colorNickNames[0] ) c = 'w'; else
18056       if( c == appData.colorNickNames[1] ) c = 'b';
18057     }
18058     switch (c) {
18059       case 'w':
18060         *blackPlaysFirst = FALSE;
18061         break;
18062       case 'b':
18063         *blackPlaysFirst = TRUE;
18064         break;
18065       default:
18066         return FALSE;
18067     }
18068
18069     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18070     /* return the extra info in global variiables             */
18071
18072     /* set defaults in case FEN is incomplete */
18073     board[EP_STATUS] = EP_UNKNOWN;
18074     for(i=0; i<nrCastlingRights; i++ ) {
18075         board[CASTLING][i] =
18076             appData.fischerCastling ? NoRights : initialRights[i];
18077     }   /* assume possible unless obviously impossible */
18078     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18079     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18080     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18081                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18082     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18083     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18084     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18085                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18086     FENrulePlies = 0;
18087
18088     while(*p==' ') p++;
18089     if(nrCastlingRights) {
18090       int fischer = 0;
18091       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18092       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18093           /* castling indicator present, so default becomes no castlings */
18094           for(i=0; i<nrCastlingRights; i++ ) {
18095                  board[CASTLING][i] = NoRights;
18096           }
18097       }
18098       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18099              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18100              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18101              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18102         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18103
18104         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18105             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18106             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18107         }
18108         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18109             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18110         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18111                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18112         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18113                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18114         switch(c) {
18115           case'K':
18116               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18117               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18118               board[CASTLING][2] = whiteKingFile;
18119               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18120               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18121               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18122               break;
18123           case'Q':
18124               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18125               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18126               board[CASTLING][2] = whiteKingFile;
18127               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18128               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18129               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18130               break;
18131           case'k':
18132               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18133               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18134               board[CASTLING][5] = blackKingFile;
18135               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18136               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18137               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18138               break;
18139           case'q':
18140               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18141               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18142               board[CASTLING][5] = blackKingFile;
18143               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18144               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18145               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18146           case '-':
18147               break;
18148           default: /* FRC castlings */
18149               if(c >= 'a') { /* black rights */
18150                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18151                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18152                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18153                   if(i == BOARD_RGHT) break;
18154                   board[CASTLING][5] = i;
18155                   c -= AAA;
18156                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18157                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18158                   if(c > i)
18159                       board[CASTLING][3] = c;
18160                   else
18161                       board[CASTLING][4] = c;
18162               } else { /* white rights */
18163                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18164                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18165                     if(board[0][i] == WhiteKing) break;
18166                   if(i == BOARD_RGHT) break;
18167                   board[CASTLING][2] = i;
18168                   c -= AAA - 'a' + 'A';
18169                   if(board[0][c] >= WhiteKing) break;
18170                   if(c > i)
18171                       board[CASTLING][0] = c;
18172                   else
18173                       board[CASTLING][1] = c;
18174               }
18175         }
18176       }
18177       for(i=0; i<nrCastlingRights; i++)
18178         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18179       if(gameInfo.variant == VariantSChess)
18180         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18181       if(fischer && shuffle) appData.fischerCastling = TRUE;
18182     if (appData.debugMode) {
18183         fprintf(debugFP, "FEN castling rights:");
18184         for(i=0; i<nrCastlingRights; i++)
18185         fprintf(debugFP, " %d", board[CASTLING][i]);
18186         fprintf(debugFP, "\n");
18187     }
18188
18189       while(*p==' ') p++;
18190     }
18191
18192     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18193
18194     /* read e.p. field in games that know e.p. capture */
18195     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18196        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18197        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18198       if(*p=='-') {
18199         p++; board[EP_STATUS] = EP_NONE;
18200       } else {
18201          char c = *p++ - AAA;
18202
18203          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18204          if(*p >= '0' && *p <='9') p++;
18205          board[EP_STATUS] = c;
18206       }
18207     }
18208
18209
18210     if(sscanf(p, "%d", &i) == 1) {
18211         FENrulePlies = i; /* 50-move ply counter */
18212         /* (The move number is still ignored)    */
18213     }
18214
18215     return TRUE;
18216 }
18217
18218 void
18219 EditPositionPasteFEN (char *fen)
18220 {
18221   if (fen != NULL) {
18222     Board initial_position;
18223
18224     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18225       DisplayError(_("Bad FEN position in clipboard"), 0);
18226       return ;
18227     } else {
18228       int savedBlackPlaysFirst = blackPlaysFirst;
18229       EditPositionEvent();
18230       blackPlaysFirst = savedBlackPlaysFirst;
18231       CopyBoard(boards[0], initial_position);
18232       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18233       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18234       DisplayBothClocks();
18235       DrawPosition(FALSE, boards[currentMove]);
18236     }
18237   }
18238 }
18239
18240 static char cseq[12] = "\\   ";
18241
18242 Boolean
18243 set_cont_sequence (char *new_seq)
18244 {
18245     int len;
18246     Boolean ret;
18247
18248     // handle bad attempts to set the sequence
18249         if (!new_seq)
18250                 return 0; // acceptable error - no debug
18251
18252     len = strlen(new_seq);
18253     ret = (len > 0) && (len < sizeof(cseq));
18254     if (ret)
18255       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18256     else if (appData.debugMode)
18257       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18258     return ret;
18259 }
18260
18261 /*
18262     reformat a source message so words don't cross the width boundary.  internal
18263     newlines are not removed.  returns the wrapped size (no null character unless
18264     included in source message).  If dest is NULL, only calculate the size required
18265     for the dest buffer.  lp argument indicats line position upon entry, and it's
18266     passed back upon exit.
18267 */
18268 int
18269 wrap (char *dest, char *src, int count, int width, int *lp)
18270 {
18271     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18272
18273     cseq_len = strlen(cseq);
18274     old_line = line = *lp;
18275     ansi = len = clen = 0;
18276
18277     for (i=0; i < count; i++)
18278     {
18279         if (src[i] == '\033')
18280             ansi = 1;
18281
18282         // if we hit the width, back up
18283         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18284         {
18285             // store i & len in case the word is too long
18286             old_i = i, old_len = len;
18287
18288             // find the end of the last word
18289             while (i && src[i] != ' ' && src[i] != '\n')
18290             {
18291                 i--;
18292                 len--;
18293             }
18294
18295             // word too long?  restore i & len before splitting it
18296             if ((old_i-i+clen) >= width)
18297             {
18298                 i = old_i;
18299                 len = old_len;
18300             }
18301
18302             // extra space?
18303             if (i && src[i-1] == ' ')
18304                 len--;
18305
18306             if (src[i] != ' ' && src[i] != '\n')
18307             {
18308                 i--;
18309                 if (len)
18310                     len--;
18311             }
18312
18313             // now append the newline and continuation sequence
18314             if (dest)
18315                 dest[len] = '\n';
18316             len++;
18317             if (dest)
18318                 strncpy(dest+len, cseq, cseq_len);
18319             len += cseq_len;
18320             line = cseq_len;
18321             clen = cseq_len;
18322             continue;
18323         }
18324
18325         if (dest)
18326             dest[len] = src[i];
18327         len++;
18328         if (!ansi)
18329             line++;
18330         if (src[i] == '\n')
18331             line = 0;
18332         if (src[i] == 'm')
18333             ansi = 0;
18334     }
18335     if (dest && appData.debugMode)
18336     {
18337         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18338             count, width, line, len, *lp);
18339         show_bytes(debugFP, src, count);
18340         fprintf(debugFP, "\ndest: ");
18341         show_bytes(debugFP, dest, len);
18342         fprintf(debugFP, "\n");
18343     }
18344     *lp = dest ? line : old_line;
18345
18346     return len;
18347 }
18348
18349 // [HGM] vari: routines for shelving variations
18350 Boolean modeRestore = FALSE;
18351
18352 void
18353 PushInner (int firstMove, int lastMove)
18354 {
18355         int i, j, nrMoves = lastMove - firstMove;
18356
18357         // push current tail of game on stack
18358         savedResult[storedGames] = gameInfo.result;
18359         savedDetails[storedGames] = gameInfo.resultDetails;
18360         gameInfo.resultDetails = NULL;
18361         savedFirst[storedGames] = firstMove;
18362         savedLast [storedGames] = lastMove;
18363         savedFramePtr[storedGames] = framePtr;
18364         framePtr -= nrMoves; // reserve space for the boards
18365         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18366             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18367             for(j=0; j<MOVE_LEN; j++)
18368                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18369             for(j=0; j<2*MOVE_LEN; j++)
18370                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18371             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18372             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18373             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18374             pvInfoList[firstMove+i-1].depth = 0;
18375             commentList[framePtr+i] = commentList[firstMove+i];
18376             commentList[firstMove+i] = NULL;
18377         }
18378
18379         storedGames++;
18380         forwardMostMove = firstMove; // truncate game so we can start variation
18381 }
18382
18383 void
18384 PushTail (int firstMove, int lastMove)
18385 {
18386         if(appData.icsActive) { // only in local mode
18387                 forwardMostMove = currentMove; // mimic old ICS behavior
18388                 return;
18389         }
18390         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18391
18392         PushInner(firstMove, lastMove);
18393         if(storedGames == 1) GreyRevert(FALSE);
18394         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18395 }
18396
18397 void
18398 PopInner (Boolean annotate)
18399 {
18400         int i, j, nrMoves;
18401         char buf[8000], moveBuf[20];
18402
18403         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18404         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18405         nrMoves = savedLast[storedGames] - currentMove;
18406         if(annotate) {
18407                 int cnt = 10;
18408                 if(!WhiteOnMove(currentMove))
18409                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18410                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18411                 for(i=currentMove; i<forwardMostMove; i++) {
18412                         if(WhiteOnMove(i))
18413                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18414                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18415                         strcat(buf, moveBuf);
18416                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18417                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18418                 }
18419                 strcat(buf, ")");
18420         }
18421         for(i=1; i<=nrMoves; i++) { // copy last variation back
18422             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18423             for(j=0; j<MOVE_LEN; j++)
18424                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18425             for(j=0; j<2*MOVE_LEN; j++)
18426                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18427             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18428             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18429             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18430             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18431             commentList[currentMove+i] = commentList[framePtr+i];
18432             commentList[framePtr+i] = NULL;
18433         }
18434         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18435         framePtr = savedFramePtr[storedGames];
18436         gameInfo.result = savedResult[storedGames];
18437         if(gameInfo.resultDetails != NULL) {
18438             free(gameInfo.resultDetails);
18439       }
18440         gameInfo.resultDetails = savedDetails[storedGames];
18441         forwardMostMove = currentMove + nrMoves;
18442 }
18443
18444 Boolean
18445 PopTail (Boolean annotate)
18446 {
18447         if(appData.icsActive) return FALSE; // only in local mode
18448         if(!storedGames) return FALSE; // sanity
18449         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18450
18451         PopInner(annotate);
18452         if(currentMove < forwardMostMove) ForwardEvent(); else
18453         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18454
18455         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18456         return TRUE;
18457 }
18458
18459 void
18460 CleanupTail ()
18461 {       // remove all shelved variations
18462         int i;
18463         for(i=0; i<storedGames; i++) {
18464             if(savedDetails[i])
18465                 free(savedDetails[i]);
18466             savedDetails[i] = NULL;
18467         }
18468         for(i=framePtr; i<MAX_MOVES; i++) {
18469                 if(commentList[i]) free(commentList[i]);
18470                 commentList[i] = NULL;
18471         }
18472         framePtr = MAX_MOVES-1;
18473         storedGames = 0;
18474 }
18475
18476 void
18477 LoadVariation (int index, char *text)
18478 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18479         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18480         int level = 0, move;
18481
18482         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18483         // first find outermost bracketing variation
18484         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18485             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18486                 if(*p == '{') wait = '}'; else
18487                 if(*p == '[') wait = ']'; else
18488                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18489                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18490             }
18491             if(*p == wait) wait = NULLCHAR; // closing ]} found
18492             p++;
18493         }
18494         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18495         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18496         end[1] = NULLCHAR; // clip off comment beyond variation
18497         ToNrEvent(currentMove-1);
18498         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18499         // kludge: use ParsePV() to append variation to game
18500         move = currentMove;
18501         ParsePV(start, TRUE, TRUE);
18502         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18503         ClearPremoveHighlights();
18504         CommentPopDown();
18505         ToNrEvent(currentMove+1);
18506 }
18507
18508 void
18509 LoadTheme ()
18510 {
18511     char *p, *q, buf[MSG_SIZ];
18512     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18513         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18514         ParseArgsFromString(buf);
18515         ActivateTheme(TRUE); // also redo colors
18516         return;
18517     }
18518     p = nickName;
18519     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18520     {
18521         int len;
18522         q = appData.themeNames;
18523         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18524       if(appData.useBitmaps) {
18525         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18526                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18527                 appData.liteBackTextureMode,
18528                 appData.darkBackTextureMode );
18529       } else {
18530         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18531                 Col2Text(2),   // lightSquareColor
18532                 Col2Text(3) ); // darkSquareColor
18533       }
18534       if(appData.useBorder) {
18535         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18536                 appData.border);
18537       } else {
18538         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18539       }
18540       if(appData.useFont) {
18541         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18542                 appData.renderPiecesWithFont,
18543                 appData.fontToPieceTable,
18544                 Col2Text(9),    // appData.fontBackColorWhite
18545                 Col2Text(10) ); // appData.fontForeColorBlack
18546       } else {
18547         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18548                 appData.pieceDirectory);
18549         if(!appData.pieceDirectory[0])
18550           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18551                 Col2Text(0),   // whitePieceColor
18552                 Col2Text(1) ); // blackPieceColor
18553       }
18554       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18555                 Col2Text(4),   // highlightSquareColor
18556                 Col2Text(5) ); // premoveHighlightColor
18557         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18558         if(insert != q) insert[-1] = NULLCHAR;
18559         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18560         if(q)   free(q);
18561     }
18562     ActivateTheme(FALSE);
18563 }