Silence two warnings
[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, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
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 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (int index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         return;
1000     }
1001     p = engineName;
1002     while(q = strchr(p, SLASH)) p = q+1;
1003     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004     if(engineDir[0] != NULLCHAR) {
1005         ASSIGN(appData.directory[i], engineDir); p = engineName;
1006     } else if(p != engineName) { // derive directory from engine path, when not given
1007         p[-1] = 0;
1008         ASSIGN(appData.directory[i], engineName);
1009         p[-1] = SLASH;
1010         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011     } else { ASSIGN(appData.directory[i], "."); }
1012     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013     if(params[0]) {
1014         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015         snprintf(command, MSG_SIZ, "%s %s", p, params);
1016         p = command;
1017     }
1018     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019     ASSIGN(appData.chessProgram[i], p);
1020     appData.isUCI[i] = isUCI;
1021     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022     appData.hasOwnBookUCI[i] = hasBook;
1023     if(!nickName[0]) useNick = FALSE;
1024     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1025     if(addToList) {
1026         int len;
1027         char quote;
1028         q = firstChessProgramNames;
1029         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032                         quote, p, quote, appData.directory[i],
1033                         useNick ? " -fn \"" : "",
1034                         useNick ? nickName : "",
1035                         useNick ? "\"" : "",
1036                         v1 ? " -firstProtocolVersion 1" : "",
1037                         hasBook ? "" : " -fNoOwnBookUCI",
1038                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039                         storeVariant ? " -variant " : "",
1040                         storeVariant ? VariantName(gameInfo.variant) : "");
1041         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043         if(insert != q) insert[-1] = NULLCHAR;
1044         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045         if(q)   free(q);
1046         FloatToFront(&appData.recentEngineList, buf);
1047     }
1048     ReplaceEngine(cps, i);
1049 }
1050
1051 void
1052 InitTimeControls ()
1053 {
1054     int matched, min, sec;
1055     /*
1056      * Parse timeControl resource
1057      */
1058     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059                           appData.movesPerSession)) {
1060         char buf[MSG_SIZ];
1061         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062         DisplayFatalError(buf, 0, 2);
1063     }
1064
1065     /*
1066      * Parse searchTime resource
1067      */
1068     if (*appData.searchTime != NULLCHAR) {
1069         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070         if (matched == 1) {
1071             searchTime = min * 60;
1072         } else if (matched == 2) {
1073             searchTime = min * 60 + sec;
1074         } else {
1075             char buf[MSG_SIZ];
1076             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077             DisplayFatalError(buf, 0, 2);
1078         }
1079     }
1080 }
1081
1082 void
1083 InitBackEnd1 ()
1084 {
1085
1086     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088
1089     GetTimeMark(&programStartTime);
1090     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091     appData.seedBase = random() + (random()<<15);
1092     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093
1094     ClearProgramStats();
1095     programStats.ok_to_send = 1;
1096     programStats.seen_stat = 0;
1097
1098     /*
1099      * Initialize game list
1100      */
1101     ListNew(&gameList);
1102
1103
1104     /*
1105      * Internet chess server status
1106      */
1107     if (appData.icsActive) {
1108         appData.matchMode = FALSE;
1109         appData.matchGames = 0;
1110 #if ZIPPY
1111         appData.noChessProgram = !appData.zippyPlay;
1112 #else
1113         appData.zippyPlay = FALSE;
1114         appData.zippyTalk = FALSE;
1115         appData.noChessProgram = TRUE;
1116 #endif
1117         if (*appData.icsHelper != NULLCHAR) {
1118             appData.useTelnet = TRUE;
1119             appData.telnetProgram = appData.icsHelper;
1120         }
1121     } else {
1122         appData.zippyTalk = appData.zippyPlay = FALSE;
1123     }
1124
1125     /* [AS] Initialize pv info list [HGM] and game state */
1126     {
1127         int i, j;
1128
1129         for( i=0; i<=framePtr; i++ ) {
1130             pvInfoList[i].depth = -1;
1131             boards[i][EP_STATUS] = EP_NONE;
1132             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1133         }
1134     }
1135
1136     InitTimeControls();
1137
1138     /* [AS] Adjudication threshold */
1139     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140
1141     InitEngine(&first, 0);
1142     InitEngine(&second, 1);
1143     CommonEngineInit();
1144
1145     pairing.which = "pairing"; // pairing engine
1146     pairing.pr = NoProc;
1147     pairing.isr = NULL;
1148     pairing.program = appData.pairingEngine;
1149     pairing.host = "localhost";
1150     pairing.dir = ".";
1151
1152     if (appData.icsActive) {
1153         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1154     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155         appData.clockMode = FALSE;
1156         first.sendTime = second.sendTime = 0;
1157     }
1158
1159 #if ZIPPY
1160     /* Override some settings from environment variables, for backward
1161        compatibility.  Unfortunately it's not feasible to have the env
1162        vars just set defaults, at least in xboard.  Ugh.
1163     */
1164     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1165       ZippyInit();
1166     }
1167 #endif
1168
1169     if (!appData.icsActive) {
1170       char buf[MSG_SIZ];
1171       int len;
1172
1173       /* Check for variants that are supported only in ICS mode,
1174          or not at all.  Some that are accepted here nevertheless
1175          have bugs; see comments below.
1176       */
1177       VariantClass variant = StringToVariant(appData.variant);
1178       switch (variant) {
1179       case VariantBughouse:     /* need four players and two boards */
1180       case VariantKriegspiel:   /* need to hide pieces and move details */
1181         /* case VariantFischeRandom: (Fabien: moved below) */
1182         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183         if( (len >= MSG_SIZ) && appData.debugMode )
1184           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185
1186         DisplayFatalError(buf, 0, 2);
1187         return;
1188
1189       case VariantUnknown:
1190       case VariantLoadable:
1191       case Variant29:
1192       case Variant30:
1193       case Variant31:
1194       case Variant32:
1195       case Variant33:
1196       case Variant34:
1197       case Variant35:
1198       case Variant36:
1199       default:
1200         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201         if( (len >= MSG_SIZ) && appData.debugMode )
1202           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203
1204         DisplayFatalError(buf, 0, 2);
1205         return;
1206
1207       case VariantNormal:     /* definitely works! */
1208         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1210           return;
1211         }
1212       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1213       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1214       case VariantGothic:     /* [HGM] should work */
1215       case VariantCapablanca: /* [HGM] should work */
1216       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1217       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1218       case VariantChu:        /* [HGM] experimental */
1219       case VariantKnightmate: /* [HGM] should work */
1220       case VariantCylinder:   /* [HGM] untested */
1221       case VariantFalcon:     /* [HGM] untested */
1222       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223                                  offboard interposition not understood */
1224       case VariantWildCastle: /* pieces not automatically shuffled */
1225       case VariantNoCastle:   /* pieces not automatically shuffled */
1226       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227       case VariantLosers:     /* should work except for win condition,
1228                                  and doesn't know captures are mandatory */
1229       case VariantSuicide:    /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantGiveaway:   /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantTwoKings:   /* should work */
1234       case VariantAtomic:     /* should work except for win condition */
1235       case Variant3Check:     /* should work except for win condition */
1236       case VariantShatranj:   /* should work except for all win conditions */
1237       case VariantMakruk:     /* should work except for draw countdown */
1238       case VariantASEAN :     /* should work except for draw countdown */
1239       case VariantBerolina:   /* might work if TestLegality is off */
1240       case VariantCapaRandom: /* should work */
1241       case VariantJanus:      /* should work */
1242       case VariantSuper:      /* experimental */
1243       case VariantGreat:      /* experimental, requires legality testing to be off */
1244       case VariantSChess:     /* S-Chess, should work */
1245       case VariantGrand:      /* should work */
1246       case VariantSpartan:    /* should work */
1247       case VariantLion:       /* should work */
1248       case VariantChuChess:   /* should work */
1249         break;
1250       }
1251     }
1252
1253 }
1254
1255 int
1256 NextIntegerFromString (char ** str, long * value)
1257 {
1258     int result = -1;
1259     char * s = *str;
1260
1261     while( *s == ' ' || *s == '\t' ) {
1262         s++;
1263     }
1264
1265     *value = 0;
1266
1267     if( *s >= '0' && *s <= '9' ) {
1268         while( *s >= '0' && *s <= '9' ) {
1269             *value = *value * 10 + (*s - '0');
1270             s++;
1271         }
1272
1273         result = 0;
1274     }
1275
1276     *str = s;
1277
1278     return result;
1279 }
1280
1281 int
1282 NextTimeControlFromString (char ** str, long * value)
1283 {
1284     long temp;
1285     int result = NextIntegerFromString( str, &temp );
1286
1287     if( result == 0 ) {
1288         *value = temp * 60; /* Minutes */
1289         if( **str == ':' ) {
1290             (*str)++;
1291             result = NextIntegerFromString( str, &temp );
1292             *value += temp; /* Seconds */
1293         }
1294     }
1295
1296     return result;
1297 }
1298
1299 int
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302     int result = -1, type = 0; long temp, temp2;
1303
1304     if(**str != ':') return -1; // old params remain in force!
1305     (*str)++;
1306     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307     if( NextIntegerFromString( str, &temp ) ) return -1;
1308     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1309
1310     if(**str != '/') {
1311         /* time only: incremental or sudden-death time control */
1312         if(**str == '+') { /* increment follows; read it */
1313             (*str)++;
1314             if(**str == '!') type = *(*str)++; // Bronstein TC
1315             if(result = NextIntegerFromString( str, &temp2)) return -1;
1316             *inc = temp2 * 1000;
1317             if(**str == '.') { // read fraction of increment
1318                 char *start = ++(*str);
1319                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320                 temp2 *= 1000;
1321                 while(start++ < *str) temp2 /= 10;
1322                 *inc += temp2;
1323             }
1324         } else *inc = 0;
1325         *moves = 0; *tc = temp * 1000; *incType = type;
1326         return 0;
1327     }
1328
1329     (*str)++; /* classical time control */
1330     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1331
1332     if(result == 0) {
1333         *moves = temp;
1334         *tc    = temp2 * 1000;
1335         *inc   = 0;
1336         *incType = type;
1337     }
1338     return result;
1339 }
1340
1341 int
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 {   /* [HGM] get time to add from the multi-session time-control string */
1344     int incType, moves=1; /* kludge to force reading of first session */
1345     long time, increment;
1346     char *s = tcString;
1347
1348     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349     do {
1350         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352         if(movenr == -1) return time;    /* last move before new session     */
1353         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355         if(!moves) return increment;     /* current session is incremental   */
1356         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357     } while(movenr >= -1);               /* try again for next session       */
1358
1359     return 0; // no new time quota on this move
1360 }
1361
1362 int
1363 ParseTimeControl (char *tc, float ti, int mps)
1364 {
1365   long tc1;
1366   long tc2;
1367   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1368   int min, sec=0;
1369
1370   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1373   if(ti > 0) {
1374
1375     if(mps)
1376       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377     else
1378       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1379   } else {
1380     if(mps)
1381       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382     else
1383       snprintf(buf, MSG_SIZ, ":%s", mytc);
1384   }
1385   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386
1387   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1388     return FALSE;
1389   }
1390
1391   if( *tc == '/' ) {
1392     /* Parse second time control */
1393     tc++;
1394
1395     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1396       return FALSE;
1397     }
1398
1399     if( tc2 == 0 ) {
1400       return FALSE;
1401     }
1402
1403     timeControl_2 = tc2 * 1000;
1404   }
1405   else {
1406     timeControl_2 = 0;
1407   }
1408
1409   if( tc1 == 0 ) {
1410     return FALSE;
1411   }
1412
1413   timeControl = tc1 * 1000;
1414
1415   if (ti >= 0) {
1416     timeIncrement = ti * 1000;  /* convert to ms */
1417     movesPerSession = 0;
1418   } else {
1419     timeIncrement = 0;
1420     movesPerSession = mps;
1421   }
1422   return TRUE;
1423 }
1424
1425 void
1426 InitBackEnd2 ()
1427 {
1428     if (appData.debugMode) {
1429 #    ifdef __GIT_VERSION
1430       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 #    else
1432       fprintf(debugFP, "Version: %s\n", programVersion);
1433 #    endif
1434     }
1435     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436
1437     set_cont_sequence(appData.wrapContSeq);
1438     if (appData.matchGames > 0) {
1439         appData.matchMode = TRUE;
1440     } else if (appData.matchMode) {
1441         appData.matchGames = 1;
1442     }
1443     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444         appData.matchGames = appData.sameColorGames;
1445     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1448     }
1449     Reset(TRUE, FALSE);
1450     if (appData.noChessProgram || first.protocolVersion == 1) {
1451       InitBackEnd3();
1452     } else {
1453       /* kludge: allow timeout for initial "feature" commands */
1454       FreezeUI();
1455       DisplayMessage("", _("Starting chess program"));
1456       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1457     }
1458 }
1459
1460 int
1461 CalculateIndex (int index, int gameNr)
1462 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463     int res;
1464     if(index > 0) return index; // fixed nmber
1465     if(index == 0) return 1;
1466     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1468     return res;
1469 }
1470
1471 int
1472 LoadGameOrPosition (int gameNr)
1473 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474     if (*appData.loadGameFile != NULLCHAR) {
1475         if (!LoadGameFromFile(appData.loadGameFile,
1476                 CalculateIndex(appData.loadGameIndex, gameNr),
1477                               appData.loadGameFile, FALSE)) {
1478             DisplayFatalError(_("Bad game file"), 0, 1);
1479             return 0;
1480         }
1481     } else if (*appData.loadPositionFile != NULLCHAR) {
1482         if (!LoadPositionFromFile(appData.loadPositionFile,
1483                 CalculateIndex(appData.loadPositionIndex, gameNr),
1484                                   appData.loadPositionFile)) {
1485             DisplayFatalError(_("Bad position file"), 0, 1);
1486             return 0;
1487         }
1488     }
1489     return 1;
1490 }
1491
1492 void
1493 ReserveGame (int gameNr, char resChar)
1494 {
1495     FILE *tf = fopen(appData.tourneyFile, "r+");
1496     char *p, *q, c, buf[MSG_SIZ];
1497     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498     safeStrCpy(buf, lastMsg, MSG_SIZ);
1499     DisplayMessage(_("Pick new game"), "");
1500     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501     ParseArgsFromFile(tf);
1502     p = q = appData.results;
1503     if(appData.debugMode) {
1504       char *r = appData.participants;
1505       fprintf(debugFP, "results = '%s'\n", p);
1506       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507       fprintf(debugFP, "\n");
1508     }
1509     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510     nextGame = q - p;
1511     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512     safeStrCpy(q, p, strlen(p) + 2);
1513     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1517         q[nextGame] = '*';
1518     }
1519     fseek(tf, -(strlen(p)+4), SEEK_END);
1520     c = fgetc(tf);
1521     if(c != '"') // depending on DOS or Unix line endings we can be one off
1522          fseek(tf, -(strlen(p)+2), SEEK_END);
1523     else fseek(tf, -(strlen(p)+3), SEEK_END);
1524     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525     DisplayMessage(buf, "");
1526     free(p); appData.results = q;
1527     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529       int round = appData.defaultMatchGames * appData.tourneyType;
1530       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1531          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532         UnloadEngine(&first);  // next game belongs to other pairing;
1533         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534     }
1535     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1536 }
1537
1538 void
1539 MatchEvent (int mode)
1540 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541         int dummy;
1542         if(matchMode) { // already in match mode: switch it off
1543             abortMatch = TRUE;
1544             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1545             return;
1546         }
1547 //      if(gameMode != BeginningOfGame) {
1548 //          DisplayError(_("You can only start a match from the initial position."), 0);
1549 //          return;
1550 //      }
1551         abortMatch = FALSE;
1552         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553         /* Set up machine vs. machine match */
1554         nextGame = 0;
1555         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556         if(appData.tourneyFile[0]) {
1557             ReserveGame(-1, 0);
1558             if(nextGame > appData.matchGames) {
1559                 char buf[MSG_SIZ];
1560                 if(strchr(appData.results, '*') == NULL) {
1561                     FILE *f;
1562                     appData.tourneyCycles++;
1563                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564                         fclose(f);
1565                         NextTourneyGame(-1, &dummy);
1566                         ReserveGame(-1, 0);
1567                         if(nextGame <= appData.matchGames) {
1568                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569                             matchMode = mode;
1570                             ScheduleDelayedEvent(NextMatchGame, 10000);
1571                             return;
1572                         }
1573                     }
1574                 }
1575                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576                 DisplayError(buf, 0);
1577                 appData.tourneyFile[0] = 0;
1578                 return;
1579             }
1580         } else
1581         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1582             DisplayFatalError(_("Can't have a match with no chess programs"),
1583                               0, 2);
1584             return;
1585         }
1586         matchMode = mode;
1587         matchGame = roundNr = 1;
1588         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1589         NextMatchGame();
1590 }
1591
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1593
1594 void
1595 InitBackEnd3 P((void))
1596 {
1597     GameMode initialMode;
1598     char buf[MSG_SIZ];
1599     int err, len;
1600
1601     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1602        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1603         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1604        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1605        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606         char c, *q = first.variants, *p = strchr(q, ',');
1607         if(p) *p = NULLCHAR;
1608         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609             int w, h, s;
1610             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613             Reset(TRUE, FALSE);         // and re-initialize
1614         }
1615         if(p) *p = ',';
1616     }
1617
1618     InitChessProgram(&first, startedFromSetupPosition);
1619
1620     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1621         free(programVersion);
1622         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1625     }
1626
1627     if (appData.icsActive) {
1628 #ifdef WIN32
1629         /* [DM] Make a console window if needed [HGM] merged ifs */
1630         ConsoleCreate();
1631 #endif
1632         err = establish();
1633         if (err != 0)
1634           {
1635             if (*appData.icsCommPort != NULLCHAR)
1636               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637                              appData.icsCommPort);
1638             else
1639               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640                         appData.icsHost, appData.icsPort);
1641
1642             if( (len >= MSG_SIZ) && appData.debugMode )
1643               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644
1645             DisplayFatalError(buf, err, 1);
1646             return;
1647         }
1648         SetICSMode();
1649         telnetISR =
1650           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651         fromUserISR =
1652           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655     } else if (appData.noChessProgram) {
1656         SetNCPMode();
1657     } else {
1658         SetGNUMode();
1659     }
1660
1661     if (*appData.cmailGameName != NULLCHAR) {
1662         SetCmailMode();
1663         OpenLoopback(&cmailPR);
1664         cmailISR =
1665           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1666     }
1667
1668     ThawUI();
1669     DisplayMessage("", "");
1670     if (StrCaseCmp(appData.initialMode, "") == 0) {
1671       initialMode = BeginningOfGame;
1672       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1676         ModeHighlight();
1677       }
1678     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679       initialMode = TwoMachinesPlay;
1680     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681       initialMode = AnalyzeFile;
1682     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683       initialMode = AnalyzeMode;
1684     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685       initialMode = MachinePlaysWhite;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687       initialMode = MachinePlaysBlack;
1688     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689       initialMode = EditGame;
1690     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691       initialMode = EditPosition;
1692     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693       initialMode = Training;
1694     } else {
1695       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696       if( (len >= MSG_SIZ) && appData.debugMode )
1697         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698
1699       DisplayFatalError(buf, 0, 2);
1700       return;
1701     }
1702
1703     if (appData.matchMode) {
1704         if(appData.tourneyFile[0]) { // start tourney from command line
1705             FILE *f;
1706             if(f = fopen(appData.tourneyFile, "r")) {
1707                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708                 fclose(f);
1709                 appData.clockMode = TRUE;
1710                 SetGNUMode();
1711             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1712         }
1713         MatchEvent(TRUE);
1714     } else if (*appData.cmailGameName != NULLCHAR) {
1715         /* Set up cmail mode */
1716         ReloadCmailMsgEvent(TRUE);
1717     } else {
1718         /* Set up other modes */
1719         if (initialMode == AnalyzeFile) {
1720           if (*appData.loadGameFile == NULLCHAR) {
1721             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1722             return;
1723           }
1724         }
1725         if (*appData.loadGameFile != NULLCHAR) {
1726             (void) LoadGameFromFile(appData.loadGameFile,
1727                                     appData.loadGameIndex,
1728                                     appData.loadGameFile, TRUE);
1729         } else if (*appData.loadPositionFile != NULLCHAR) {
1730             (void) LoadPositionFromFile(appData.loadPositionFile,
1731                                         appData.loadPositionIndex,
1732                                         appData.loadPositionFile);
1733             /* [HGM] try to make self-starting even after FEN load */
1734             /* to allow automatic setup of fairy variants with wtm */
1735             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736                 gameMode = BeginningOfGame;
1737                 setboardSpoiledMachineBlack = 1;
1738             }
1739             /* [HGM] loadPos: make that every new game uses the setup */
1740             /* from file as long as we do not switch variant          */
1741             if(!blackPlaysFirst) {
1742                 startedFromPositionFile = TRUE;
1743                 CopyBoard(filePosition, boards[0]);
1744                 CopyBoard(initialPosition, boards[0]);
1745             }
1746         }
1747         if (initialMode == AnalyzeMode) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1750             return;
1751           }
1752           if (appData.icsActive) {
1753             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1754             return;
1755           }
1756           AnalyzeModeEvent();
1757         } else if (initialMode == AnalyzeFile) {
1758           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1759           ShowThinkingEvent();
1760           AnalyzeFileEvent();
1761           AnalysisPeriodicEvent(1);
1762         } else if (initialMode == MachinePlaysWhite) {
1763           if (appData.noChessProgram) {
1764             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1765                               0, 2);
1766             return;
1767           }
1768           if (appData.icsActive) {
1769             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1770                               0, 2);
1771             return;
1772           }
1773           MachineWhiteEvent();
1774         } else if (initialMode == MachinePlaysBlack) {
1775           if (appData.noChessProgram) {
1776             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1777                               0, 2);
1778             return;
1779           }
1780           if (appData.icsActive) {
1781             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1782                               0, 2);
1783             return;
1784           }
1785           MachineBlackEvent();
1786         } else if (initialMode == TwoMachinesPlay) {
1787           if (appData.noChessProgram) {
1788             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1789                               0, 2);
1790             return;
1791           }
1792           if (appData.icsActive) {
1793             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1794                               0, 2);
1795             return;
1796           }
1797           TwoMachinesEvent();
1798         } else if (initialMode == EditGame) {
1799           EditGameEvent();
1800         } else if (initialMode == EditPosition) {
1801           EditPositionEvent();
1802         } else if (initialMode == Training) {
1803           if (*appData.loadGameFile == NULLCHAR) {
1804             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1805             return;
1806           }
1807           TrainingEvent();
1808         }
1809     }
1810 }
1811
1812 void
1813 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1814 {
1815     DisplayBook(current+1);
1816
1817     MoveHistorySet( movelist, first, last, current, pvInfoList );
1818
1819     EvalGraphSet( first, last, current, pvInfoList );
1820
1821     MakeEngineOutputTitle();
1822 }
1823
1824 /*
1825  * Establish will establish a contact to a remote host.port.
1826  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1827  *  used to talk to the host.
1828  * Returns 0 if okay, error code if not.
1829  */
1830 int
1831 establish ()
1832 {
1833     char buf[MSG_SIZ];
1834
1835     if (*appData.icsCommPort != NULLCHAR) {
1836         /* Talk to the host through a serial comm port */
1837         return OpenCommPort(appData.icsCommPort, &icsPR);
1838
1839     } else if (*appData.gateway != NULLCHAR) {
1840         if (*appData.remoteShell == NULLCHAR) {
1841             /* Use the rcmd protocol to run telnet program on a gateway host */
1842             snprintf(buf, sizeof(buf), "%s %s %s",
1843                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1844             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1845
1846         } else {
1847             /* Use the rsh program to run telnet program on a gateway host */
1848             if (*appData.remoteUser == NULLCHAR) {
1849                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1850                         appData.gateway, appData.telnetProgram,
1851                         appData.icsHost, appData.icsPort);
1852             } else {
1853                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1854                         appData.remoteShell, appData.gateway,
1855                         appData.remoteUser, appData.telnetProgram,
1856                         appData.icsHost, appData.icsPort);
1857             }
1858             return StartChildProcess(buf, "", &icsPR);
1859
1860         }
1861     } else if (appData.useTelnet) {
1862         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1863
1864     } else {
1865         /* TCP socket interface differs somewhat between
1866            Unix and NT; handle details in the front end.
1867            */
1868         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1869     }
1870 }
1871
1872 void
1873 EscapeExpand (char *p, char *q)
1874 {       // [HGM] initstring: routine to shape up string arguments
1875         while(*p++ = *q++) if(p[-1] == '\\')
1876             switch(*q++) {
1877                 case 'n': p[-1] = '\n'; break;
1878                 case 'r': p[-1] = '\r'; break;
1879                 case 't': p[-1] = '\t'; break;
1880                 case '\\': p[-1] = '\\'; break;
1881                 case 0: *p = 0; return;
1882                 default: p[-1] = q[-1]; break;
1883             }
1884 }
1885
1886 void
1887 show_bytes (FILE *fp, char *buf, int count)
1888 {
1889     while (count--) {
1890         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1891             fprintf(fp, "\\%03o", *buf & 0xff);
1892         } else {
1893             putc(*buf, fp);
1894         }
1895         buf++;
1896     }
1897     fflush(fp);
1898 }
1899
1900 /* Returns an errno value */
1901 int
1902 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1903 {
1904     char buf[8192], *p, *q, *buflim;
1905     int left, newcount, outcount;
1906
1907     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1908         *appData.gateway != NULLCHAR) {
1909         if (appData.debugMode) {
1910             fprintf(debugFP, ">ICS: ");
1911             show_bytes(debugFP, message, count);
1912             fprintf(debugFP, "\n");
1913         }
1914         return OutputToProcess(pr, message, count, outError);
1915     }
1916
1917     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1918     p = message;
1919     q = buf;
1920     left = count;
1921     newcount = 0;
1922     while (left) {
1923         if (q >= buflim) {
1924             if (appData.debugMode) {
1925                 fprintf(debugFP, ">ICS: ");
1926                 show_bytes(debugFP, buf, newcount);
1927                 fprintf(debugFP, "\n");
1928             }
1929             outcount = OutputToProcess(pr, buf, newcount, outError);
1930             if (outcount < newcount) return -1; /* to be sure */
1931             q = buf;
1932             newcount = 0;
1933         }
1934         if (*p == '\n') {
1935             *q++ = '\r';
1936             newcount++;
1937         } else if (((unsigned char) *p) == TN_IAC) {
1938             *q++ = (char) TN_IAC;
1939             newcount ++;
1940         }
1941         *q++ = *p++;
1942         newcount++;
1943         left--;
1944     }
1945     if (appData.debugMode) {
1946         fprintf(debugFP, ">ICS: ");
1947         show_bytes(debugFP, buf, newcount);
1948         fprintf(debugFP, "\n");
1949     }
1950     outcount = OutputToProcess(pr, buf, newcount, outError);
1951     if (outcount < newcount) return -1; /* to be sure */
1952     return count;
1953 }
1954
1955 void
1956 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1957 {
1958     int outError, outCount;
1959     static int gotEof = 0;
1960     static FILE *ini;
1961
1962     /* Pass data read from player on to ICS */
1963     if (count > 0) {
1964         gotEof = 0;
1965         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1966         if (outCount < count) {
1967             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1968         }
1969         if(have_sent_ICS_logon == 2) {
1970           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1971             fprintf(ini, "%s", message);
1972             have_sent_ICS_logon = 3;
1973           } else
1974             have_sent_ICS_logon = 1;
1975         } else if(have_sent_ICS_logon == 3) {
1976             fprintf(ini, "%s", message);
1977             fclose(ini);
1978           have_sent_ICS_logon = 1;
1979         }
1980     } else if (count < 0) {
1981         RemoveInputSource(isr);
1982         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1983     } else if (gotEof++ > 0) {
1984         RemoveInputSource(isr);
1985         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1986     }
1987 }
1988
1989 void
1990 KeepAlive ()
1991 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1992     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1993     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1994     SendToICS("date\n");
1995     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1996 }
1997
1998 /* added routine for printf style output to ics */
1999 void
2000 ics_printf (char *format, ...)
2001 {
2002     char buffer[MSG_SIZ];
2003     va_list args;
2004
2005     va_start(args, format);
2006     vsnprintf(buffer, sizeof(buffer), format, args);
2007     buffer[sizeof(buffer)-1] = '\0';
2008     SendToICS(buffer);
2009     va_end(args);
2010 }
2011
2012 void
2013 SendToICS (char *s)
2014 {
2015     int count, outCount, outError;
2016
2017     if (icsPR == NoProc) return;
2018
2019     count = strlen(s);
2020     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2021     if (outCount < count) {
2022         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2023     }
2024 }
2025
2026 /* This is used for sending logon scripts to the ICS. Sending
2027    without a delay causes problems when using timestamp on ICC
2028    (at least on my machine). */
2029 void
2030 SendToICSDelayed (char *s, long msdelay)
2031 {
2032     int count, outCount, outError;
2033
2034     if (icsPR == NoProc) return;
2035
2036     count = strlen(s);
2037     if (appData.debugMode) {
2038         fprintf(debugFP, ">ICS: ");
2039         show_bytes(debugFP, s, count);
2040         fprintf(debugFP, "\n");
2041     }
2042     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2043                                       msdelay);
2044     if (outCount < count) {
2045         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2046     }
2047 }
2048
2049
2050 /* Remove all highlighting escape sequences in s
2051    Also deletes any suffix starting with '('
2052    */
2053 char *
2054 StripHighlightAndTitle (char *s)
2055 {
2056     static char retbuf[MSG_SIZ];
2057     char *p = retbuf;
2058
2059     while (*s != NULLCHAR) {
2060         while (*s == '\033') {
2061             while (*s != NULLCHAR && !isalpha(*s)) s++;
2062             if (*s != NULLCHAR) s++;
2063         }
2064         while (*s != NULLCHAR && *s != '\033') {
2065             if (*s == '(' || *s == '[') {
2066                 *p = NULLCHAR;
2067                 return retbuf;
2068             }
2069             *p++ = *s++;
2070         }
2071     }
2072     *p = NULLCHAR;
2073     return retbuf;
2074 }
2075
2076 /* Remove all highlighting escape sequences in s */
2077 char *
2078 StripHighlight (char *s)
2079 {
2080     static char retbuf[MSG_SIZ];
2081     char *p = retbuf;
2082
2083     while (*s != NULLCHAR) {
2084         while (*s == '\033') {
2085             while (*s != NULLCHAR && !isalpha(*s)) s++;
2086             if (*s != NULLCHAR) s++;
2087         }
2088         while (*s != NULLCHAR && *s != '\033') {
2089             *p++ = *s++;
2090         }
2091     }
2092     *p = NULLCHAR;
2093     return retbuf;
2094 }
2095
2096 char engineVariant[MSG_SIZ];
2097 char *variantNames[] = VARIANT_NAMES;
2098 char *
2099 VariantName (VariantClass v)
2100 {
2101     if(v == VariantUnknown || *engineVariant) return engineVariant;
2102     return variantNames[v];
2103 }
2104
2105
2106 /* Identify a variant from the strings the chess servers use or the
2107    PGN Variant tag names we use. */
2108 VariantClass
2109 StringToVariant (char *e)
2110 {
2111     char *p;
2112     int wnum = -1;
2113     VariantClass v = VariantNormal;
2114     int i, found = FALSE;
2115     char buf[MSG_SIZ], c;
2116     int len;
2117
2118     if (!e) return v;
2119
2120     /* [HGM] skip over optional board-size prefixes */
2121     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2122         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2123         while( *e++ != '_');
2124     }
2125
2126     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2127         v = VariantNormal;
2128         found = TRUE;
2129     } else
2130     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2131       if (p = StrCaseStr(e, variantNames[i])) {
2132         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2133         v = (VariantClass) i;
2134         found = TRUE;
2135         break;
2136       }
2137     }
2138
2139     if (!found) {
2140       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2141           || StrCaseStr(e, "wild/fr")
2142           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2143         v = VariantFischeRandom;
2144       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2145                  (i = 1, p = StrCaseStr(e, "w"))) {
2146         p += i;
2147         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2148         if (isdigit(*p)) {
2149           wnum = atoi(p);
2150         } else {
2151           wnum = -1;
2152         }
2153         switch (wnum) {
2154         case 0: /* FICS only, actually */
2155         case 1:
2156           /* Castling legal even if K starts on d-file */
2157           v = VariantWildCastle;
2158           break;
2159         case 2:
2160         case 3:
2161         case 4:
2162           /* Castling illegal even if K & R happen to start in
2163              normal positions. */
2164           v = VariantNoCastle;
2165           break;
2166         case 5:
2167         case 7:
2168         case 8:
2169         case 10:
2170         case 11:
2171         case 12:
2172         case 13:
2173         case 14:
2174         case 15:
2175         case 18:
2176         case 19:
2177           /* Castling legal iff K & R start in normal positions */
2178           v = VariantNormal;
2179           break;
2180         case 6:
2181         case 20:
2182         case 21:
2183           /* Special wilds for position setup; unclear what to do here */
2184           v = VariantLoadable;
2185           break;
2186         case 9:
2187           /* Bizarre ICC game */
2188           v = VariantTwoKings;
2189           break;
2190         case 16:
2191           v = VariantKriegspiel;
2192           break;
2193         case 17:
2194           v = VariantLosers;
2195           break;
2196         case 22:
2197           v = VariantFischeRandom;
2198           break;
2199         case 23:
2200           v = VariantCrazyhouse;
2201           break;
2202         case 24:
2203           v = VariantBughouse;
2204           break;
2205         case 25:
2206           v = Variant3Check;
2207           break;
2208         case 26:
2209           /* Not quite the same as FICS suicide! */
2210           v = VariantGiveaway;
2211           break;
2212         case 27:
2213           v = VariantAtomic;
2214           break;
2215         case 28:
2216           v = VariantShatranj;
2217           break;
2218
2219         /* Temporary names for future ICC types.  The name *will* change in
2220            the next xboard/WinBoard release after ICC defines it. */
2221         case 29:
2222           v = Variant29;
2223           break;
2224         case 30:
2225           v = Variant30;
2226           break;
2227         case 31:
2228           v = Variant31;
2229           break;
2230         case 32:
2231           v = Variant32;
2232           break;
2233         case 33:
2234           v = Variant33;
2235           break;
2236         case 34:
2237           v = Variant34;
2238           break;
2239         case 35:
2240           v = Variant35;
2241           break;
2242         case 36:
2243           v = Variant36;
2244           break;
2245         case 37:
2246           v = VariantShogi;
2247           break;
2248         case 38:
2249           v = VariantXiangqi;
2250           break;
2251         case 39:
2252           v = VariantCourier;
2253           break;
2254         case 40:
2255           v = VariantGothic;
2256           break;
2257         case 41:
2258           v = VariantCapablanca;
2259           break;
2260         case 42:
2261           v = VariantKnightmate;
2262           break;
2263         case 43:
2264           v = VariantFairy;
2265           break;
2266         case 44:
2267           v = VariantCylinder;
2268           break;
2269         case 45:
2270           v = VariantFalcon;
2271           break;
2272         case 46:
2273           v = VariantCapaRandom;
2274           break;
2275         case 47:
2276           v = VariantBerolina;
2277           break;
2278         case 48:
2279           v = VariantJanus;
2280           break;
2281         case 49:
2282           v = VariantSuper;
2283           break;
2284         case 50:
2285           v = VariantGreat;
2286           break;
2287         case -1:
2288           /* Found "wild" or "w" in the string but no number;
2289              must assume it's normal chess. */
2290           v = VariantNormal;
2291           break;
2292         default:
2293           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2294           if( (len >= MSG_SIZ) && appData.debugMode )
2295             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2296
2297           DisplayError(buf, 0);
2298           v = VariantUnknown;
2299           break;
2300         }
2301       }
2302     }
2303     if (appData.debugMode) {
2304       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2305               e, wnum, VariantName(v));
2306     }
2307     return v;
2308 }
2309
2310 static int leftover_start = 0, leftover_len = 0;
2311 char star_match[STAR_MATCH_N][MSG_SIZ];
2312
2313 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2314    advance *index beyond it, and set leftover_start to the new value of
2315    *index; else return FALSE.  If pattern contains the character '*', it
2316    matches any sequence of characters not containing '\r', '\n', or the
2317    character following the '*' (if any), and the matched sequence(s) are
2318    copied into star_match.
2319    */
2320 int
2321 looking_at ( char *buf, int *index, char *pattern)
2322 {
2323     char *bufp = &buf[*index], *patternp = pattern;
2324     int star_count = 0;
2325     char *matchp = star_match[0];
2326
2327     for (;;) {
2328         if (*patternp == NULLCHAR) {
2329             *index = leftover_start = bufp - buf;
2330             *matchp = NULLCHAR;
2331             return TRUE;
2332         }
2333         if (*bufp == NULLCHAR) return FALSE;
2334         if (*patternp == '*') {
2335             if (*bufp == *(patternp + 1)) {
2336                 *matchp = NULLCHAR;
2337                 matchp = star_match[++star_count];
2338                 patternp += 2;
2339                 bufp++;
2340                 continue;
2341             } else if (*bufp == '\n' || *bufp == '\r') {
2342                 patternp++;
2343                 if (*patternp == NULLCHAR)
2344                   continue;
2345                 else
2346                   return FALSE;
2347             } else {
2348                 *matchp++ = *bufp++;
2349                 continue;
2350             }
2351         }
2352         if (*patternp != *bufp) return FALSE;
2353         patternp++;
2354         bufp++;
2355     }
2356 }
2357
2358 void
2359 SendToPlayer (char *data, int length)
2360 {
2361     int error, outCount;
2362     outCount = OutputToProcess(NoProc, data, length, &error);
2363     if (outCount < length) {
2364         DisplayFatalError(_("Error writing to display"), error, 1);
2365     }
2366 }
2367
2368 void
2369 PackHolding (char packed[], char *holding)
2370 {
2371     char *p = holding;
2372     char *q = packed;
2373     int runlength = 0;
2374     int curr = 9999;
2375     do {
2376         if (*p == curr) {
2377             runlength++;
2378         } else {
2379             switch (runlength) {
2380               case 0:
2381                 break;
2382               case 1:
2383                 *q++ = curr;
2384                 break;
2385               case 2:
2386                 *q++ = curr;
2387                 *q++ = curr;
2388                 break;
2389               default:
2390                 sprintf(q, "%d", runlength);
2391                 while (*q) q++;
2392                 *q++ = curr;
2393                 break;
2394             }
2395             runlength = 1;
2396             curr = *p;
2397         }
2398     } while (*p++);
2399     *q = NULLCHAR;
2400 }
2401
2402 /* Telnet protocol requests from the front end */
2403 void
2404 TelnetRequest (unsigned char ddww, unsigned char option)
2405 {
2406     unsigned char msg[3];
2407     int outCount, outError;
2408
2409     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2410
2411     if (appData.debugMode) {
2412         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2413         switch (ddww) {
2414           case TN_DO:
2415             ddwwStr = "DO";
2416             break;
2417           case TN_DONT:
2418             ddwwStr = "DONT";
2419             break;
2420           case TN_WILL:
2421             ddwwStr = "WILL";
2422             break;
2423           case TN_WONT:
2424             ddwwStr = "WONT";
2425             break;
2426           default:
2427             ddwwStr = buf1;
2428             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2429             break;
2430         }
2431         switch (option) {
2432           case TN_ECHO:
2433             optionStr = "ECHO";
2434             break;
2435           default:
2436             optionStr = buf2;
2437             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2438             break;
2439         }
2440         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2441     }
2442     msg[0] = TN_IAC;
2443     msg[1] = ddww;
2444     msg[2] = option;
2445     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2446     if (outCount < 3) {
2447         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2448     }
2449 }
2450
2451 void
2452 DoEcho ()
2453 {
2454     if (!appData.icsActive) return;
2455     TelnetRequest(TN_DO, TN_ECHO);
2456 }
2457
2458 void
2459 DontEcho ()
2460 {
2461     if (!appData.icsActive) return;
2462     TelnetRequest(TN_DONT, TN_ECHO);
2463 }
2464
2465 void
2466 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2467 {
2468     /* put the holdings sent to us by the server on the board holdings area */
2469     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2470     char p;
2471     ChessSquare piece;
2472
2473     if(gameInfo.holdingsWidth < 2)  return;
2474     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2475         return; // prevent overwriting by pre-board holdings
2476
2477     if( (int)lowestPiece >= BlackPawn ) {
2478         holdingsColumn = 0;
2479         countsColumn = 1;
2480         holdingsStartRow = BOARD_HEIGHT-1;
2481         direction = -1;
2482     } else {
2483         holdingsColumn = BOARD_WIDTH-1;
2484         countsColumn = BOARD_WIDTH-2;
2485         holdingsStartRow = 0;
2486         direction = 1;
2487     }
2488
2489     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2490         board[i][holdingsColumn] = EmptySquare;
2491         board[i][countsColumn]   = (ChessSquare) 0;
2492     }
2493     while( (p=*holdings++) != NULLCHAR ) {
2494         piece = CharToPiece( ToUpper(p) );
2495         if(piece == EmptySquare) continue;
2496         /*j = (int) piece - (int) WhitePawn;*/
2497         j = PieceToNumber(piece);
2498         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2499         if(j < 0) continue;               /* should not happen */
2500         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2501         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2502         board[holdingsStartRow+j*direction][countsColumn]++;
2503     }
2504 }
2505
2506
2507 void
2508 VariantSwitch (Board board, VariantClass newVariant)
2509 {
2510    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2511    static Board oldBoard;
2512
2513    startedFromPositionFile = FALSE;
2514    if(gameInfo.variant == newVariant) return;
2515
2516    /* [HGM] This routine is called each time an assignment is made to
2517     * gameInfo.variant during a game, to make sure the board sizes
2518     * are set to match the new variant. If that means adding or deleting
2519     * holdings, we shift the playing board accordingly
2520     * This kludge is needed because in ICS observe mode, we get boards
2521     * of an ongoing game without knowing the variant, and learn about the
2522     * latter only later. This can be because of the move list we requested,
2523     * in which case the game history is refilled from the beginning anyway,
2524     * but also when receiving holdings of a crazyhouse game. In the latter
2525     * case we want to add those holdings to the already received position.
2526     */
2527
2528
2529    if (appData.debugMode) {
2530      fprintf(debugFP, "Switch board from %s to %s\n",
2531              VariantName(gameInfo.variant), VariantName(newVariant));
2532      setbuf(debugFP, NULL);
2533    }
2534    shuffleOpenings = 0;       /* [HGM] shuffle */
2535    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2536    switch(newVariant)
2537      {
2538      case VariantShogi:
2539        newWidth = 9;  newHeight = 9;
2540        gameInfo.holdingsSize = 7;
2541      case VariantBughouse:
2542      case VariantCrazyhouse:
2543        newHoldingsWidth = 2; break;
2544      case VariantGreat:
2545        newWidth = 10;
2546      case VariantSuper:
2547        newHoldingsWidth = 2;
2548        gameInfo.holdingsSize = 8;
2549        break;
2550      case VariantGothic:
2551      case VariantCapablanca:
2552      case VariantCapaRandom:
2553        newWidth = 10;
2554      default:
2555        newHoldingsWidth = gameInfo.holdingsSize = 0;
2556      };
2557
2558    if(newWidth  != gameInfo.boardWidth  ||
2559       newHeight != gameInfo.boardHeight ||
2560       newHoldingsWidth != gameInfo.holdingsWidth ) {
2561
2562      /* shift position to new playing area, if needed */
2563      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2564        for(i=0; i<BOARD_HEIGHT; i++)
2565          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2566            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2567              board[i][j];
2568        for(i=0; i<newHeight; i++) {
2569          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2570          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2571        }
2572      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2573        for(i=0; i<BOARD_HEIGHT; i++)
2574          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2575            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576              board[i][j];
2577      }
2578      board[HOLDINGS_SET] = 0;
2579      gameInfo.boardWidth  = newWidth;
2580      gameInfo.boardHeight = newHeight;
2581      gameInfo.holdingsWidth = newHoldingsWidth;
2582      gameInfo.variant = newVariant;
2583      InitDrawingSizes(-2, 0);
2584    } else gameInfo.variant = newVariant;
2585    CopyBoard(oldBoard, board);   // remember correctly formatted board
2586      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2587    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2588 }
2589
2590 static int loggedOn = FALSE;
2591
2592 /*-- Game start info cache: --*/
2593 int gs_gamenum;
2594 char gs_kind[MSG_SIZ];
2595 static char player1Name[128] = "";
2596 static char player2Name[128] = "";
2597 static char cont_seq[] = "\n\\   ";
2598 static int player1Rating = -1;
2599 static int player2Rating = -1;
2600 /*----------------------------*/
2601
2602 ColorClass curColor = ColorNormal;
2603 int suppressKibitz = 0;
2604
2605 // [HGM] seekgraph
2606 Boolean soughtPending = FALSE;
2607 Boolean seekGraphUp;
2608 #define MAX_SEEK_ADS 200
2609 #define SQUARE 0x80
2610 char *seekAdList[MAX_SEEK_ADS];
2611 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2612 float tcList[MAX_SEEK_ADS];
2613 char colorList[MAX_SEEK_ADS];
2614 int nrOfSeekAds = 0;
2615 int minRating = 1010, maxRating = 2800;
2616 int hMargin = 10, vMargin = 20, h, w;
2617 extern int squareSize, lineGap;
2618
2619 void
2620 PlotSeekAd (int i)
2621 {
2622         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2623         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2624         if(r < minRating+100 && r >=0 ) r = minRating+100;
2625         if(r > maxRating) r = maxRating;
2626         if(tc < 1.f) tc = 1.f;
2627         if(tc > 95.f) tc = 95.f;
2628         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2629         y = ((double)r - minRating)/(maxRating - minRating)
2630             * (h-vMargin-squareSize/8-1) + vMargin;
2631         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2632         if(strstr(seekAdList[i], " u ")) color = 1;
2633         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2634            !strstr(seekAdList[i], "bullet") &&
2635            !strstr(seekAdList[i], "blitz") &&
2636            !strstr(seekAdList[i], "standard") ) color = 2;
2637         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2638         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2639 }
2640
2641 void
2642 PlotSingleSeekAd (int i)
2643 {
2644         PlotSeekAd(i);
2645 }
2646
2647 void
2648 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2649 {
2650         char buf[MSG_SIZ], *ext = "";
2651         VariantClass v = StringToVariant(type);
2652         if(strstr(type, "wild")) {
2653             ext = type + 4; // append wild number
2654             if(v == VariantFischeRandom) type = "chess960"; else
2655             if(v == VariantLoadable) type = "setup"; else
2656             type = VariantName(v);
2657         }
2658         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2659         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2660             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2661             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2662             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2663             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2664             seekNrList[nrOfSeekAds] = nr;
2665             zList[nrOfSeekAds] = 0;
2666             seekAdList[nrOfSeekAds++] = StrSave(buf);
2667             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2668         }
2669 }
2670
2671 void
2672 EraseSeekDot (int i)
2673 {
2674     int x = xList[i], y = yList[i], d=squareSize/4, k;
2675     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2676     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2677     // now replot every dot that overlapped
2678     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2679         int xx = xList[k], yy = yList[k];
2680         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2681             DrawSeekDot(xx, yy, colorList[k]);
2682     }
2683 }
2684
2685 void
2686 RemoveSeekAd (int nr)
2687 {
2688         int i;
2689         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2690             EraseSeekDot(i);
2691             if(seekAdList[i]) free(seekAdList[i]);
2692             seekAdList[i] = seekAdList[--nrOfSeekAds];
2693             seekNrList[i] = seekNrList[nrOfSeekAds];
2694             ratingList[i] = ratingList[nrOfSeekAds];
2695             colorList[i]  = colorList[nrOfSeekAds];
2696             tcList[i] = tcList[nrOfSeekAds];
2697             xList[i]  = xList[nrOfSeekAds];
2698             yList[i]  = yList[nrOfSeekAds];
2699             zList[i]  = zList[nrOfSeekAds];
2700             seekAdList[nrOfSeekAds] = NULL;
2701             break;
2702         }
2703 }
2704
2705 Boolean
2706 MatchSoughtLine (char *line)
2707 {
2708     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2709     int nr, base, inc, u=0; char dummy;
2710
2711     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2712        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2713        (u=1) &&
2714        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2715         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2716         // match: compact and save the line
2717         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2718         return TRUE;
2719     }
2720     return FALSE;
2721 }
2722
2723 int
2724 DrawSeekGraph ()
2725 {
2726     int i;
2727     if(!seekGraphUp) return FALSE;
2728     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2729     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2730
2731     DrawSeekBackground(0, 0, w, h);
2732     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2733     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2734     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2735         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2736         yy = h-1-yy;
2737         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2738         if(i%500 == 0) {
2739             char buf[MSG_SIZ];
2740             snprintf(buf, MSG_SIZ, "%d", i);
2741             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2742         }
2743     }
2744     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2745     for(i=1; i<100; i+=(i<10?1:5)) {
2746         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2747         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2748         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2749             char buf[MSG_SIZ];
2750             snprintf(buf, MSG_SIZ, "%d", i);
2751             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2752         }
2753     }
2754     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2755     return TRUE;
2756 }
2757
2758 int
2759 SeekGraphClick (ClickType click, int x, int y, int moving)
2760 {
2761     static int lastDown = 0, displayed = 0, lastSecond;
2762     if(y < 0) return FALSE;
2763     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2764         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2765         if(!seekGraphUp) return FALSE;
2766         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2767         DrawPosition(TRUE, NULL);
2768         return TRUE;
2769     }
2770     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2771         if(click == Release || moving) return FALSE;
2772         nrOfSeekAds = 0;
2773         soughtPending = TRUE;
2774         SendToICS(ics_prefix);
2775         SendToICS("sought\n"); // should this be "sought all"?
2776     } else { // issue challenge based on clicked ad
2777         int dist = 10000; int i, closest = 0, second = 0;
2778         for(i=0; i<nrOfSeekAds; i++) {
2779             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2780             if(d < dist) { dist = d; closest = i; }
2781             second += (d - zList[i] < 120); // count in-range ads
2782             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2783         }
2784         if(dist < 120) {
2785             char buf[MSG_SIZ];
2786             second = (second > 1);
2787             if(displayed != closest || second != lastSecond) {
2788                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2789                 lastSecond = second; displayed = closest;
2790             }
2791             if(click == Press) {
2792                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2793                 lastDown = closest;
2794                 return TRUE;
2795             } // on press 'hit', only show info
2796             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2797             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2798             SendToICS(ics_prefix);
2799             SendToICS(buf);
2800             return TRUE; // let incoming board of started game pop down the graph
2801         } else if(click == Release) { // release 'miss' is ignored
2802             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2803             if(moving == 2) { // right up-click
2804                 nrOfSeekAds = 0; // refresh graph
2805                 soughtPending = TRUE;
2806                 SendToICS(ics_prefix);
2807                 SendToICS("sought\n"); // should this be "sought all"?
2808             }
2809             return TRUE;
2810         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2811         // press miss or release hit 'pop down' seek graph
2812         seekGraphUp = FALSE;
2813         DrawPosition(TRUE, NULL);
2814     }
2815     return TRUE;
2816 }
2817
2818 void
2819 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2820 {
2821 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2822 #define STARTED_NONE 0
2823 #define STARTED_MOVES 1
2824 #define STARTED_BOARD 2
2825 #define STARTED_OBSERVE 3
2826 #define STARTED_HOLDINGS 4
2827 #define STARTED_CHATTER 5
2828 #define STARTED_COMMENT 6
2829 #define STARTED_MOVES_NOHIDE 7
2830
2831     static int started = STARTED_NONE;
2832     static char parse[20000];
2833     static int parse_pos = 0;
2834     static char buf[BUF_SIZE + 1];
2835     static int firstTime = TRUE, intfSet = FALSE;
2836     static ColorClass prevColor = ColorNormal;
2837     static int savingComment = FALSE;
2838     static int cmatch = 0; // continuation sequence match
2839     char *bp;
2840     char str[MSG_SIZ];
2841     int i, oldi;
2842     int buf_len;
2843     int next_out;
2844     int tkind;
2845     int backup;    /* [DM] For zippy color lines */
2846     char *p;
2847     char talker[MSG_SIZ]; // [HGM] chat
2848     int channel, collective=0;
2849
2850     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2851
2852     if (appData.debugMode) {
2853       if (!error) {
2854         fprintf(debugFP, "<ICS: ");
2855         show_bytes(debugFP, data, count);
2856         fprintf(debugFP, "\n");
2857       }
2858     }
2859
2860     if (appData.debugMode) { int f = forwardMostMove;
2861         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2862                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2863                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2864     }
2865     if (count > 0) {
2866         /* If last read ended with a partial line that we couldn't parse,
2867            prepend it to the new read and try again. */
2868         if (leftover_len > 0) {
2869             for (i=0; i<leftover_len; i++)
2870               buf[i] = buf[leftover_start + i];
2871         }
2872
2873     /* copy new characters into the buffer */
2874     bp = buf + leftover_len;
2875     buf_len=leftover_len;
2876     for (i=0; i<count; i++)
2877     {
2878         // ignore these
2879         if (data[i] == '\r')
2880             continue;
2881
2882         // join lines split by ICS?
2883         if (!appData.noJoin)
2884         {
2885             /*
2886                 Joining just consists of finding matches against the
2887                 continuation sequence, and discarding that sequence
2888                 if found instead of copying it.  So, until a match
2889                 fails, there's nothing to do since it might be the
2890                 complete sequence, and thus, something we don't want
2891                 copied.
2892             */
2893             if (data[i] == cont_seq[cmatch])
2894             {
2895                 cmatch++;
2896                 if (cmatch == strlen(cont_seq))
2897                 {
2898                     cmatch = 0; // complete match.  just reset the counter
2899
2900                     /*
2901                         it's possible for the ICS to not include the space
2902                         at the end of the last word, making our [correct]
2903                         join operation fuse two separate words.  the server
2904                         does this when the space occurs at the width setting.
2905                     */
2906                     if (!buf_len || buf[buf_len-1] != ' ')
2907                     {
2908                         *bp++ = ' ';
2909                         buf_len++;
2910                     }
2911                 }
2912                 continue;
2913             }
2914             else if (cmatch)
2915             {
2916                 /*
2917                     match failed, so we have to copy what matched before
2918                     falling through and copying this character.  In reality,
2919                     this will only ever be just the newline character, but
2920                     it doesn't hurt to be precise.
2921                 */
2922                 strncpy(bp, cont_seq, cmatch);
2923                 bp += cmatch;
2924                 buf_len += cmatch;
2925                 cmatch = 0;
2926             }
2927         }
2928
2929         // copy this char
2930         *bp++ = data[i];
2931         buf_len++;
2932     }
2933
2934         buf[buf_len] = NULLCHAR;
2935 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2936         next_out = 0;
2937         leftover_start = 0;
2938
2939         i = 0;
2940         while (i < buf_len) {
2941             /* Deal with part of the TELNET option negotiation
2942                protocol.  We refuse to do anything beyond the
2943                defaults, except that we allow the WILL ECHO option,
2944                which ICS uses to turn off password echoing when we are
2945                directly connected to it.  We reject this option
2946                if localLineEditing mode is on (always on in xboard)
2947                and we are talking to port 23, which might be a real
2948                telnet server that will try to keep WILL ECHO on permanently.
2949              */
2950             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2951                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2952                 unsigned char option;
2953                 oldi = i;
2954                 switch ((unsigned char) buf[++i]) {
2955                   case TN_WILL:
2956                     if (appData.debugMode)
2957                       fprintf(debugFP, "\n<WILL ");
2958                     switch (option = (unsigned char) buf[++i]) {
2959                       case TN_ECHO:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "ECHO ");
2962                         /* Reply only if this is a change, according
2963                            to the protocol rules. */
2964                         if (remoteEchoOption) break;
2965                         if (appData.localLineEditing &&
2966                             atoi(appData.icsPort) == TN_PORT) {
2967                             TelnetRequest(TN_DONT, TN_ECHO);
2968                         } else {
2969                             EchoOff();
2970                             TelnetRequest(TN_DO, TN_ECHO);
2971                             remoteEchoOption = TRUE;
2972                         }
2973                         break;
2974                       default:
2975                         if (appData.debugMode)
2976                           fprintf(debugFP, "%d ", option);
2977                         /* Whatever this is, we don't want it. */
2978                         TelnetRequest(TN_DONT, option);
2979                         break;
2980                     }
2981                     break;
2982                   case TN_WONT:
2983                     if (appData.debugMode)
2984                       fprintf(debugFP, "\n<WONT ");
2985                     switch (option = (unsigned char) buf[++i]) {
2986                       case TN_ECHO:
2987                         if (appData.debugMode)
2988                           fprintf(debugFP, "ECHO ");
2989                         /* Reply only if this is a change, according
2990                            to the protocol rules. */
2991                         if (!remoteEchoOption) break;
2992                         EchoOn();
2993                         TelnetRequest(TN_DONT, TN_ECHO);
2994                         remoteEchoOption = FALSE;
2995                         break;
2996                       default:
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", (unsigned char) option);
2999                         /* Whatever this is, it must already be turned
3000                            off, because we never agree to turn on
3001                            anything non-default, so according to the
3002                            protocol rules, we don't reply. */
3003                         break;
3004                     }
3005                     break;
3006                   case TN_DO:
3007                     if (appData.debugMode)
3008                       fprintf(debugFP, "\n<DO ");
3009                     switch (option = (unsigned char) buf[++i]) {
3010                       default:
3011                         /* Whatever this is, we refuse to do it. */
3012                         if (appData.debugMode)
3013                           fprintf(debugFP, "%d ", option);
3014                         TelnetRequest(TN_WONT, option);
3015                         break;
3016                     }
3017                     break;
3018                   case TN_DONT:
3019                     if (appData.debugMode)
3020                       fprintf(debugFP, "\n<DONT ");
3021                     switch (option = (unsigned char) buf[++i]) {
3022                       default:
3023                         if (appData.debugMode)
3024                           fprintf(debugFP, "%d ", option);
3025                         /* Whatever this is, we are already not doing
3026                            it, because we never agree to do anything
3027                            non-default, so according to the protocol
3028                            rules, we don't reply. */
3029                         break;
3030                     }
3031                     break;
3032                   case TN_IAC:
3033                     if (appData.debugMode)
3034                       fprintf(debugFP, "\n<IAC ");
3035                     /* Doubled IAC; pass it through */
3036                     i--;
3037                     break;
3038                   default:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3041                     /* Drop all other telnet commands on the floor */
3042                     break;
3043                 }
3044                 if (oldi > next_out)
3045                   SendToPlayer(&buf[next_out], oldi - next_out);
3046                 if (++i > next_out)
3047                   next_out = i;
3048                 continue;
3049             }
3050
3051             /* OK, this at least will *usually* work */
3052             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3053                 loggedOn = TRUE;
3054             }
3055
3056             if (loggedOn && !intfSet) {
3057                 if (ics_type == ICS_ICC) {
3058                   snprintf(str, MSG_SIZ,
3059                           "/set-quietly interface %s\n/set-quietly style 12\n",
3060                           programVersion);
3061                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3063                 } else if (ics_type == ICS_CHESSNET) {
3064                   snprintf(str, MSG_SIZ, "/style 12\n");
3065                 } else {
3066                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3067                   strcat(str, programVersion);
3068                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3071 #ifdef WIN32
3072                   strcat(str, "$iset nohighlight 1\n");
3073 #endif
3074                   strcat(str, "$iset lock 1\n$style 12\n");
3075                 }
3076                 SendToICS(str);
3077                 NotifyFrontendLogin();
3078                 intfSet = TRUE;
3079             }
3080
3081             if (started == STARTED_COMMENT) {
3082                 /* Accumulate characters in comment */
3083                 parse[parse_pos++] = buf[i];
3084                 if (buf[i] == '\n') {
3085                     parse[parse_pos] = NULLCHAR;
3086                     if(chattingPartner>=0) {
3087                         char mess[MSG_SIZ];
3088                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3089                         OutputChatMessage(chattingPartner, mess);
3090                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3091                             int p;
3092                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3093                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3094                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3095                                 OutputChatMessage(p, mess);
3096                                 break;
3097                             }
3098                         }
3099                         chattingPartner = -1;
3100                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3101                         collective = 0;
3102                     } else
3103                     if(!suppressKibitz) // [HGM] kibitz
3104                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3105                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3106                         int nrDigit = 0, nrAlph = 0, j;
3107                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3108                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3109                         parse[parse_pos] = NULLCHAR;
3110                         // try to be smart: if it does not look like search info, it should go to
3111                         // ICS interaction window after all, not to engine-output window.
3112                         for(j=0; j<parse_pos; j++) { // count letters and digits
3113                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3114                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3115                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3116                         }
3117                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3118                             int depth=0; float score;
3119                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3120                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3121                                 pvInfoList[forwardMostMove-1].depth = depth;
3122                                 pvInfoList[forwardMostMove-1].score = 100*score;
3123                             }
3124                             OutputKibitz(suppressKibitz, parse);
3125                         } else {
3126                             char tmp[MSG_SIZ];
3127                             if(gameMode == IcsObserving) // restore original ICS messages
3128                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3129                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3130                             else
3131                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3132                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3133                             SendToPlayer(tmp, strlen(tmp));
3134                         }
3135                         next_out = i+1; // [HGM] suppress printing in ICS window
3136                     }
3137                     started = STARTED_NONE;
3138                 } else {
3139                     /* Don't match patterns against characters in comment */
3140                     i++;
3141                     continue;
3142                 }
3143             }
3144             if (started == STARTED_CHATTER) {
3145                 if (buf[i] != '\n') {
3146                     /* Don't match patterns against characters in chatter */
3147                     i++;
3148                     continue;
3149                 }
3150                 started = STARTED_NONE;
3151                 if(suppressKibitz) next_out = i+1;
3152             }
3153
3154             /* Kludge to deal with rcmd protocol */
3155             if (firstTime && looking_at(buf, &i, "\001*")) {
3156                 DisplayFatalError(&buf[1], 0, 1);
3157                 continue;
3158             } else {
3159                 firstTime = FALSE;
3160             }
3161
3162             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3163                 ics_type = ICS_ICC;
3164                 ics_prefix = "/";
3165                 if (appData.debugMode)
3166                   fprintf(debugFP, "ics_type %d\n", ics_type);
3167                 continue;
3168             }
3169             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3170                 ics_type = ICS_FICS;
3171                 ics_prefix = "$";
3172                 if (appData.debugMode)
3173                   fprintf(debugFP, "ics_type %d\n", ics_type);
3174                 continue;
3175             }
3176             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3177                 ics_type = ICS_CHESSNET;
3178                 ics_prefix = "/";
3179                 if (appData.debugMode)
3180                   fprintf(debugFP, "ics_type %d\n", ics_type);
3181                 continue;
3182             }
3183
3184             if (!loggedOn &&
3185                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3186                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3187                  looking_at(buf, &i, "will be \"*\""))) {
3188               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3189               continue;
3190             }
3191
3192             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3193               char buf[MSG_SIZ];
3194               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3195               DisplayIcsInteractionTitle(buf);
3196               have_set_title = TRUE;
3197             }
3198
3199             /* skip finger notes */
3200             if (started == STARTED_NONE &&
3201                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3202                  (buf[i] == '1' && buf[i+1] == '0')) &&
3203                 buf[i+2] == ':' && buf[i+3] == ' ') {
3204               started = STARTED_CHATTER;
3205               i += 3;
3206               continue;
3207             }
3208
3209             oldi = i;
3210             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3211             if(appData.seekGraph) {
3212                 if(soughtPending && MatchSoughtLine(buf+i)) {
3213                     i = strstr(buf+i, "rated") - buf;
3214                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3215                     next_out = leftover_start = i;
3216                     started = STARTED_CHATTER;
3217                     suppressKibitz = TRUE;
3218                     continue;
3219                 }
3220                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3221                         && looking_at(buf, &i, "* ads displayed")) {
3222                     soughtPending = FALSE;
3223                     seekGraphUp = TRUE;
3224                     DrawSeekGraph();
3225                     continue;
3226                 }
3227                 if(appData.autoRefresh) {
3228                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3229                         int s = (ics_type == ICS_ICC); // ICC format differs
3230                         if(seekGraphUp)
3231                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3232                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3233                         looking_at(buf, &i, "*% "); // eat prompt
3234                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3235                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3236                         next_out = i; // suppress
3237                         continue;
3238                     }
3239                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3240                         char *p = star_match[0];
3241                         while(*p) {
3242                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3243                             while(*p && *p++ != ' '); // next
3244                         }
3245                         looking_at(buf, &i, "*% "); // eat prompt
3246                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247                         next_out = i;
3248                         continue;
3249                     }
3250                 }
3251             }
3252
3253             /* skip formula vars */
3254             if (started == STARTED_NONE &&
3255                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3256               started = STARTED_CHATTER;
3257               i += 3;
3258               continue;
3259             }
3260
3261             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3262             if (appData.autoKibitz && started == STARTED_NONE &&
3263                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3264                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3265                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3266                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3267                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3268                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3269                         suppressKibitz = TRUE;
3270                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3271                         next_out = i;
3272                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3273                                 && (gameMode == IcsPlayingWhite)) ||
3274                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3275                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3276                             started = STARTED_CHATTER; // own kibitz we simply discard
3277                         else {
3278                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3279                             parse_pos = 0; parse[0] = NULLCHAR;
3280                             savingComment = TRUE;
3281                             suppressKibitz = gameMode != IcsObserving ? 2 :
3282                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3283                         }
3284                         continue;
3285                 } else
3286                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3287                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3288                          && atoi(star_match[0])) {
3289                     // suppress the acknowledgements of our own autoKibitz
3290                     char *p;
3291                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3292                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3293                     SendToPlayer(star_match[0], strlen(star_match[0]));
3294                     if(looking_at(buf, &i, "*% ")) // eat prompt
3295                         suppressKibitz = FALSE;
3296                     next_out = i;
3297                     continue;
3298                 }
3299             } // [HGM] kibitz: end of patch
3300
3301             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3302
3303             // [HGM] chat: intercept tells by users for which we have an open chat window
3304             channel = -1;
3305             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3306                                            looking_at(buf, &i, "* whispers:") ||
3307                                            looking_at(buf, &i, "* kibitzes:") ||
3308                                            looking_at(buf, &i, "* shouts:") ||
3309                                            looking_at(buf, &i, "* c-shouts:") ||
3310                                            looking_at(buf, &i, "--> * ") ||
3311                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3314                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3315                 int p;
3316                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3317                 chattingPartner = -1; collective = 0;
3318
3319                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3320                 for(p=0; p<MAX_CHAT; p++) {
3321                     collective = 1;
3322                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3323                     talker[0] = '['; strcat(talker, "] ");
3324                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3325                     chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3329                 for(p=0; p<MAX_CHAT; p++) {
3330                     collective = 1;
3331                     if(!strcmp("kibitzes", chatPartner[p])) {
3332                         talker[0] = '['; strcat(talker, "] ");
3333                         chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("whispers", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3345                   if(buf[i-8] == '-' && buf[i-3] == 't')
3346                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3347                     collective = 1;
3348                     if(!strcmp("c-shouts", chatPartner[p])) {
3349                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3350                         chattingPartner = p; break;
3351                     }
3352                   }
3353                   if(chattingPartner < 0)
3354                   for(p=0; p<MAX_CHAT; p++) {
3355                     collective = 1;
3356                     if(!strcmp("shouts", chatPartner[p])) {
3357                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3358                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3359                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3360                         chattingPartner = p; break;
3361                     }
3362                   }
3363                 }
3364                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3365                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3366                     talker[0] = 0;
3367                     Colorize(ColorTell, FALSE);
3368                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3369                     collective |= 2;
3370                     chattingPartner = p; break;
3371                 }
3372                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3373                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3374                     started = STARTED_COMMENT;
3375                     parse_pos = 0; parse[0] = NULLCHAR;
3376                     savingComment = 3 + chattingPartner; // counts as TRUE
3377                     if(collective == 3) i = oldi; else {
3378                         suppressKibitz = TRUE;
3379                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3380                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3381                         continue;
3382                     }
3383                 }
3384             } // [HGM] chat: end of patch
3385
3386           backup = i;
3387             if (appData.zippyTalk || appData.zippyPlay) {
3388                 /* [DM] Backup address for color zippy lines */
3389 #if ZIPPY
3390                if (loggedOn == TRUE)
3391                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3392                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3393 #endif
3394             } // [DM] 'else { ' deleted
3395                 if (
3396                     /* Regular tells and says */
3397                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3398                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3399                     looking_at(buf, &i, "* says: ") ||
3400                     /* Don't color "message" or "messages" output */
3401                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3402                     looking_at(buf, &i, "*. * at *:*: ") ||
3403                     looking_at(buf, &i, "--* (*:*): ") ||
3404                     /* Message notifications (same color as tells) */
3405                     looking_at(buf, &i, "* has left a message ") ||
3406                     looking_at(buf, &i, "* just sent you a message:\n") ||
3407                     /* Whispers and kibitzes */
3408                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3409                     looking_at(buf, &i, "* kibitzes: ") ||
3410                     /* Channel tells */
3411                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3412
3413                   if (tkind == 1 && strchr(star_match[0], ':')) {
3414                       /* Avoid "tells you:" spoofs in channels */
3415                      tkind = 3;
3416                   }
3417                   if (star_match[0][0] == NULLCHAR ||
3418                       strchr(star_match[0], ' ') ||
3419                       (tkind == 3 && strchr(star_match[1], ' '))) {
3420                     /* Reject bogus matches */
3421                     i = oldi;
3422                   } else {
3423                     if (appData.colorize) {
3424                       if (oldi > next_out) {
3425                         SendToPlayer(&buf[next_out], oldi - next_out);
3426                         next_out = oldi;
3427                       }
3428                       switch (tkind) {
3429                       case 1:
3430                         Colorize(ColorTell, FALSE);
3431                         curColor = ColorTell;
3432                         break;
3433                       case 2:
3434                         Colorize(ColorKibitz, FALSE);
3435                         curColor = ColorKibitz;
3436                         break;
3437                       case 3:
3438                         p = strrchr(star_match[1], '(');
3439                         if (p == NULL) {
3440                           p = star_match[1];
3441                         } else {
3442                           p++;
3443                         }
3444                         if (atoi(p) == 1) {
3445                           Colorize(ColorChannel1, FALSE);
3446                           curColor = ColorChannel1;
3447                         } else {
3448                           Colorize(ColorChannel, FALSE);
3449                           curColor = ColorChannel;
3450                         }
3451                         break;
3452                       case 5:
3453                         curColor = ColorNormal;
3454                         break;
3455                       }
3456                     }
3457                     if (started == STARTED_NONE && appData.autoComment &&
3458                         (gameMode == IcsObserving ||
3459                          gameMode == IcsPlayingWhite ||
3460                          gameMode == IcsPlayingBlack)) {
3461                       parse_pos = i - oldi;
3462                       memcpy(parse, &buf[oldi], parse_pos);
3463                       parse[parse_pos] = NULLCHAR;
3464                       started = STARTED_COMMENT;
3465                       savingComment = TRUE;
3466                     } else if(collective != 3) {
3467                       started = STARTED_CHATTER;
3468                       savingComment = FALSE;
3469                     }
3470                     loggedOn = TRUE;
3471                     continue;
3472                   }
3473                 }
3474
3475                 if (looking_at(buf, &i, "* s-shouts: ") ||
3476                     looking_at(buf, &i, "* c-shouts: ")) {
3477                     if (appData.colorize) {
3478                         if (oldi > next_out) {
3479                             SendToPlayer(&buf[next_out], oldi - next_out);
3480                             next_out = oldi;
3481                         }
3482                         Colorize(ColorSShout, FALSE);
3483                         curColor = ColorSShout;
3484                     }
3485                     loggedOn = TRUE;
3486                     started = STARTED_CHATTER;
3487                     continue;
3488                 }
3489
3490                 if (looking_at(buf, &i, "--->")) {
3491                     loggedOn = TRUE;
3492                     continue;
3493                 }
3494
3495                 if (looking_at(buf, &i, "* shouts: ") ||
3496                     looking_at(buf, &i, "--> ")) {
3497                     if (appData.colorize) {
3498                         if (oldi > next_out) {
3499                             SendToPlayer(&buf[next_out], oldi - next_out);
3500                             next_out = oldi;
3501                         }
3502                         Colorize(ColorShout, FALSE);
3503                         curColor = ColorShout;
3504                     }
3505                     loggedOn = TRUE;
3506                     started = STARTED_CHATTER;
3507                     continue;
3508                 }
3509
3510                 if (looking_at( buf, &i, "Challenge:")) {
3511                     if (appData.colorize) {
3512                         if (oldi > next_out) {
3513                             SendToPlayer(&buf[next_out], oldi - next_out);
3514                             next_out = oldi;
3515                         }
3516                         Colorize(ColorChallenge, FALSE);
3517                         curColor = ColorChallenge;
3518                     }
3519                     loggedOn = TRUE;
3520                     continue;
3521                 }
3522
3523                 if (looking_at(buf, &i, "* offers you") ||
3524                     looking_at(buf, &i, "* offers to be") ||
3525                     looking_at(buf, &i, "* would like to") ||
3526                     looking_at(buf, &i, "* requests to") ||
3527                     looking_at(buf, &i, "Your opponent offers") ||
3528                     looking_at(buf, &i, "Your opponent requests")) {
3529
3530                     if (appData.colorize) {
3531                         if (oldi > next_out) {
3532                             SendToPlayer(&buf[next_out], oldi - next_out);
3533                             next_out = oldi;
3534                         }
3535                         Colorize(ColorRequest, FALSE);
3536                         curColor = ColorRequest;
3537                     }
3538                     continue;
3539                 }
3540
3541                 if (looking_at(buf, &i, "* (*) seeking")) {
3542                     if (appData.colorize) {
3543                         if (oldi > next_out) {
3544                             SendToPlayer(&buf[next_out], oldi - next_out);
3545                             next_out = oldi;
3546                         }
3547                         Colorize(ColorSeek, FALSE);
3548                         curColor = ColorSeek;
3549                     }
3550                     continue;
3551             }
3552
3553           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3554
3555             if (looking_at(buf, &i, "\\   ")) {
3556                 if (prevColor != ColorNormal) {
3557                     if (oldi > next_out) {
3558                         SendToPlayer(&buf[next_out], oldi - next_out);
3559                         next_out = oldi;
3560                     }
3561                     Colorize(prevColor, TRUE);
3562                     curColor = prevColor;
3563                 }
3564                 if (savingComment) {
3565                     parse_pos = i - oldi;
3566                     memcpy(parse, &buf[oldi], parse_pos);
3567                     parse[parse_pos] = NULLCHAR;
3568                     started = STARTED_COMMENT;
3569                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3570                         chattingPartner = savingComment - 3; // kludge to remember the box
3571                 } else {
3572                     started = STARTED_CHATTER;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "Black Strength :") ||
3578                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3579                 looking_at(buf, &i, "<10>") ||
3580                 looking_at(buf, &i, "#@#")) {
3581                 /* Wrong board style */
3582                 loggedOn = TRUE;
3583                 SendToICS(ics_prefix);
3584                 SendToICS("set style 12\n");
3585                 SendToICS(ics_prefix);
3586                 SendToICS("refresh\n");
3587                 continue;
3588             }
3589
3590             if (looking_at(buf, &i, "login:")) {
3591               if (!have_sent_ICS_logon) {
3592                 if(ICSInitScript())
3593                   have_sent_ICS_logon = 1;
3594                 else // no init script was found
3595                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3596               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3597                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3598               }
3599                 continue;
3600             }
3601
3602             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3603                 (looking_at(buf, &i, "\n<12> ") ||
3604                  looking_at(buf, &i, "<12> "))) {
3605                 loggedOn = TRUE;
3606                 if (oldi > next_out) {
3607                     SendToPlayer(&buf[next_out], oldi - next_out);
3608                 }
3609                 next_out = i;
3610                 started = STARTED_BOARD;
3611                 parse_pos = 0;
3612                 continue;
3613             }
3614
3615             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3616                 looking_at(buf, &i, "<b1> ")) {
3617                 if (oldi > next_out) {
3618                     SendToPlayer(&buf[next_out], oldi - next_out);
3619                 }
3620                 next_out = i;
3621                 started = STARTED_HOLDINGS;
3622                 parse_pos = 0;
3623                 continue;
3624             }
3625
3626             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3627                 loggedOn = TRUE;
3628                 /* Header for a move list -- first line */
3629
3630                 switch (ics_getting_history) {
3631                   case H_FALSE:
3632                     switch (gameMode) {
3633                       case IcsIdle:
3634                       case BeginningOfGame:
3635                         /* User typed "moves" or "oldmoves" while we
3636                            were idle.  Pretend we asked for these
3637                            moves and soak them up so user can step
3638                            through them and/or save them.
3639                            */
3640                         Reset(FALSE, TRUE);
3641                         gameMode = IcsObserving;
3642                         ModeHighlight();
3643                         ics_gamenum = -1;
3644                         ics_getting_history = H_GOT_UNREQ_HEADER;
3645                         break;
3646                       case EditGame: /*?*/
3647                       case EditPosition: /*?*/
3648                         /* Should above feature work in these modes too? */
3649                         /* For now it doesn't */
3650                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3651                         break;
3652                       default:
3653                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3654                         break;
3655                     }
3656                     break;
3657                   case H_REQUESTED:
3658                     /* Is this the right one? */
3659                     if (gameInfo.white && gameInfo.black &&
3660                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3661                         strcmp(gameInfo.black, star_match[2]) == 0) {
3662                         /* All is well */
3663                         ics_getting_history = H_GOT_REQ_HEADER;
3664                     }
3665                     break;
3666                   case H_GOT_REQ_HEADER:
3667                   case H_GOT_UNREQ_HEADER:
3668                   case H_GOT_UNWANTED_HEADER:
3669                   case H_GETTING_MOVES:
3670                     /* Should not happen */
3671                     DisplayError(_("Error gathering move list: two headers"), 0);
3672                     ics_getting_history = H_FALSE;
3673                     break;
3674                 }
3675
3676                 /* Save player ratings into gameInfo if needed */
3677                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3678                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3679                     (gameInfo.whiteRating == -1 ||
3680                      gameInfo.blackRating == -1)) {
3681
3682                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3683                     gameInfo.blackRating = string_to_rating(star_match[3]);
3684                     if (appData.debugMode)
3685                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3686                               gameInfo.whiteRating, gameInfo.blackRating);
3687                 }
3688                 continue;
3689             }
3690
3691             if (looking_at(buf, &i,
3692               "* * match, initial time: * minute*, increment: * second")) {
3693                 /* Header for a move list -- second line */
3694                 /* Initial board will follow if this is a wild game */
3695                 if (gameInfo.event != NULL) free(gameInfo.event);
3696                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3697                 gameInfo.event = StrSave(str);
3698                 /* [HGM] we switched variant. Translate boards if needed. */
3699                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3700                 continue;
3701             }
3702
3703             if (looking_at(buf, &i, "Move  ")) {
3704                 /* Beginning of a move list */
3705                 switch (ics_getting_history) {
3706                   case H_FALSE:
3707                     /* Normally should not happen */
3708                     /* Maybe user hit reset while we were parsing */
3709                     break;
3710                   case H_REQUESTED:
3711                     /* Happens if we are ignoring a move list that is not
3712                      * the one we just requested.  Common if the user
3713                      * tries to observe two games without turning off
3714                      * getMoveList */
3715                     break;
3716                   case H_GETTING_MOVES:
3717                     /* Should not happen */
3718                     DisplayError(_("Error gathering move list: nested"), 0);
3719                     ics_getting_history = H_FALSE;
3720                     break;
3721                   case H_GOT_REQ_HEADER:
3722                     ics_getting_history = H_GETTING_MOVES;
3723                     started = STARTED_MOVES;
3724                     parse_pos = 0;
3725                     if (oldi > next_out) {
3726                         SendToPlayer(&buf[next_out], oldi - next_out);
3727                     }
3728                     break;
3729                   case H_GOT_UNREQ_HEADER:
3730                     ics_getting_history = H_GETTING_MOVES;
3731                     started = STARTED_MOVES_NOHIDE;
3732                     parse_pos = 0;
3733                     break;
3734                   case H_GOT_UNWANTED_HEADER:
3735                     ics_getting_history = H_FALSE;
3736                     break;
3737                 }
3738                 continue;
3739             }
3740
3741             if (looking_at(buf, &i, "% ") ||
3742                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3743                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3744                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3745                     soughtPending = FALSE;
3746                     seekGraphUp = TRUE;
3747                     DrawSeekGraph();
3748                 }
3749                 if(suppressKibitz) next_out = i;
3750                 savingComment = FALSE;
3751                 suppressKibitz = 0;
3752                 switch (started) {
3753                   case STARTED_MOVES:
3754                   case STARTED_MOVES_NOHIDE:
3755                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3756                     parse[parse_pos + i - oldi] = NULLCHAR;
3757                     ParseGameHistory(parse);
3758 #if ZIPPY
3759                     if (appData.zippyPlay && first.initDone) {
3760                         FeedMovesToProgram(&first, forwardMostMove);
3761                         if (gameMode == IcsPlayingWhite) {
3762                             if (WhiteOnMove(forwardMostMove)) {
3763                                 if (first.sendTime) {
3764                                   if (first.useColors) {
3765                                     SendToProgram("black\n", &first);
3766                                   }
3767                                   SendTimeRemaining(&first, TRUE);
3768                                 }
3769                                 if (first.useColors) {
3770                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3771                                 }
3772                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3773                                 first.maybeThinking = TRUE;
3774                             } else {
3775                                 if (first.usePlayother) {
3776                                   if (first.sendTime) {
3777                                     SendTimeRemaining(&first, TRUE);
3778                                   }
3779                                   SendToProgram("playother\n", &first);
3780                                   firstMove = FALSE;
3781                                 } else {
3782                                   firstMove = TRUE;
3783                                 }
3784                             }
3785                         } else if (gameMode == IcsPlayingBlack) {
3786                             if (!WhiteOnMove(forwardMostMove)) {
3787                                 if (first.sendTime) {
3788                                   if (first.useColors) {
3789                                     SendToProgram("white\n", &first);
3790                                   }
3791                                   SendTimeRemaining(&first, FALSE);
3792                                 }
3793                                 if (first.useColors) {
3794                                   SendToProgram("black\n", &first);
3795                                 }
3796                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3797                                 first.maybeThinking = TRUE;
3798                             } else {
3799                                 if (first.usePlayother) {
3800                                   if (first.sendTime) {
3801                                     SendTimeRemaining(&first, FALSE);
3802                                   }
3803                                   SendToProgram("playother\n", &first);
3804                                   firstMove = FALSE;
3805                                 } else {
3806                                   firstMove = TRUE;
3807                                 }
3808                             }
3809                         }
3810                     }
3811 #endif
3812                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3813                         /* Moves came from oldmoves or moves command
3814                            while we weren't doing anything else.
3815                            */
3816                         currentMove = forwardMostMove;
3817                         ClearHighlights();/*!!could figure this out*/
3818                         flipView = appData.flipView;
3819                         DrawPosition(TRUE, boards[currentMove]);
3820                         DisplayBothClocks();
3821                         snprintf(str, MSG_SIZ, "%s %s %s",
3822                                 gameInfo.white, _("vs."),  gameInfo.black);
3823                         DisplayTitle(str);
3824                         gameMode = IcsIdle;
3825                     } else {
3826                         /* Moves were history of an active game */
3827                         if (gameInfo.resultDetails != NULL) {
3828                             free(gameInfo.resultDetails);
3829                             gameInfo.resultDetails = NULL;
3830                         }
3831                     }
3832                     HistorySet(parseList, backwardMostMove,
3833                                forwardMostMove, currentMove-1);
3834                     DisplayMove(currentMove - 1);
3835                     if (started == STARTED_MOVES) next_out = i;
3836                     started = STARTED_NONE;
3837                     ics_getting_history = H_FALSE;
3838                     break;
3839
3840                   case STARTED_OBSERVE:
3841                     started = STARTED_NONE;
3842                     SendToICS(ics_prefix);
3843                     SendToICS("refresh\n");
3844                     break;
3845
3846                   default:
3847                     break;
3848                 }
3849                 if(bookHit) { // [HGM] book: simulate book reply
3850                     static char bookMove[MSG_SIZ]; // a bit generous?
3851
3852                     programStats.nodes = programStats.depth = programStats.time =
3853                     programStats.score = programStats.got_only_move = 0;
3854                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3855
3856                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3857                     strcat(bookMove, bookHit);
3858                     HandleMachineMove(bookMove, &first);
3859                 }
3860                 continue;
3861             }
3862
3863             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3864                  started == STARTED_HOLDINGS ||
3865                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3866                 /* Accumulate characters in move list or board */
3867                 parse[parse_pos++] = buf[i];
3868             }
3869
3870             /* Start of game messages.  Mostly we detect start of game
3871                when the first board image arrives.  On some versions
3872                of the ICS, though, we need to do a "refresh" after starting
3873                to observe in order to get the current board right away. */
3874             if (looking_at(buf, &i, "Adding game * to observation list")) {
3875                 started = STARTED_OBSERVE;
3876                 continue;
3877             }
3878
3879             /* Handle auto-observe */
3880             if (appData.autoObserve &&
3881                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3882                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3883                 char *player;
3884                 /* Choose the player that was highlighted, if any. */
3885                 if (star_match[0][0] == '\033' ||
3886                     star_match[1][0] != '\033') {
3887                     player = star_match[0];
3888                 } else {
3889                     player = star_match[2];
3890                 }
3891                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3892                         ics_prefix, StripHighlightAndTitle(player));
3893                 SendToICS(str);
3894
3895                 /* Save ratings from notify string */
3896                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3897                 player1Rating = string_to_rating(star_match[1]);
3898                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3899                 player2Rating = string_to_rating(star_match[3]);
3900
3901                 if (appData.debugMode)
3902                   fprintf(debugFP,
3903                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3904                           player1Name, player1Rating,
3905                           player2Name, player2Rating);
3906
3907                 continue;
3908             }
3909
3910             /* Deal with automatic examine mode after a game,
3911                and with IcsObserving -> IcsExamining transition */
3912             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3913                 looking_at(buf, &i, "has made you an examiner of game *")) {
3914
3915                 int gamenum = atoi(star_match[0]);
3916                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3917                     gamenum == ics_gamenum) {
3918                     /* We were already playing or observing this game;
3919                        no need to refetch history */
3920                     gameMode = IcsExamining;
3921                     if (pausing) {
3922                         pauseExamForwardMostMove = forwardMostMove;
3923                     } else if (currentMove < forwardMostMove) {
3924                         ForwardInner(forwardMostMove);
3925                     }
3926                 } else {
3927                     /* I don't think this case really can happen */
3928                     SendToICS(ics_prefix);
3929                     SendToICS("refresh\n");
3930                 }
3931                 continue;
3932             }
3933
3934             /* Error messages */
3935 //          if (ics_user_moved) {
3936             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3937                 if (looking_at(buf, &i, "Illegal move") ||
3938                     looking_at(buf, &i, "Not a legal move") ||
3939                     looking_at(buf, &i, "Your king is in check") ||
3940                     looking_at(buf, &i, "It isn't your turn") ||
3941                     looking_at(buf, &i, "It is not your move")) {
3942                     /* Illegal move */
3943                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3944                         currentMove = forwardMostMove-1;
3945                         DisplayMove(currentMove - 1); /* before DMError */
3946                         DrawPosition(FALSE, boards[currentMove]);
3947                         SwitchClocks(forwardMostMove-1); // [HGM] race
3948                         DisplayBothClocks();
3949                     }
3950                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3951                     ics_user_moved = 0;
3952                     continue;
3953                 }
3954             }
3955
3956             if (looking_at(buf, &i, "still have time") ||
3957                 looking_at(buf, &i, "not out of time") ||
3958                 looking_at(buf, &i, "either player is out of time") ||
3959                 looking_at(buf, &i, "has timeseal; checking")) {
3960                 /* We must have called his flag a little too soon */
3961                 whiteFlag = blackFlag = FALSE;
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "added * seconds to") ||
3966                 looking_at(buf, &i, "seconds were added to")) {
3967                 /* Update the clocks */
3968                 SendToICS(ics_prefix);
3969                 SendToICS("refresh\n");
3970                 continue;
3971             }
3972
3973             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3974                 ics_clock_paused = TRUE;
3975                 StopClocks();
3976                 continue;
3977             }
3978
3979             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3980                 ics_clock_paused = FALSE;
3981                 StartClocks();
3982                 continue;
3983             }
3984
3985             /* Grab player ratings from the Creating: message.
3986                Note we have to check for the special case when
3987                the ICS inserts things like [white] or [black]. */
3988             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3989                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3990                 /* star_matches:
3991                    0    player 1 name (not necessarily white)
3992                    1    player 1 rating
3993                    2    empty, white, or black (IGNORED)
3994                    3    player 2 name (not necessarily black)
3995                    4    player 2 rating
3996
3997                    The names/ratings are sorted out when the game
3998                    actually starts (below).
3999                 */
4000                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4001                 player1Rating = string_to_rating(star_match[1]);
4002                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4003                 player2Rating = string_to_rating(star_match[4]);
4004
4005                 if (appData.debugMode)
4006                   fprintf(debugFP,
4007                           "Ratings from 'Creating:' %s %d, %s %d\n",
4008                           player1Name, player1Rating,
4009                           player2Name, player2Rating);
4010
4011                 continue;
4012             }
4013
4014             /* Improved generic start/end-of-game messages */
4015             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4016                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4017                 /* If tkind == 0: */
4018                 /* star_match[0] is the game number */
4019                 /*           [1] is the white player's name */
4020                 /*           [2] is the black player's name */
4021                 /* For end-of-game: */
4022                 /*           [3] is the reason for the game end */
4023                 /*           [4] is a PGN end game-token, preceded by " " */
4024                 /* For start-of-game: */
4025                 /*           [3] begins with "Creating" or "Continuing" */
4026                 /*           [4] is " *" or empty (don't care). */
4027                 int gamenum = atoi(star_match[0]);
4028                 char *whitename, *blackname, *why, *endtoken;
4029                 ChessMove endtype = EndOfFile;
4030
4031                 if (tkind == 0) {
4032                   whitename = star_match[1];
4033                   blackname = star_match[2];
4034                   why = star_match[3];
4035                   endtoken = star_match[4];
4036                 } else {
4037                   whitename = star_match[1];
4038                   blackname = star_match[3];
4039                   why = star_match[5];
4040                   endtoken = star_match[6];
4041                 }
4042
4043                 /* Game start messages */
4044                 if (strncmp(why, "Creating ", 9) == 0 ||
4045                     strncmp(why, "Continuing ", 11) == 0) {
4046                     gs_gamenum = gamenum;
4047                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4048                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4049                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4050 #if ZIPPY
4051                     if (appData.zippyPlay) {
4052                         ZippyGameStart(whitename, blackname);
4053                     }
4054 #endif /*ZIPPY*/
4055                     partnerBoardValid = FALSE; // [HGM] bughouse
4056                     continue;
4057                 }
4058
4059                 /* Game end messages */
4060                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4061                     ics_gamenum != gamenum) {
4062                     continue;
4063                 }
4064                 while (endtoken[0] == ' ') endtoken++;
4065                 switch (endtoken[0]) {
4066                   case '*':
4067                   default:
4068                     endtype = GameUnfinished;
4069                     break;
4070                   case '0':
4071                     endtype = BlackWins;
4072                     break;
4073                   case '1':
4074                     if (endtoken[1] == '/')
4075                       endtype = GameIsDrawn;
4076                     else
4077                       endtype = WhiteWins;
4078                     break;
4079                 }
4080                 GameEnds(endtype, why, GE_ICS);
4081 #if ZIPPY
4082                 if (appData.zippyPlay && first.initDone) {
4083                     ZippyGameEnd(endtype, why);
4084                     if (first.pr == NoProc) {
4085                       /* Start the next process early so that we'll
4086                          be ready for the next challenge */
4087                       StartChessProgram(&first);
4088                     }
4089                     /* Send "new" early, in case this command takes
4090                        a long time to finish, so that we'll be ready
4091                        for the next challenge. */
4092                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4093                     Reset(TRUE, TRUE);
4094                 }
4095 #endif /*ZIPPY*/
4096                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4097                 continue;
4098             }
4099
4100             if (looking_at(buf, &i, "Removing game * from observation") ||
4101                 looking_at(buf, &i, "no longer observing game *") ||
4102                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4103                 if (gameMode == IcsObserving &&
4104                     atoi(star_match[0]) == ics_gamenum)
4105                   {
4106                       /* icsEngineAnalyze */
4107                       if (appData.icsEngineAnalyze) {
4108                             ExitAnalyzeMode();
4109                             ModeHighlight();
4110                       }
4111                       StopClocks();
4112                       gameMode = IcsIdle;
4113                       ics_gamenum = -1;
4114                       ics_user_moved = FALSE;
4115                   }
4116                 continue;
4117             }
4118
4119             if (looking_at(buf, &i, "no longer examining game *")) {
4120                 if (gameMode == IcsExamining &&
4121                     atoi(star_match[0]) == ics_gamenum)
4122                   {
4123                       gameMode = IcsIdle;
4124                       ics_gamenum = -1;
4125                       ics_user_moved = FALSE;
4126                   }
4127                 continue;
4128             }
4129
4130             /* Advance leftover_start past any newlines we find,
4131                so only partial lines can get reparsed */
4132             if (looking_at(buf, &i, "\n")) {
4133                 prevColor = curColor;
4134                 if (curColor != ColorNormal) {
4135                     if (oldi > next_out) {
4136                         SendToPlayer(&buf[next_out], oldi - next_out);
4137                         next_out = oldi;
4138                     }
4139                     Colorize(ColorNormal, FALSE);
4140                     curColor = ColorNormal;
4141                 }
4142                 if (started == STARTED_BOARD) {
4143                     started = STARTED_NONE;
4144                     parse[parse_pos] = NULLCHAR;
4145                     ParseBoard12(parse);
4146                     ics_user_moved = 0;
4147
4148                     /* Send premove here */
4149                     if (appData.premove) {
4150                       char str[MSG_SIZ];
4151                       if (currentMove == 0 &&
4152                           gameMode == IcsPlayingWhite &&
4153                           appData.premoveWhite) {
4154                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4155                         if (appData.debugMode)
4156                           fprintf(debugFP, "Sending premove:\n");
4157                         SendToICS(str);
4158                       } else if (currentMove == 1 &&
4159                                  gameMode == IcsPlayingBlack &&
4160                                  appData.premoveBlack) {
4161                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4162                         if (appData.debugMode)
4163                           fprintf(debugFP, "Sending premove:\n");
4164                         SendToICS(str);
4165                       } else if (gotPremove) {
4166                         int oldFMM = forwardMostMove;
4167                         gotPremove = 0;
4168                         ClearPremoveHighlights();
4169                         if (appData.debugMode)
4170                           fprintf(debugFP, "Sending premove:\n");
4171                           UserMoveEvent(premoveFromX, premoveFromY,
4172                                         premoveToX, premoveToY,
4173                                         premovePromoChar);
4174                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4175                           if(moveList[oldFMM-1][1] != '@')
4176                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4177                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4178                           else // (drop)
4179                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4180                         }
4181                       }
4182                     }
4183
4184                     /* Usually suppress following prompt */
4185                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4186                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4187                         if (looking_at(buf, &i, "*% ")) {
4188                             savingComment = FALSE;
4189                             suppressKibitz = 0;
4190                         }
4191                     }
4192                     next_out = i;
4193                 } else if (started == STARTED_HOLDINGS) {
4194                     int gamenum;
4195                     char new_piece[MSG_SIZ];
4196                     started = STARTED_NONE;
4197                     parse[parse_pos] = NULLCHAR;
4198                     if (appData.debugMode)
4199                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4200                                                         parse, currentMove);
4201                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4202                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4203                         if (gameInfo.variant == VariantNormal) {
4204                           /* [HGM] We seem to switch variant during a game!
4205                            * Presumably no holdings were displayed, so we have
4206                            * to move the position two files to the right to
4207                            * create room for them!
4208                            */
4209                           VariantClass newVariant;
4210                           switch(gameInfo.boardWidth) { // base guess on board width
4211                                 case 9:  newVariant = VariantShogi; break;
4212                                 case 10: newVariant = VariantGreat; break;
4213                                 default: newVariant = VariantCrazyhouse; break;
4214                           }
4215                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4216                           /* Get a move list just to see the header, which
4217                              will tell us whether this is really bug or zh */
4218                           if (ics_getting_history == H_FALSE) {
4219                             ics_getting_history = H_REQUESTED;
4220                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4221                             SendToICS(str);
4222                           }
4223                         }
4224                         new_piece[0] = NULLCHAR;
4225                         sscanf(parse, "game %d white [%s black [%s <- %s",
4226                                &gamenum, white_holding, black_holding,
4227                                new_piece);
4228                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4229                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4230                         /* [HGM] copy holdings to board holdings area */
4231                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4232                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4233                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4234 #if ZIPPY
4235                         if (appData.zippyPlay && first.initDone) {
4236                             ZippyHoldings(white_holding, black_holding,
4237                                           new_piece);
4238                         }
4239 #endif /*ZIPPY*/
4240                         if (tinyLayout || smallLayout) {
4241                             char wh[16], bh[16];
4242                             PackHolding(wh, white_holding);
4243                             PackHolding(bh, black_holding);
4244                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4245                                     gameInfo.white, gameInfo.black);
4246                         } else {
4247                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4248                                     gameInfo.white, white_holding, _("vs."),
4249                                     gameInfo.black, black_holding);
4250                         }
4251                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4252                         DrawPosition(FALSE, boards[currentMove]);
4253                         DisplayTitle(str);
4254                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4255                         sscanf(parse, "game %d white [%s black [%s <- %s",
4256                                &gamenum, white_holding, black_holding,
4257                                new_piece);
4258                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4259                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4260                         /* [HGM] copy holdings to partner-board holdings area */
4261                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4262                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4263                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4264                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4265                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4266                       }
4267                     }
4268                     /* Suppress following prompt */
4269                     if (looking_at(buf, &i, "*% ")) {
4270                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4271                         savingComment = FALSE;
4272                         suppressKibitz = 0;
4273                     }
4274                     next_out = i;
4275                 }
4276                 continue;
4277             }
4278
4279             i++;                /* skip unparsed character and loop back */
4280         }
4281
4282         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4283 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4284 //          SendToPlayer(&buf[next_out], i - next_out);
4285             started != STARTED_HOLDINGS && leftover_start > next_out) {
4286             SendToPlayer(&buf[next_out], leftover_start - next_out);
4287             next_out = i;
4288         }
4289
4290         leftover_len = buf_len - leftover_start;
4291         /* if buffer ends with something we couldn't parse,
4292            reparse it after appending the next read */
4293
4294     } else if (count == 0) {
4295         RemoveInputSource(isr);
4296         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4297     } else {
4298         DisplayFatalError(_("Error reading from ICS"), error, 1);
4299     }
4300 }
4301
4302
4303 /* Board style 12 looks like this:
4304
4305    <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
4306
4307  * The "<12> " is stripped before it gets to this routine.  The two
4308  * trailing 0's (flip state and clock ticking) are later addition, and
4309  * some chess servers may not have them, or may have only the first.
4310  * Additional trailing fields may be added in the future.
4311  */
4312
4313 #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"
4314
4315 #define RELATION_OBSERVING_PLAYED    0
4316 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4317 #define RELATION_PLAYING_MYMOVE      1
4318 #define RELATION_PLAYING_NOTMYMOVE  -1
4319 #define RELATION_EXAMINING           2
4320 #define RELATION_ISOLATED_BOARD     -3
4321 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4322
4323 void
4324 ParseBoard12 (char *string)
4325 {
4326 #if ZIPPY
4327     int i, takeback;
4328     char *bookHit = NULL; // [HGM] book
4329 #endif
4330     GameMode newGameMode;
4331     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4332     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4333     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4334     char to_play, board_chars[200];
4335     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4336     char black[32], white[32];
4337     Board board;
4338     int prevMove = currentMove;
4339     int ticking = 2;
4340     ChessMove moveType;
4341     int fromX, fromY, toX, toY;
4342     char promoChar;
4343     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4344     Boolean weird = FALSE, reqFlag = FALSE;
4345
4346     fromX = fromY = toX = toY = -1;
4347
4348     newGame = FALSE;
4349
4350     if (appData.debugMode)
4351       fprintf(debugFP, "Parsing board: %s\n", string);
4352
4353     move_str[0] = NULLCHAR;
4354     elapsed_time[0] = NULLCHAR;
4355     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4356         int  i = 0, j;
4357         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4358             if(string[i] == ' ') { ranks++; files = 0; }
4359             else files++;
4360             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4361             i++;
4362         }
4363         for(j = 0; j <i; j++) board_chars[j] = string[j];
4364         board_chars[i] = '\0';
4365         string += i + 1;
4366     }
4367     n = sscanf(string, PATTERN, &to_play, &double_push,
4368                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4369                &gamenum, white, black, &relation, &basetime, &increment,
4370                &white_stren, &black_stren, &white_time, &black_time,
4371                &moveNum, str, elapsed_time, move_str, &ics_flip,
4372                &ticking);
4373
4374     if (n < 21) {
4375         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4376         DisplayError(str, 0);
4377         return;
4378     }
4379
4380     /* Convert the move number to internal form */
4381     moveNum = (moveNum - 1) * 2;
4382     if (to_play == 'B') moveNum++;
4383     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4384       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4385                         0, 1);
4386       return;
4387     }
4388
4389     switch (relation) {
4390       case RELATION_OBSERVING_PLAYED:
4391       case RELATION_OBSERVING_STATIC:
4392         if (gamenum == -1) {
4393             /* Old ICC buglet */
4394             relation = RELATION_OBSERVING_STATIC;
4395         }
4396         newGameMode = IcsObserving;
4397         break;
4398       case RELATION_PLAYING_MYMOVE:
4399       case RELATION_PLAYING_NOTMYMOVE:
4400         newGameMode =
4401           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4402             IcsPlayingWhite : IcsPlayingBlack;
4403         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4404         break;
4405       case RELATION_EXAMINING:
4406         newGameMode = IcsExamining;
4407         break;
4408       case RELATION_ISOLATED_BOARD:
4409       default:
4410         /* Just display this board.  If user was doing something else,
4411            we will forget about it until the next board comes. */
4412         newGameMode = IcsIdle;
4413         break;
4414       case RELATION_STARTING_POSITION:
4415         newGameMode = gameMode;
4416         break;
4417     }
4418
4419     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4420         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4421          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4422       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4423       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4424       static int lastBgGame = -1;
4425       char *toSqr;
4426       for (k = 0; k < ranks; k++) {
4427         for (j = 0; j < files; j++)
4428           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4429         if(gameInfo.holdingsWidth > 1) {
4430              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4431              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4432         }
4433       }
4434       CopyBoard(partnerBoard, board);
4435       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4436         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4437         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4438       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4439       if(toSqr = strchr(str, '-')) {
4440         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4441         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4442       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4443       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4444       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4445       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4446       if(twoBoards) {
4447           DisplayWhiteClock(white_time*fac, to_play == 'W');
4448           DisplayBlackClock(black_time*fac, to_play != 'W');
4449           activePartner = to_play;
4450           if(gamenum != lastBgGame) {
4451               char buf[MSG_SIZ];
4452               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4453               DisplayTitle(buf);
4454           }
4455           lastBgGame = gamenum;
4456           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4457                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4458       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4459                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4460       if(!twoBoards) DisplayMessage(partnerStatus, "");
4461         partnerBoardValid = TRUE;
4462       return;
4463     }
4464
4465     if(appData.dualBoard && appData.bgObserve) {
4466         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4467             SendToICS(ics_prefix), SendToICS("pobserve\n");
4468         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4469             char buf[MSG_SIZ];
4470             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4471             SendToICS(buf);
4472         }
4473     }
4474
4475     /* Modify behavior for initial board display on move listing
4476        of wild games.
4477        */
4478     switch (ics_getting_history) {
4479       case H_FALSE:
4480       case H_REQUESTED:
4481         break;
4482       case H_GOT_REQ_HEADER:
4483       case H_GOT_UNREQ_HEADER:
4484         /* This is the initial position of the current game */
4485         gamenum = ics_gamenum;
4486         moveNum = 0;            /* old ICS bug workaround */
4487         if (to_play == 'B') {
4488           startedFromSetupPosition = TRUE;
4489           blackPlaysFirst = TRUE;
4490           moveNum = 1;
4491           if (forwardMostMove == 0) forwardMostMove = 1;
4492           if (backwardMostMove == 0) backwardMostMove = 1;
4493           if (currentMove == 0) currentMove = 1;
4494         }
4495         newGameMode = gameMode;
4496         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4497         break;
4498       case H_GOT_UNWANTED_HEADER:
4499         /* This is an initial board that we don't want */
4500         return;
4501       case H_GETTING_MOVES:
4502         /* Should not happen */
4503         DisplayError(_("Error gathering move list: extra board"), 0);
4504         ics_getting_history = H_FALSE;
4505         return;
4506     }
4507
4508    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4509                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4510                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4511      /* [HGM] We seem to have switched variant unexpectedly
4512       * Try to guess new variant from board size
4513       */
4514           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4515           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4516           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4517           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4518           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4519           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4520           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4521           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4522           /* Get a move list just to see the header, which
4523              will tell us whether this is really bug or zh */
4524           if (ics_getting_history == H_FALSE) {
4525             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528           }
4529     }
4530
4531     /* Take action if this is the first board of a new game, or of a
4532        different game than is currently being displayed.  */
4533     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4534         relation == RELATION_ISOLATED_BOARD) {
4535
4536         /* Forget the old game and get the history (if any) of the new one */
4537         if (gameMode != BeginningOfGame) {
4538           Reset(TRUE, TRUE);
4539         }
4540         newGame = TRUE;
4541         if (appData.autoRaiseBoard) BoardToTop();
4542         prevMove = -3;
4543         if (gamenum == -1) {
4544             newGameMode = IcsIdle;
4545         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4546                    appData.getMoveList && !reqFlag) {
4547             /* Need to get game history */
4548             ics_getting_history = H_REQUESTED;
4549             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4550             SendToICS(str);
4551         }
4552
4553         /* Initially flip the board to have black on the bottom if playing
4554            black or if the ICS flip flag is set, but let the user change
4555            it with the Flip View button. */
4556         flipView = appData.autoFlipView ?
4557           (newGameMode == IcsPlayingBlack) || ics_flip :
4558           appData.flipView;
4559
4560         /* Done with values from previous mode; copy in new ones */
4561         gameMode = newGameMode;
4562         ModeHighlight();
4563         ics_gamenum = gamenum;
4564         if (gamenum == gs_gamenum) {
4565             int klen = strlen(gs_kind);
4566             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4567             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4568             gameInfo.event = StrSave(str);
4569         } else {
4570             gameInfo.event = StrSave("ICS game");
4571         }
4572         gameInfo.site = StrSave(appData.icsHost);
4573         gameInfo.date = PGNDate();
4574         gameInfo.round = StrSave("-");
4575         gameInfo.white = StrSave(white);
4576         gameInfo.black = StrSave(black);
4577         timeControl = basetime * 60 * 1000;
4578         timeControl_2 = 0;
4579         timeIncrement = increment * 1000;
4580         movesPerSession = 0;
4581         gameInfo.timeControl = TimeControlTagValue();
4582         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4583   if (appData.debugMode) {
4584     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4585     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4586     setbuf(debugFP, NULL);
4587   }
4588
4589         gameInfo.outOfBook = NULL;
4590
4591         /* Do we have the ratings? */
4592         if (strcmp(player1Name, white) == 0 &&
4593             strcmp(player2Name, black) == 0) {
4594             if (appData.debugMode)
4595               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4596                       player1Rating, player2Rating);
4597             gameInfo.whiteRating = player1Rating;
4598             gameInfo.blackRating = player2Rating;
4599         } else if (strcmp(player2Name, white) == 0 &&
4600                    strcmp(player1Name, black) == 0) {
4601             if (appData.debugMode)
4602               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4603                       player2Rating, player1Rating);
4604             gameInfo.whiteRating = player2Rating;
4605             gameInfo.blackRating = player1Rating;
4606         }
4607         player1Name[0] = player2Name[0] = NULLCHAR;
4608
4609         /* Silence shouts if requested */
4610         if (appData.quietPlay &&
4611             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4612             SendToICS(ics_prefix);
4613             SendToICS("set shout 0\n");
4614         }
4615     }
4616
4617     /* Deal with midgame name changes */
4618     if (!newGame) {
4619         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4620             if (gameInfo.white) free(gameInfo.white);
4621             gameInfo.white = StrSave(white);
4622         }
4623         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4624             if (gameInfo.black) free(gameInfo.black);
4625             gameInfo.black = StrSave(black);
4626         }
4627     }
4628
4629     /* Throw away game result if anything actually changes in examine mode */
4630     if (gameMode == IcsExamining && !newGame) {
4631         gameInfo.result = GameUnfinished;
4632         if (gameInfo.resultDetails != NULL) {
4633             free(gameInfo.resultDetails);
4634             gameInfo.resultDetails = NULL;
4635         }
4636     }
4637
4638     /* In pausing && IcsExamining mode, we ignore boards coming
4639        in if they are in a different variation than we are. */
4640     if (pauseExamInvalid) return;
4641     if (pausing && gameMode == IcsExamining) {
4642         if (moveNum <= pauseExamForwardMostMove) {
4643             pauseExamInvalid = TRUE;
4644             forwardMostMove = pauseExamForwardMostMove;
4645             return;
4646         }
4647     }
4648
4649   if (appData.debugMode) {
4650     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4651   }
4652     /* Parse the board */
4653     for (k = 0; k < ranks; k++) {
4654       for (j = 0; j < files; j++)
4655         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4656       if(gameInfo.holdingsWidth > 1) {
4657            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4658            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4659       }
4660     }
4661     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4662       board[5][BOARD_RGHT+1] = WhiteAngel;
4663       board[6][BOARD_RGHT+1] = WhiteMarshall;
4664       board[1][0] = BlackMarshall;
4665       board[2][0] = BlackAngel;
4666       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4667     }
4668     CopyBoard(boards[moveNum], board);
4669     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4670     if (moveNum == 0) {
4671         startedFromSetupPosition =
4672           !CompareBoards(board, initialPosition);
4673         if(startedFromSetupPosition)
4674             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4675     }
4676
4677     /* [HGM] Set castling rights. Take the outermost Rooks,
4678        to make it also work for FRC opening positions. Note that board12
4679        is really defective for later FRC positions, as it has no way to
4680        indicate which Rook can castle if they are on the same side of King.
4681        For the initial position we grant rights to the outermost Rooks,
4682        and remember thos rights, and we then copy them on positions
4683        later in an FRC game. This means WB might not recognize castlings with
4684        Rooks that have moved back to their original position as illegal,
4685        but in ICS mode that is not its job anyway.
4686     */
4687     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4688     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4689
4690         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4691             if(board[0][i] == WhiteRook) j = i;
4692         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4694             if(board[0][i] == WhiteRook) j = i;
4695         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4696         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4697             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4698         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4699         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4700             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4701         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4702
4703         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4704         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4705         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4706             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4707         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4708             if(board[BOARD_HEIGHT-1][k] == bKing)
4709                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4710         if(gameInfo.variant == VariantTwoKings) {
4711             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4712             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4713             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4714         }
4715     } else { int r;
4716         r = boards[moveNum][CASTLING][0] = initialRights[0];
4717         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4718         r = boards[moveNum][CASTLING][1] = initialRights[1];
4719         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4720         r = boards[moveNum][CASTLING][3] = initialRights[3];
4721         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4722         r = boards[moveNum][CASTLING][4] = initialRights[4];
4723         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4724         /* wildcastle kludge: always assume King has rights */
4725         r = boards[moveNum][CASTLING][2] = initialRights[2];
4726         r = boards[moveNum][CASTLING][5] = initialRights[5];
4727     }
4728     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4729     boards[moveNum][EP_STATUS] = EP_NONE;
4730     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4731     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4732     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4733
4734
4735     if (ics_getting_history == H_GOT_REQ_HEADER ||
4736         ics_getting_history == H_GOT_UNREQ_HEADER) {
4737         /* This was an initial position from a move list, not
4738            the current position */
4739         return;
4740     }
4741
4742     /* Update currentMove and known move number limits */
4743     newMove = newGame || moveNum > forwardMostMove;
4744
4745     if (newGame) {
4746         forwardMostMove = backwardMostMove = currentMove = moveNum;
4747         if (gameMode == IcsExamining && moveNum == 0) {
4748           /* Workaround for ICS limitation: we are not told the wild
4749              type when starting to examine a game.  But if we ask for
4750              the move list, the move list header will tell us */
4751             ics_getting_history = H_REQUESTED;
4752             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4753             SendToICS(str);
4754         }
4755     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4756                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4757 #if ZIPPY
4758         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4759         /* [HGM] applied this also to an engine that is silently watching        */
4760         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4761             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4762             gameInfo.variant == currentlyInitializedVariant) {
4763           takeback = forwardMostMove - moveNum;
4764           for (i = 0; i < takeback; i++) {
4765             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4766             SendToProgram("undo\n", &first);
4767           }
4768         }
4769 #endif
4770
4771         forwardMostMove = moveNum;
4772         if (!pausing || currentMove > forwardMostMove)
4773           currentMove = forwardMostMove;
4774     } else {
4775         /* New part of history that is not contiguous with old part */
4776         if (pausing && gameMode == IcsExamining) {
4777             pauseExamInvalid = TRUE;
4778             forwardMostMove = pauseExamForwardMostMove;
4779             return;
4780         }
4781         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4782 #if ZIPPY
4783             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4784                 // [HGM] when we will receive the move list we now request, it will be
4785                 // fed to the engine from the first move on. So if the engine is not
4786                 // in the initial position now, bring it there.
4787                 InitChessProgram(&first, 0);
4788             }
4789 #endif
4790             ics_getting_history = H_REQUESTED;
4791             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4792             SendToICS(str);
4793         }
4794         forwardMostMove = backwardMostMove = currentMove = moveNum;
4795     }
4796
4797     /* Update the clocks */
4798     if (strchr(elapsed_time, '.')) {
4799       /* Time is in ms */
4800       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4801       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4802     } else {
4803       /* Time is in seconds */
4804       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4805       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4806     }
4807
4808
4809 #if ZIPPY
4810     if (appData.zippyPlay && newGame &&
4811         gameMode != IcsObserving && gameMode != IcsIdle &&
4812         gameMode != IcsExamining)
4813       ZippyFirstBoard(moveNum, basetime, increment);
4814 #endif
4815
4816     /* Put the move on the move list, first converting
4817        to canonical algebraic form. */
4818     if (moveNum > 0) {
4819   if (appData.debugMode) {
4820     int f = forwardMostMove;
4821     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4822             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4823             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4824     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4825     fprintf(debugFP, "moveNum = %d\n", moveNum);
4826     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4827     setbuf(debugFP, NULL);
4828   }
4829         if (moveNum <= backwardMostMove) {
4830             /* We don't know what the board looked like before
4831                this move.  Punt. */
4832           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4833             strcat(parseList[moveNum - 1], " ");
4834             strcat(parseList[moveNum - 1], elapsed_time);
4835             moveList[moveNum - 1][0] = NULLCHAR;
4836         } else if (strcmp(move_str, "none") == 0) {
4837             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4838             /* Again, we don't know what the board looked like;
4839                this is really the start of the game. */
4840             parseList[moveNum - 1][0] = NULLCHAR;
4841             moveList[moveNum - 1][0] = NULLCHAR;
4842             backwardMostMove = moveNum;
4843             startedFromSetupPosition = TRUE;
4844             fromX = fromY = toX = toY = -1;
4845         } else {
4846           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4847           //                 So we parse the long-algebraic move string in stead of the SAN move
4848           int valid; char buf[MSG_SIZ], *prom;
4849
4850           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4851                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4852           // str looks something like "Q/a1-a2"; kill the slash
4853           if(str[1] == '/')
4854             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4855           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4856           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4857                 strcat(buf, prom); // long move lacks promo specification!
4858           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4859                 if(appData.debugMode)
4860                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4861                 safeStrCpy(move_str, buf, MSG_SIZ);
4862           }
4863           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4864                                 &fromX, &fromY, &toX, &toY, &promoChar)
4865                || ParseOneMove(buf, moveNum - 1, &moveType,
4866                                 &fromX, &fromY, &toX, &toY, &promoChar);
4867           // end of long SAN patch
4868           if (valid) {
4869             (void) CoordsToAlgebraic(boards[moveNum - 1],
4870                                      PosFlags(moveNum - 1),
4871                                      fromY, fromX, toY, toX, promoChar,
4872                                      parseList[moveNum-1]);
4873             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4874               case MT_NONE:
4875               case MT_STALEMATE:
4876               default:
4877                 break;
4878               case MT_CHECK:
4879                 if(!IS_SHOGI(gameInfo.variant))
4880                     strcat(parseList[moveNum - 1], "+");
4881                 break;
4882               case MT_CHECKMATE:
4883               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4884                 strcat(parseList[moveNum - 1], "#");
4885                 break;
4886             }
4887             strcat(parseList[moveNum - 1], " ");
4888             strcat(parseList[moveNum - 1], elapsed_time);
4889             /* currentMoveString is set as a side-effect of ParseOneMove */
4890             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4891             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4892             strcat(moveList[moveNum - 1], "\n");
4893
4894             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4895                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4896               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4897                 ChessSquare old, new = boards[moveNum][k][j];
4898                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4899                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4900                   if(old == new) continue;
4901                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4902                   else if(new == WhiteWazir || new == BlackWazir) {
4903                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4904                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4905                       else boards[moveNum][k][j] = old; // preserve type of Gold
4906                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4907                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4908               }
4909           } else {
4910             /* Move from ICS was illegal!?  Punt. */
4911             if (appData.debugMode) {
4912               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4913               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4914             }
4915             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4916             strcat(parseList[moveNum - 1], " ");
4917             strcat(parseList[moveNum - 1], elapsed_time);
4918             moveList[moveNum - 1][0] = NULLCHAR;
4919             fromX = fromY = toX = toY = -1;
4920           }
4921         }
4922   if (appData.debugMode) {
4923     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4924     setbuf(debugFP, NULL);
4925   }
4926
4927 #if ZIPPY
4928         /* Send move to chess program (BEFORE animating it). */
4929         if (appData.zippyPlay && !newGame && newMove &&
4930            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4931
4932             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4933                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4934                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4935                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4936                             move_str);
4937                     DisplayError(str, 0);
4938                 } else {
4939                     if (first.sendTime) {
4940                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4941                     }
4942                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4943                     if (firstMove && !bookHit) {
4944                         firstMove = FALSE;
4945                         if (first.useColors) {
4946                           SendToProgram(gameMode == IcsPlayingWhite ?
4947                                         "white\ngo\n" :
4948                                         "black\ngo\n", &first);
4949                         } else {
4950                           SendToProgram("go\n", &first);
4951                         }
4952                         first.maybeThinking = TRUE;
4953                     }
4954                 }
4955             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4956               if (moveList[moveNum - 1][0] == NULLCHAR) {
4957                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4958                 DisplayError(str, 0);
4959               } else {
4960                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4961                 SendMoveToProgram(moveNum - 1, &first);
4962               }
4963             }
4964         }
4965 #endif
4966     }
4967
4968     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4969         /* If move comes from a remote source, animate it.  If it
4970            isn't remote, it will have already been animated. */
4971         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4972             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4973         }
4974         if (!pausing && appData.highlightLastMove) {
4975             SetHighlights(fromX, fromY, toX, toY);
4976         }
4977     }
4978
4979     /* Start the clocks */
4980     whiteFlag = blackFlag = FALSE;
4981     appData.clockMode = !(basetime == 0 && increment == 0);
4982     if (ticking == 0) {
4983       ics_clock_paused = TRUE;
4984       StopClocks();
4985     } else if (ticking == 1) {
4986       ics_clock_paused = FALSE;
4987     }
4988     if (gameMode == IcsIdle ||
4989         relation == RELATION_OBSERVING_STATIC ||
4990         relation == RELATION_EXAMINING ||
4991         ics_clock_paused)
4992       DisplayBothClocks();
4993     else
4994       StartClocks();
4995
4996     /* Display opponents and material strengths */
4997     if (gameInfo.variant != VariantBughouse &&
4998         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4999         if (tinyLayout || smallLayout) {
5000             if(gameInfo.variant == VariantNormal)
5001               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5002                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5003                     basetime, increment);
5004             else
5005               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5006                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5007                     basetime, increment, (int) gameInfo.variant);
5008         } else {
5009             if(gameInfo.variant == VariantNormal)
5010               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5011                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5012                     basetime, increment);
5013             else
5014               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5015                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5016                     basetime, increment, VariantName(gameInfo.variant));
5017         }
5018         DisplayTitle(str);
5019   if (appData.debugMode) {
5020     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5021   }
5022     }
5023
5024
5025     /* Display the board */
5026     if (!pausing && !appData.noGUI) {
5027
5028       if (appData.premove)
5029           if (!gotPremove ||
5030              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5031              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5032               ClearPremoveHighlights();
5033
5034       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5035         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5036       DrawPosition(j, boards[currentMove]);
5037
5038       DisplayMove(moveNum - 1);
5039       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5040             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5041               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5042         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5043       }
5044     }
5045
5046     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5047 #if ZIPPY
5048     if(bookHit) { // [HGM] book: simulate book reply
5049         static char bookMove[MSG_SIZ]; // a bit generous?
5050
5051         programStats.nodes = programStats.depth = programStats.time =
5052         programStats.score = programStats.got_only_move = 0;
5053         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5054
5055         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5056         strcat(bookMove, bookHit);
5057         HandleMachineMove(bookMove, &first);
5058     }
5059 #endif
5060 }
5061
5062 void
5063 GetMoveListEvent ()
5064 {
5065     char buf[MSG_SIZ];
5066     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5067         ics_getting_history = H_REQUESTED;
5068         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5069         SendToICS(buf);
5070     }
5071 }
5072
5073 void
5074 SendToBoth (char *msg)
5075 {   // to make it easy to keep two engines in step in dual analysis
5076     SendToProgram(msg, &first);
5077     if(second.analyzing) SendToProgram(msg, &second);
5078 }
5079
5080 void
5081 AnalysisPeriodicEvent (int force)
5082 {
5083     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5084          && !force) || !appData.periodicUpdates)
5085       return;
5086
5087     /* Send . command to Crafty to collect stats */
5088     SendToBoth(".\n");
5089
5090     /* Don't send another until we get a response (this makes
5091        us stop sending to old Crafty's which don't understand
5092        the "." command (sending illegal cmds resets node count & time,
5093        which looks bad)) */
5094     programStats.ok_to_send = 0;
5095 }
5096
5097 void
5098 ics_update_width (int new_width)
5099 {
5100         ics_printf("set width %d\n", new_width);
5101 }
5102
5103 void
5104 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5105 {
5106     char buf[MSG_SIZ];
5107
5108     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5109         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5110             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5111             SendToProgram(buf, cps);
5112             return;
5113         }
5114         // null move in variant where engine does not understand it (for analysis purposes)
5115         SendBoard(cps, moveNum + 1); // send position after move in stead.
5116         return;
5117     }
5118     if (cps->useUsermove) {
5119       SendToProgram("usermove ", cps);
5120     }
5121     if (cps->useSAN) {
5122       char *space;
5123       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5124         int len = space - parseList[moveNum];
5125         memcpy(buf, parseList[moveNum], len);
5126         buf[len++] = '\n';
5127         buf[len] = NULLCHAR;
5128       } else {
5129         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5130       }
5131       SendToProgram(buf, cps);
5132     } else {
5133       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5134         AlphaRank(moveList[moveNum], 4);
5135         SendToProgram(moveList[moveNum], cps);
5136         AlphaRank(moveList[moveNum], 4); // and back
5137       } else
5138       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5139        * the engine. It would be nice to have a better way to identify castle
5140        * moves here. */
5141       if(appData.fischerCastling && cps->useOOCastle) {
5142         int fromX = moveList[moveNum][0] - AAA;
5143         int fromY = moveList[moveNum][1] - ONE;
5144         int toX = moveList[moveNum][2] - AAA;
5145         int toY = moveList[moveNum][3] - ONE;
5146         if((boards[moveNum][fromY][fromX] == WhiteKing
5147             && boards[moveNum][toY][toX] == WhiteRook)
5148            || (boards[moveNum][fromY][fromX] == BlackKing
5149                && boards[moveNum][toY][toX] == BlackRook)) {
5150           if(toX > fromX) SendToProgram("O-O\n", cps);
5151           else SendToProgram("O-O-O\n", cps);
5152         }
5153         else SendToProgram(moveList[moveNum], cps);
5154       } else
5155       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5156         char *m = moveList[moveNum];
5157         static char c[2];
5158         *c = m[7]; // promoChar
5159         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5160           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5161                                                m[2], m[3] - '0',
5162                                                m[5], m[6] - '0',
5163                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5164         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5165           *c = m[9];
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5167                                                m[7], m[8] - '0',
5168                                                m[7], m[8] - '0',
5169                                                m[5], m[6] - '0',
5170                                                m[5], m[6] - '0',
5171                                                m[2], m[3] - '0', c);
5172         } else
5173           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5174                                                m[5], m[6] - '0',
5175                                                m[5], m[6] - '0',
5176                                                m[2], m[3] - '0', c);
5177           SendToProgram(buf, cps);
5178       } else
5179       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5180         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5181           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5182           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5183                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5184         } else
5185           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5186                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5187         SendToProgram(buf, cps);
5188       }
5189       else SendToProgram(moveList[moveNum], cps);
5190       /* End of additions by Tord */
5191     }
5192
5193     /* [HGM] setting up the opening has brought engine in force mode! */
5194     /*       Send 'go' if we are in a mode where machine should play. */
5195     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5196         (gameMode == TwoMachinesPlay   ||
5197 #if ZIPPY
5198          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5199 #endif
5200          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5201         SendToProgram("go\n", cps);
5202   if (appData.debugMode) {
5203     fprintf(debugFP, "(extra)\n");
5204   }
5205     }
5206     setboardSpoiledMachineBlack = 0;
5207 }
5208
5209 void
5210 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5211 {
5212     char user_move[MSG_SIZ];
5213     char suffix[4];
5214
5215     if(gameInfo.variant == VariantSChess && promoChar) {
5216         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5217         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5218     } else suffix[0] = NULLCHAR;
5219
5220     switch (moveType) {
5221       default:
5222         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5223                 (int)moveType, fromX, fromY, toX, toY);
5224         DisplayError(user_move + strlen("say "), 0);
5225         break;
5226       case WhiteKingSideCastle:
5227       case BlackKingSideCastle:
5228       case WhiteQueenSideCastleWild:
5229       case BlackQueenSideCastleWild:
5230       /* PUSH Fabien */
5231       case WhiteHSideCastleFR:
5232       case BlackHSideCastleFR:
5233       /* POP Fabien */
5234         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5235         break;
5236       case WhiteQueenSideCastle:
5237       case BlackQueenSideCastle:
5238       case WhiteKingSideCastleWild:
5239       case BlackKingSideCastleWild:
5240       /* PUSH Fabien */
5241       case WhiteASideCastleFR:
5242       case BlackASideCastleFR:
5243       /* POP Fabien */
5244         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5245         break;
5246       case WhiteNonPromotion:
5247       case BlackNonPromotion:
5248         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5249         break;
5250       case WhitePromotion:
5251       case BlackPromotion:
5252         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5253            gameInfo.variant == VariantMakruk)
5254           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5255                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5256                 PieceToChar(WhiteFerz));
5257         else if(gameInfo.variant == VariantGreat)
5258           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5259                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5260                 PieceToChar(WhiteMan));
5261         else
5262           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5263                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5264                 promoChar);
5265         break;
5266       case WhiteDrop:
5267       case BlackDrop:
5268       drop:
5269         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5270                  ToUpper(PieceToChar((ChessSquare) fromX)),
5271                  AAA + toX, ONE + toY);
5272         break;
5273       case IllegalMove:  /* could be a variant we don't quite understand */
5274         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5275       case NormalMove:
5276       case WhiteCapturesEnPassant:
5277       case BlackCapturesEnPassant:
5278         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5279                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5280         break;
5281     }
5282     SendToICS(user_move);
5283     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5284         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5285 }
5286
5287 void
5288 UploadGameEvent ()
5289 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5290     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5291     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5292     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5293       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5294       return;
5295     }
5296     if(gameMode != IcsExamining) { // is this ever not the case?
5297         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5298
5299         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5300           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5301         } else { // on FICS we must first go to general examine mode
5302           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5303         }
5304         if(gameInfo.variant != VariantNormal) {
5305             // try figure out wild number, as xboard names are not always valid on ICS
5306             for(i=1; i<=36; i++) {
5307               snprintf(buf, MSG_SIZ, "wild/%d", i);
5308                 if(StringToVariant(buf) == gameInfo.variant) break;
5309             }
5310             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5311             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5312             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5313         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5314         SendToICS(ics_prefix);
5315         SendToICS(buf);
5316         if(startedFromSetupPosition || backwardMostMove != 0) {
5317           fen = PositionToFEN(backwardMostMove, NULL, 1);
5318           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5319             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5320             SendToICS(buf);
5321           } else { // FICS: everything has to set by separate bsetup commands
5322             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5323             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5324             SendToICS(buf);
5325             if(!WhiteOnMove(backwardMostMove)) {
5326                 SendToICS("bsetup tomove black\n");
5327             }
5328             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5329             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5330             SendToICS(buf);
5331             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5332             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5333             SendToICS(buf);
5334             i = boards[backwardMostMove][EP_STATUS];
5335             if(i >= 0) { // set e.p.
5336               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5337                 SendToICS(buf);
5338             }
5339             bsetup++;
5340           }
5341         }
5342       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5343             SendToICS("bsetup done\n"); // switch to normal examining.
5344     }
5345     for(i = backwardMostMove; i<last; i++) {
5346         char buf[20];
5347         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5348         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5349             int len = strlen(moveList[i]);
5350             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5351             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5352         }
5353         SendToICS(buf);
5354     }
5355     SendToICS(ics_prefix);
5356     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5357 }
5358
5359 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5360 int legNr = 1;
5361
5362 void
5363 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5364 {
5365     if (rf == DROP_RANK) {
5366       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5367       sprintf(move, "%c@%c%c\n",
5368                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5369     } else {
5370         if (promoChar == 'x' || promoChar == NULLCHAR) {
5371           sprintf(move, "%c%c%c%c\n",
5372                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5373           if(killX >= 0 && killY >= 0) {
5374             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5375             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5376           }
5377         } else {
5378             sprintf(move, "%c%c%c%c%c\n",
5379                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5380           if(killX >= 0 && killY >= 0) {
5381             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5382             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5383           }
5384         }
5385     }
5386 }
5387
5388 void
5389 ProcessICSInitScript (FILE *f)
5390 {
5391     char buf[MSG_SIZ];
5392
5393     while (fgets(buf, MSG_SIZ, f)) {
5394         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5395     }
5396
5397     fclose(f);
5398 }
5399
5400
5401 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5402 int dragging;
5403 static ClickType lastClickType;
5404
5405 int
5406 PieceInString (char *s, ChessSquare piece)
5407 {
5408   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5409   while((p = strchr(s, ID))) {
5410     if(!suffix || p[1] == suffix) return TRUE;
5411     s = p;
5412   }
5413   return FALSE;
5414 }
5415
5416 int
5417 Partner (ChessSquare *p)
5418 { // change piece into promotion partner if one shogi-promotes to the other
5419   ChessSquare partner = promoPartner[*p];
5420   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5421   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5422   *p = partner;
5423   return 1;
5424 }
5425
5426 void
5427 Sweep (int step)
5428 {
5429     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5430     static int toggleFlag;
5431     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5432     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5433     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5434     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5435     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5436     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5437     do {
5438         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5439         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5440         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5441         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5442         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5443         if(!step) step = -1;
5444     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5445             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5446             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5447             promoSweep == pawn ||
5448             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5449             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5450     if(toX >= 0) {
5451         int victim = boards[currentMove][toY][toX];
5452         boards[currentMove][toY][toX] = promoSweep;
5453         DrawPosition(FALSE, boards[currentMove]);
5454         boards[currentMove][toY][toX] = victim;
5455     } else
5456     ChangeDragPiece(promoSweep);
5457 }
5458
5459 int
5460 PromoScroll (int x, int y)
5461 {
5462   int step = 0;
5463
5464   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5465   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5466   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5467   if(!step) return FALSE;
5468   lastX = x; lastY = y;
5469   if((promoSweep < BlackPawn) == flipView) step = -step;
5470   if(step > 0) selectFlag = 1;
5471   if(!selectFlag) Sweep(step);
5472   return FALSE;
5473 }
5474
5475 void
5476 NextPiece (int step)
5477 {
5478     ChessSquare piece = boards[currentMove][toY][toX];
5479     do {
5480         pieceSweep -= step;
5481         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5482         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5483         if(!step) step = -1;
5484     } while(PieceToChar(pieceSweep) == '.');
5485     boards[currentMove][toY][toX] = pieceSweep;
5486     DrawPosition(FALSE, boards[currentMove]);
5487     boards[currentMove][toY][toX] = piece;
5488 }
5489 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5490 void
5491 AlphaRank (char *move, int n)
5492 {
5493 //    char *p = move, c; int x, y;
5494
5495     if (appData.debugMode) {
5496         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5497     }
5498
5499     if(move[1]=='*' &&
5500        move[2]>='0' && move[2]<='9' &&
5501        move[3]>='a' && move[3]<='x'    ) {
5502         move[1] = '@';
5503         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5504         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5505     } else
5506     if(move[0]>='0' && move[0]<='9' &&
5507        move[1]>='a' && move[1]<='x' &&
5508        move[2]>='0' && move[2]<='9' &&
5509        move[3]>='a' && move[3]<='x'    ) {
5510         /* input move, Shogi -> normal */
5511         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5512         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5513         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5514         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5515     } else
5516     if(move[1]=='@' &&
5517        move[3]>='0' && move[3]<='9' &&
5518        move[2]>='a' && move[2]<='x'    ) {
5519         move[1] = '*';
5520         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5521         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5522     } else
5523     if(
5524        move[0]>='a' && move[0]<='x' &&
5525        move[3]>='0' && move[3]<='9' &&
5526        move[2]>='a' && move[2]<='x'    ) {
5527          /* output move, normal -> Shogi */
5528         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5529         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5530         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5531         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5532         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5533     }
5534     if (appData.debugMode) {
5535         fprintf(debugFP, "   out = '%s'\n", move);
5536     }
5537 }
5538
5539 char yy_textstr[8000];
5540
5541 /* Parser for moves from gnuchess, ICS, or user typein box */
5542 Boolean
5543 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5544 {
5545     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5546
5547     switch (*moveType) {
5548       case WhitePromotion:
5549       case BlackPromotion:
5550       case WhiteNonPromotion:
5551       case BlackNonPromotion:
5552       case NormalMove:
5553       case FirstLeg:
5554       case WhiteCapturesEnPassant:
5555       case BlackCapturesEnPassant:
5556       case WhiteKingSideCastle:
5557       case WhiteQueenSideCastle:
5558       case BlackKingSideCastle:
5559       case BlackQueenSideCastle:
5560       case WhiteKingSideCastleWild:
5561       case WhiteQueenSideCastleWild:
5562       case BlackKingSideCastleWild:
5563       case BlackQueenSideCastleWild:
5564       /* Code added by Tord: */
5565       case WhiteHSideCastleFR:
5566       case WhiteASideCastleFR:
5567       case BlackHSideCastleFR:
5568       case BlackASideCastleFR:
5569       /* End of code added by Tord */
5570       case IllegalMove:         /* bug or odd chess variant */
5571         if(currentMoveString[1] == '@') { // illegal drop
5572           *fromX = WhiteOnMove(moveNum) ?
5573             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5574             (int) CharToPiece(ToLower(currentMoveString[0]));
5575           goto drop;
5576         }
5577         *fromX = currentMoveString[0] - AAA;
5578         *fromY = currentMoveString[1] - ONE;
5579         *toX = currentMoveString[2] - AAA;
5580         *toY = currentMoveString[3] - ONE;
5581         *promoChar = currentMoveString[4];
5582         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5583         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5584             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5585     if (appData.debugMode) {
5586         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5587     }
5588             *fromX = *fromY = *toX = *toY = 0;
5589             return FALSE;
5590         }
5591         if (appData.testLegality) {
5592           return (*moveType != IllegalMove);
5593         } else {
5594           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5595                          // [HGM] lion: if this is a double move we are less critical
5596                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5597         }
5598
5599       case WhiteDrop:
5600       case BlackDrop:
5601         *fromX = *moveType == WhiteDrop ?
5602           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5603           (int) CharToPiece(ToLower(currentMoveString[0]));
5604       drop:
5605         *fromY = DROP_RANK;
5606         *toX = currentMoveString[2] - AAA;
5607         *toY = currentMoveString[3] - ONE;
5608         *promoChar = NULLCHAR;
5609         return TRUE;
5610
5611       case AmbiguousMove:
5612       case ImpossibleMove:
5613       case EndOfFile:
5614       case ElapsedTime:
5615       case Comment:
5616       case PGNTag:
5617       case NAG:
5618       case WhiteWins:
5619       case BlackWins:
5620       case GameIsDrawn:
5621       default:
5622     if (appData.debugMode) {
5623         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5624     }
5625         /* bug? */
5626         *fromX = *fromY = *toX = *toY = 0;
5627         *promoChar = NULLCHAR;
5628         return FALSE;
5629     }
5630 }
5631
5632 Boolean pushed = FALSE;
5633 char *lastParseAttempt;
5634
5635 void
5636 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5637 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5638   int fromX, fromY, toX, toY; char promoChar;
5639   ChessMove moveType;
5640   Boolean valid;
5641   int nr = 0;
5642
5643   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5644   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5645     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5646     pushed = TRUE;
5647   }
5648   endPV = forwardMostMove;
5649   do {
5650     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5651     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5652     lastParseAttempt = pv;
5653     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5654     if(!valid && nr == 0 &&
5655        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5656         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5657         // Hande case where played move is different from leading PV move
5658         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5659         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5660         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5661         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5662           endPV += 2; // if position different, keep this
5663           moveList[endPV-1][0] = fromX + AAA;
5664           moveList[endPV-1][1] = fromY + ONE;
5665           moveList[endPV-1][2] = toX + AAA;
5666           moveList[endPV-1][3] = toY + ONE;
5667           parseList[endPV-1][0] = NULLCHAR;
5668           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5669         }
5670       }
5671     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5672     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5673     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5674     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5675         valid++; // allow comments in PV
5676         continue;
5677     }
5678     nr++;
5679     if(endPV+1 > framePtr) break; // no space, truncate
5680     if(!valid) break;
5681     endPV++;
5682     CopyBoard(boards[endPV], boards[endPV-1]);
5683     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5684     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5685     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5686     CoordsToAlgebraic(boards[endPV - 1],
5687                              PosFlags(endPV - 1),
5688                              fromY, fromX, toY, toX, promoChar,
5689                              parseList[endPV - 1]);
5690   } while(valid);
5691   if(atEnd == 2) return; // used hidden, for PV conversion
5692   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5693   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5694   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5695                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5696   DrawPosition(TRUE, boards[currentMove]);
5697 }
5698
5699 int
5700 MultiPV (ChessProgramState *cps, int kind)
5701 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5702         int i;
5703         for(i=0; i<cps->nrOptions; i++) {
5704             char *s = cps->option[i].name;
5705             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5706             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5707                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5708         }
5709         return -1;
5710 }
5711
5712 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5713 static int multi, pv_margin;
5714 static ChessProgramState *activeCps;
5715
5716 Boolean
5717 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5718 {
5719         int startPV, lineStart, origIndex = index;
5720         char *p, buf2[MSG_SIZ];
5721         ChessProgramState *cps = (pane ? &second : &first);
5722
5723         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5724         lastX = x; lastY = y;
5725         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5726         lineStart = startPV = index;
5727         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5728         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5729         index = startPV;
5730         do{ while(buf[index] && buf[index] != '\n') index++;
5731         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5732         buf[index] = 0;
5733         if(lineStart == 0 && gameMode == AnalyzeMode) {
5734             int n = 0;
5735             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5736             if(n == 0) { // click not on "fewer" or "more"
5737                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5738                     pv_margin = cps->option[multi].value;
5739                     activeCps = cps; // non-null signals margin adjustment
5740                 }
5741             } else if((multi = MultiPV(cps, 1)) >= 0) {
5742                 n += cps->option[multi].value; if(n < 1) n = 1;
5743                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5744                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5745                 cps->option[multi].value = n;
5746                 *start = *end = 0;
5747                 return FALSE;
5748             }
5749         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5750                 ExcludeClick(origIndex - lineStart);
5751                 return FALSE;
5752         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5753                 Collapse(origIndex - lineStart);
5754                 return FALSE;
5755         }
5756         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5757         *start = startPV; *end = index-1;
5758         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5759         return TRUE;
5760 }
5761
5762 char *
5763 PvToSAN (char *pv)
5764 {
5765         static char buf[10*MSG_SIZ];
5766         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5767         *buf = NULLCHAR;
5768         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5769         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5770         for(i = forwardMostMove; i<endPV; i++){
5771             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5772             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5773             k += strlen(buf+k);
5774         }
5775         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5776         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5777         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5778         endPV = savedEnd;
5779         return buf;
5780 }
5781
5782 Boolean
5783 LoadPV (int x, int y)
5784 { // called on right mouse click to load PV
5785   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5786   lastX = x; lastY = y;
5787   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5788   extendGame = FALSE;
5789   return TRUE;
5790 }
5791
5792 void
5793 UnLoadPV ()
5794 {
5795   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5796   if(activeCps) {
5797     if(pv_margin != activeCps->option[multi].value) {
5798       char buf[MSG_SIZ];
5799       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5800       SendToProgram(buf, activeCps);
5801       activeCps->option[multi].value = pv_margin;
5802     }
5803     activeCps = NULL;
5804     return;
5805   }
5806   if(endPV < 0) return;
5807   if(appData.autoCopyPV) CopyFENToClipboard();
5808   endPV = -1;
5809   if(extendGame && currentMove > forwardMostMove) {
5810         Boolean saveAnimate = appData.animate;
5811         if(pushed) {
5812             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5813                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5814             } else storedGames--; // abandon shelved tail of original game
5815         }
5816         pushed = FALSE;
5817         forwardMostMove = currentMove;
5818         currentMove = oldFMM;
5819         appData.animate = FALSE;
5820         ToNrEvent(forwardMostMove);
5821         appData.animate = saveAnimate;
5822   }
5823   currentMove = forwardMostMove;
5824   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5825   ClearPremoveHighlights();
5826   DrawPosition(TRUE, boards[currentMove]);
5827 }
5828
5829 void
5830 MovePV (int x, int y, int h)
5831 { // step through PV based on mouse coordinates (called on mouse move)
5832   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5833
5834   if(activeCps) { // adjusting engine's multi-pv margin
5835     if(x > lastX) pv_margin++; else
5836     if(x < lastX) pv_margin -= (pv_margin > 0);
5837     if(x != lastX) {
5838       char buf[MSG_SIZ];
5839       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5840       DisplayMessage(buf, "");
5841     }
5842     lastX = x;
5843     return;
5844   }
5845   // we must somehow check if right button is still down (might be released off board!)
5846   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5847   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5848   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5849   if(!step) return;
5850   lastX = x; lastY = y;
5851
5852   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5853   if(endPV < 0) return;
5854   if(y < margin) step = 1; else
5855   if(y > h - margin) step = -1;
5856   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5857   currentMove += step;
5858   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5859   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5860                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5861   DrawPosition(FALSE, boards[currentMove]);
5862 }
5863
5864
5865 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5866 // All positions will have equal probability, but the current method will not provide a unique
5867 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5868 #define DARK 1
5869 #define LITE 2
5870 #define ANY 3
5871
5872 int squaresLeft[4];
5873 int piecesLeft[(int)BlackPawn];
5874 int seed, nrOfShuffles;
5875
5876 void
5877 GetPositionNumber ()
5878 {       // sets global variable seed
5879         int i;
5880
5881         seed = appData.defaultFrcPosition;
5882         if(seed < 0) { // randomize based on time for negative FRC position numbers
5883                 for(i=0; i<50; i++) seed += random();
5884                 seed = random() ^ random() >> 8 ^ random() << 8;
5885                 if(seed<0) seed = -seed;
5886         }
5887 }
5888
5889 int
5890 put (Board board, int pieceType, int rank, int n, int shade)
5891 // put the piece on the (n-1)-th empty squares of the given shade
5892 {
5893         int i;
5894
5895         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5896                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5897                         board[rank][i] = (ChessSquare) pieceType;
5898                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5899                         squaresLeft[ANY]--;
5900                         piecesLeft[pieceType]--;
5901                         return i;
5902                 }
5903         }
5904         return -1;
5905 }
5906
5907
5908 void
5909 AddOnePiece (Board board, int pieceType, int rank, int shade)
5910 // calculate where the next piece goes, (any empty square), and put it there
5911 {
5912         int i;
5913
5914         i = seed % squaresLeft[shade];
5915         nrOfShuffles *= squaresLeft[shade];
5916         seed /= squaresLeft[shade];
5917         put(board, pieceType, rank, i, shade);
5918 }
5919
5920 void
5921 AddTwoPieces (Board board, int pieceType, int rank)
5922 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5923 {
5924         int i, n=squaresLeft[ANY], j=n-1, k;
5925
5926         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5927         i = seed % k;  // pick one
5928         nrOfShuffles *= k;
5929         seed /= k;
5930         while(i >= j) i -= j--;
5931         j = n - 1 - j; i += j;
5932         put(board, pieceType, rank, j, ANY);
5933         put(board, pieceType, rank, i, ANY);
5934 }
5935
5936 void
5937 SetUpShuffle (Board board, int number)
5938 {
5939         int i, p, first=1;
5940
5941         GetPositionNumber(); nrOfShuffles = 1;
5942
5943         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5944         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5945         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5946
5947         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5948
5949         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5950             p = (int) board[0][i];
5951             if(p < (int) BlackPawn) piecesLeft[p] ++;
5952             board[0][i] = EmptySquare;
5953         }
5954
5955         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5956             // shuffles restricted to allow normal castling put KRR first
5957             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5958                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5959             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5960                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5961             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5962                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5963             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5964                 put(board, WhiteRook, 0, 0, ANY);
5965             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5966         }
5967
5968         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5969             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5970             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5971                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5972                 while(piecesLeft[p] >= 2) {
5973                     AddOnePiece(board, p, 0, LITE);
5974                     AddOnePiece(board, p, 0, DARK);
5975                 }
5976                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5977             }
5978
5979         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5980             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5981             // but we leave King and Rooks for last, to possibly obey FRC restriction
5982             if(p == (int)WhiteRook) continue;
5983             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5984             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5985         }
5986
5987         // now everything is placed, except perhaps King (Unicorn) and Rooks
5988
5989         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5990             // Last King gets castling rights
5991             while(piecesLeft[(int)WhiteUnicorn]) {
5992                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5993                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5994             }
5995
5996             while(piecesLeft[(int)WhiteKing]) {
5997                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5998                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5999             }
6000
6001
6002         } else {
6003             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6004             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6005         }
6006
6007         // Only Rooks can be left; simply place them all
6008         while(piecesLeft[(int)WhiteRook]) {
6009                 i = put(board, WhiteRook, 0, 0, ANY);
6010                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6011                         if(first) {
6012                                 first=0;
6013                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6014                         }
6015                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6016                 }
6017         }
6018         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6019             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6020         }
6021
6022         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6023 }
6024
6025 int
6026 ptclen (const char *s, char *escapes)
6027 {
6028     int n = 0;
6029     if(!*escapes) return strlen(s);
6030     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6031     return n;
6032 }
6033
6034 int
6035 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6036 /* [HGM] moved here from winboard.c because of its general usefulness */
6037 /*       Basically a safe strcpy that uses the last character as King */
6038 {
6039     int result = FALSE; int NrPieces;
6040     unsigned char partner[EmptySquare];
6041
6042     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6043                     && NrPieces >= 12 && !(NrPieces&1)) {
6044         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6045
6046         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6047         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6048             char *p, c=0;
6049             if(map[j] == '/') offs = WhitePBishop - i, j++;
6050             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6051             table[i+offs] = map[j++];
6052             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6053             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6054             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6055         }
6056         table[(int) WhiteKing]  = map[j++];
6057         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6058             char *p, c=0;
6059             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6060             i = WHITE_TO_BLACK ii;
6061             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6062             table[i+offs] = map[j++];
6063             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6064             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6065             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6066         }
6067         table[(int) BlackKing]  = map[j++];
6068
6069
6070         if(*escapes) { // set up promotion pairing
6071             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6072             // pieceToChar entirely filled, so we can look up specified partners
6073             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6074                 int c = table[i];
6075                 if(c == '^' || c == '-') { // has specified partner
6076                     int p;
6077                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6078                     if(c == '^') table[i] = '+';
6079                     if(p < EmptySquare) {
6080                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6081                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6082                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6083                     }
6084                 } else if(c == '*') {
6085                     table[i] = partner[i];
6086                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6087                 }
6088             }
6089         }
6090
6091         result = TRUE;
6092     }
6093
6094     return result;
6095 }
6096
6097 int
6098 SetCharTable (unsigned char *table, const char * map)
6099 {
6100     return SetCharTableEsc(table, map, "");
6101 }
6102
6103 void
6104 Prelude (Board board)
6105 {       // [HGM] superchess: random selection of exo-pieces
6106         int i, j, k; ChessSquare p;
6107         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6108
6109         GetPositionNumber(); // use FRC position number
6110
6111         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6112             SetCharTable(pieceToChar, appData.pieceToCharTable);
6113             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6114                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6115         }
6116
6117         j = seed%4;                 seed /= 4;
6118         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6119         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6120         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6121         j = seed%3 + (seed%3 >= j); seed /= 3;
6122         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6123         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6124         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6125         j = seed%3;                 seed /= 3;
6126         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6127         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6128         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6129         j = seed%2 + (seed%2 >= j); seed /= 2;
6130         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6131         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6132         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6133         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6134         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6135         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6136         put(board, exoPieces[0],    0, 0, ANY);
6137         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6138 }
6139
6140 void
6141 InitPosition (int redraw)
6142 {
6143     ChessSquare (* pieces)[BOARD_FILES];
6144     int i, j, pawnRow=1, pieceRows=1, overrule,
6145     oldx = gameInfo.boardWidth,
6146     oldy = gameInfo.boardHeight,
6147     oldh = gameInfo.holdingsWidth;
6148     static int oldv;
6149
6150     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6151
6152     /* [AS] Initialize pv info list [HGM] and game status */
6153     {
6154         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6155             pvInfoList[i].depth = 0;
6156             boards[i][EP_STATUS] = EP_NONE;
6157             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6158         }
6159
6160         initialRulePlies = 0; /* 50-move counter start */
6161
6162         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6163         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6164     }
6165
6166
6167     /* [HGM] logic here is completely changed. In stead of full positions */
6168     /* the initialized data only consist of the two backranks. The switch */
6169     /* selects which one we will use, which is than copied to the Board   */
6170     /* initialPosition, which for the rest is initialized by Pawns and    */
6171     /* empty squares. This initial position is then copied to boards[0],  */
6172     /* possibly after shuffling, so that it remains available.            */
6173
6174     gameInfo.holdingsWidth = 0; /* default board sizes */
6175     gameInfo.boardWidth    = 8;
6176     gameInfo.boardHeight   = 8;
6177     gameInfo.holdingsSize  = 0;
6178     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6179     for(i=0; i<BOARD_FILES-6; i++)
6180       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6181     initialPosition[EP_STATUS] = EP_NONE;
6182     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6183     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6184     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6185          SetCharTable(pieceNickName, appData.pieceNickNames);
6186     else SetCharTable(pieceNickName, "............");
6187     pieces = FIDEArray;
6188
6189     switch (gameInfo.variant) {
6190     case VariantFischeRandom:
6191       shuffleOpenings = TRUE;
6192       appData.fischerCastling = TRUE;
6193     default:
6194       break;
6195     case VariantShatranj:
6196       pieces = ShatranjArray;
6197       nrCastlingRights = 0;
6198       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6199       break;
6200     case VariantMakruk:
6201       pieces = makrukArray;
6202       nrCastlingRights = 0;
6203       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6204       break;
6205     case VariantASEAN:
6206       pieces = aseanArray;
6207       nrCastlingRights = 0;
6208       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6209       break;
6210     case VariantTwoKings:
6211       pieces = twoKingsArray;
6212       break;
6213     case VariantGrand:
6214       pieces = GrandArray;
6215       nrCastlingRights = 0;
6216       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6217       gameInfo.boardWidth = 10;
6218       gameInfo.boardHeight = 10;
6219       gameInfo.holdingsSize = 7;
6220       break;
6221     case VariantCapaRandom:
6222       shuffleOpenings = TRUE;
6223       appData.fischerCastling = TRUE;
6224     case VariantCapablanca:
6225       pieces = CapablancaArray;
6226       gameInfo.boardWidth = 10;
6227       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6228       break;
6229     case VariantGothic:
6230       pieces = GothicArray;
6231       gameInfo.boardWidth = 10;
6232       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6233       break;
6234     case VariantSChess:
6235       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6236       gameInfo.holdingsSize = 7;
6237       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6238       break;
6239     case VariantJanus:
6240       pieces = JanusArray;
6241       gameInfo.boardWidth = 10;
6242       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6243       nrCastlingRights = 6;
6244         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6245         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6246         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6247         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6248         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6249         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6250       break;
6251     case VariantFalcon:
6252       pieces = FalconArray;
6253       gameInfo.boardWidth = 10;
6254       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6255       break;
6256     case VariantXiangqi:
6257       pieces = XiangqiArray;
6258       gameInfo.boardWidth  = 9;
6259       gameInfo.boardHeight = 10;
6260       nrCastlingRights = 0;
6261       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6262       break;
6263     case VariantShogi:
6264       pieces = ShogiArray;
6265       gameInfo.boardWidth  = 9;
6266       gameInfo.boardHeight = 9;
6267       gameInfo.holdingsSize = 7;
6268       nrCastlingRights = 0;
6269       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6270       break;
6271     case VariantChu:
6272       pieces = ChuArray; pieceRows = 3;
6273       gameInfo.boardWidth  = 12;
6274       gameInfo.boardHeight = 12;
6275       nrCastlingRights = 0;
6276       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6277                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6278       break;
6279     case VariantCourier:
6280       pieces = CourierArray;
6281       gameInfo.boardWidth  = 12;
6282       nrCastlingRights = 0;
6283       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6284       break;
6285     case VariantKnightmate:
6286       pieces = KnightmateArray;
6287       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6288       break;
6289     case VariantSpartan:
6290       pieces = SpartanArray;
6291       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6292       break;
6293     case VariantLion:
6294       pieces = lionArray;
6295       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6296       break;
6297     case VariantChuChess:
6298       pieces = ChuChessArray;
6299       gameInfo.boardWidth = 10;
6300       gameInfo.boardHeight = 10;
6301       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6302       break;
6303     case VariantFairy:
6304       pieces = fairyArray;
6305       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6306       break;
6307     case VariantGreat:
6308       pieces = GreatArray;
6309       gameInfo.boardWidth = 10;
6310       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6311       gameInfo.holdingsSize = 8;
6312       break;
6313     case VariantSuper:
6314       pieces = FIDEArray;
6315       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6316       gameInfo.holdingsSize = 8;
6317       startedFromSetupPosition = TRUE;
6318       break;
6319     case VariantCrazyhouse:
6320     case VariantBughouse:
6321       pieces = FIDEArray;
6322       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6323       gameInfo.holdingsSize = 5;
6324       break;
6325     case VariantWildCastle:
6326       pieces = FIDEArray;
6327       /* !!?shuffle with kings guaranteed to be on d or e file */
6328       shuffleOpenings = 1;
6329       break;
6330     case VariantNoCastle:
6331       pieces = FIDEArray;
6332       nrCastlingRights = 0;
6333       /* !!?unconstrained back-rank shuffle */
6334       shuffleOpenings = 1;
6335       break;
6336     }
6337
6338     overrule = 0;
6339     if(appData.NrFiles >= 0) {
6340         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6341         gameInfo.boardWidth = appData.NrFiles;
6342     }
6343     if(appData.NrRanks >= 0) {
6344         gameInfo.boardHeight = appData.NrRanks;
6345     }
6346     if(appData.holdingsSize >= 0) {
6347         i = appData.holdingsSize;
6348         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6349         gameInfo.holdingsSize = i;
6350     }
6351     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6352     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6353         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6354
6355     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6356     if(pawnRow < 1) pawnRow = 1;
6357     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6358        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6359     if(gameInfo.variant == VariantChu) pawnRow = 3;
6360
6361     /* User pieceToChar list overrules defaults */
6362     if(appData.pieceToCharTable != NULL)
6363         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6364
6365     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6366
6367         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6368             s = (ChessSquare) 0; /* account holding counts in guard band */
6369         for( i=0; i<BOARD_HEIGHT; i++ )
6370             initialPosition[i][j] = s;
6371
6372         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6373         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6374         initialPosition[pawnRow][j] = WhitePawn;
6375         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6376         if(gameInfo.variant == VariantXiangqi) {
6377             if(j&1) {
6378                 initialPosition[pawnRow][j] =
6379                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6380                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6381                    initialPosition[2][j] = WhiteCannon;
6382                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6383                 }
6384             }
6385         }
6386         if(gameInfo.variant == VariantChu) {
6387              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6388                initialPosition[pawnRow+1][j] = WhiteCobra,
6389                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6390              for(i=1; i<pieceRows; i++) {
6391                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6392                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6393              }
6394         }
6395         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6396             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6397                initialPosition[0][j] = WhiteRook;
6398                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6399             }
6400         }
6401         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6402     }
6403     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6404     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6405
6406             j=BOARD_LEFT+1;
6407             initialPosition[1][j] = WhiteBishop;
6408             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6409             j=BOARD_RGHT-2;
6410             initialPosition[1][j] = WhiteRook;
6411             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6412     }
6413
6414     if( nrCastlingRights == -1) {
6415         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6416         /*       This sets default castling rights from none to normal corners   */
6417         /* Variants with other castling rights must set them themselves above    */
6418         nrCastlingRights = 6;
6419
6420         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6421         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6422         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6423         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6424         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6425         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6426      }
6427
6428      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6429      if(gameInfo.variant == VariantGreat) { // promotion commoners
6430         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6431         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6432         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6433         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6434      }
6435      if( gameInfo.variant == VariantSChess ) {
6436       initialPosition[1][0] = BlackMarshall;
6437       initialPosition[2][0] = BlackAngel;
6438       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6439       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6440       initialPosition[1][1] = initialPosition[2][1] =
6441       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6442      }
6443   if (appData.debugMode) {
6444     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6445   }
6446     if(shuffleOpenings) {
6447         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6448         startedFromSetupPosition = TRUE;
6449     }
6450     if(startedFromPositionFile) {
6451       /* [HGM] loadPos: use PositionFile for every new game */
6452       CopyBoard(initialPosition, filePosition);
6453       for(i=0; i<nrCastlingRights; i++)
6454           initialRights[i] = filePosition[CASTLING][i];
6455       startedFromSetupPosition = TRUE;
6456     }
6457
6458     CopyBoard(boards[0], initialPosition);
6459
6460     if(oldx != gameInfo.boardWidth ||
6461        oldy != gameInfo.boardHeight ||
6462        oldv != gameInfo.variant ||
6463        oldh != gameInfo.holdingsWidth
6464                                          )
6465             InitDrawingSizes(-2 ,0);
6466
6467     oldv = gameInfo.variant;
6468     if (redraw)
6469       DrawPosition(TRUE, boards[currentMove]);
6470 }
6471
6472 void
6473 SendBoard (ChessProgramState *cps, int moveNum)
6474 {
6475     char message[MSG_SIZ];
6476
6477     if (cps->useSetboard) {
6478       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6479       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6480       SendToProgram(message, cps);
6481       free(fen);
6482
6483     } else {
6484       ChessSquare *bp;
6485       int i, j, left=0, right=BOARD_WIDTH;
6486       /* Kludge to set black to move, avoiding the troublesome and now
6487        * deprecated "black" command.
6488        */
6489       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6490         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6491
6492       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6493
6494       SendToProgram("edit\n", cps);
6495       SendToProgram("#\n", cps);
6496       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6497         bp = &boards[moveNum][i][left];
6498         for (j = left; j < right; j++, bp++) {
6499           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6500           if ((int) *bp < (int) BlackPawn) {
6501             if(j == BOARD_RGHT+1)
6502                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6503             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6504             if(message[0] == '+' || message[0] == '~') {
6505               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6506                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6507                         AAA + j, ONE + i - '0');
6508             }
6509             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6510                 message[1] = BOARD_RGHT   - 1 - j + '1';
6511                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6512             }
6513             SendToProgram(message, cps);
6514           }
6515         }
6516       }
6517
6518       SendToProgram("c\n", cps);
6519       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6520         bp = &boards[moveNum][i][left];
6521         for (j = left; j < right; j++, bp++) {
6522           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6523           if (((int) *bp != (int) EmptySquare)
6524               && ((int) *bp >= (int) BlackPawn)) {
6525             if(j == BOARD_LEFT-2)
6526                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6527             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6528                     AAA + j, ONE + i - '0');
6529             if(message[0] == '+' || message[0] == '~') {
6530               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6531                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6532                         AAA + j, ONE + i - '0');
6533             }
6534             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6535                 message[1] = BOARD_RGHT   - 1 - j + '1';
6536                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6537             }
6538             SendToProgram(message, cps);
6539           }
6540         }
6541       }
6542
6543       SendToProgram(".\n", cps);
6544     }
6545     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6546 }
6547
6548 char exclusionHeader[MSG_SIZ];
6549 int exCnt, excludePtr;
6550 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6551 static Exclusion excluTab[200];
6552 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6553
6554 static void
6555 WriteMap (int s)
6556 {
6557     int j;
6558     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6559     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6560 }
6561
6562 static void
6563 ClearMap ()
6564 {
6565     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6566     excludePtr = 24; exCnt = 0;
6567     WriteMap(0);
6568 }
6569
6570 static void
6571 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6572 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6573     char buf[2*MOVE_LEN], *p;
6574     Exclusion *e = excluTab;
6575     int i;
6576     for(i=0; i<exCnt; i++)
6577         if(e[i].ff == fromX && e[i].fr == fromY &&
6578            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6579     if(i == exCnt) { // was not in exclude list; add it
6580         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6581         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6582             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6583             return; // abort
6584         }
6585         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6586         excludePtr++; e[i].mark = excludePtr++;
6587         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6588         exCnt++;
6589     }
6590     exclusionHeader[e[i].mark] = state;
6591 }
6592
6593 static int
6594 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6595 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6596     char buf[MSG_SIZ];
6597     int j, k;
6598     ChessMove moveType;
6599     if((signed char)promoChar == -1) { // kludge to indicate best move
6600         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6601             return 1; // if unparsable, abort
6602     }
6603     // update exclusion map (resolving toggle by consulting existing state)
6604     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6605     j = k%8; k >>= 3;
6606     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6607     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6608          excludeMap[k] |=   1<<j;
6609     else excludeMap[k] &= ~(1<<j);
6610     // update header
6611     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6612     // inform engine
6613     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6614     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6615     SendToBoth(buf);
6616     return (state == '+');
6617 }
6618
6619 static void
6620 ExcludeClick (int index)
6621 {
6622     int i, j;
6623     Exclusion *e = excluTab;
6624     if(index < 25) { // none, best or tail clicked
6625         if(index < 13) { // none: include all
6626             WriteMap(0); // clear map
6627             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6628             SendToBoth("include all\n"); // and inform engine
6629         } else if(index > 18) { // tail
6630             if(exclusionHeader[19] == '-') { // tail was excluded
6631                 SendToBoth("include all\n");
6632                 WriteMap(0); // clear map completely
6633                 // now re-exclude selected moves
6634                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6635                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6636             } else { // tail was included or in mixed state
6637                 SendToBoth("exclude all\n");
6638                 WriteMap(0xFF); // fill map completely
6639                 // now re-include selected moves
6640                 j = 0; // count them
6641                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6642                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6643                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6644             }
6645         } else { // best
6646             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6647         }
6648     } else {
6649         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6650             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6651             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6652             break;
6653         }
6654     }
6655 }
6656
6657 ChessSquare
6658 DefaultPromoChoice (int white)
6659 {
6660     ChessSquare result;
6661     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6662        gameInfo.variant == VariantMakruk)
6663         result = WhiteFerz; // no choice
6664     else if(gameInfo.variant == VariantASEAN)
6665         result = WhiteRook; // no choice
6666     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6667         result= WhiteKing; // in Suicide Q is the last thing we want
6668     else if(gameInfo.variant == VariantSpartan)
6669         result = white ? WhiteQueen : WhiteAngel;
6670     else result = WhiteQueen;
6671     if(!white) result = WHITE_TO_BLACK result;
6672     return result;
6673 }
6674
6675 static int autoQueen; // [HGM] oneclick
6676
6677 int
6678 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6679 {
6680     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6681     /* [HGM] add Shogi promotions */
6682     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6683     ChessSquare piece, partner;
6684     ChessMove moveType;
6685     Boolean premove;
6686
6687     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6688     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6689
6690     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6691       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6692         return FALSE;
6693
6694     piece = boards[currentMove][fromY][fromX];
6695     if(gameInfo.variant == VariantChu) {
6696         promotionZoneSize = BOARD_HEIGHT/3;
6697         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6698     } else if(gameInfo.variant == VariantShogi) {
6699         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6700         highestPromotingPiece = (int)WhiteAlfil;
6701     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6702         promotionZoneSize = 3;
6703     }
6704
6705     // Treat Lance as Pawn when it is not representing Amazon or Lance
6706     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6707         if(piece == WhiteLance) piece = WhitePawn; else
6708         if(piece == BlackLance) piece = BlackPawn;
6709     }
6710
6711     // next weed out all moves that do not touch the promotion zone at all
6712     if((int)piece >= BlackPawn) {
6713         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6714              return FALSE;
6715         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6716         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6717     } else {
6718         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6719            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6720         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6721              return FALSE;
6722     }
6723
6724     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6725
6726     // weed out mandatory Shogi promotions
6727     if(gameInfo.variant == VariantShogi) {
6728         if(piece >= BlackPawn) {
6729             if(toY == 0 && piece == BlackPawn ||
6730                toY == 0 && piece == BlackQueen ||
6731                toY <= 1 && piece == BlackKnight) {
6732                 *promoChoice = '+';
6733                 return FALSE;
6734             }
6735         } else {
6736             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6737                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6738                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6739                 *promoChoice = '+';
6740                 return FALSE;
6741             }
6742         }
6743     }
6744
6745     // weed out obviously illegal Pawn moves
6746     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6747         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6748         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6749         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6750         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6751         // note we are not allowed to test for valid (non-)capture, due to premove
6752     }
6753
6754     // we either have a choice what to promote to, or (in Shogi) whether to promote
6755     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6756        gameInfo.variant == VariantMakruk) {
6757         ChessSquare p=BlackFerz;  // no choice
6758         while(p < EmptySquare) {  //but make sure we use piece that exists
6759             *promoChoice = PieceToChar(p++);
6760             if(*promoChoice != '.') break;
6761         }
6762         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6763     }
6764     // no sense asking what we must promote to if it is going to explode...
6765     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6766         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6767         return FALSE;
6768     }
6769     // give caller the default choice even if we will not make it
6770     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6771     partner = piece; // pieces can promote if the pieceToCharTable says so
6772     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6773     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6774     if(        sweepSelect && gameInfo.variant != VariantGreat
6775                            && gameInfo.variant != VariantGrand
6776                            && gameInfo.variant != VariantSuper) return FALSE;
6777     if(autoQueen) return FALSE; // predetermined
6778
6779     // suppress promotion popup on illegal moves that are not premoves
6780     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6781               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6782     if(appData.testLegality && !premove) {
6783         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6784                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6785         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6786         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6787             return FALSE;
6788     }
6789
6790     return TRUE;
6791 }
6792
6793 int
6794 InPalace (int row, int column)
6795 {   /* [HGM] for Xiangqi */
6796     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6797          column < (BOARD_WIDTH + 4)/2 &&
6798          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6799     return FALSE;
6800 }
6801
6802 int
6803 PieceForSquare (int x, int y)
6804 {
6805   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6806      return -1;
6807   else
6808      return boards[currentMove][y][x];
6809 }
6810
6811 int
6812 OKToStartUserMove (int x, int y)
6813 {
6814     ChessSquare from_piece;
6815     int white_piece;
6816
6817     if (matchMode) return FALSE;
6818     if (gameMode == EditPosition) return TRUE;
6819
6820     if (x >= 0 && y >= 0)
6821       from_piece = boards[currentMove][y][x];
6822     else
6823       from_piece = EmptySquare;
6824
6825     if (from_piece == EmptySquare) return FALSE;
6826
6827     white_piece = (int)from_piece >= (int)WhitePawn &&
6828       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6829
6830     switch (gameMode) {
6831       case AnalyzeFile:
6832       case TwoMachinesPlay:
6833       case EndOfGame:
6834         return FALSE;
6835
6836       case IcsObserving:
6837       case IcsIdle:
6838         return FALSE;
6839
6840       case MachinePlaysWhite:
6841       case IcsPlayingBlack:
6842         if (appData.zippyPlay) return FALSE;
6843         if (white_piece) {
6844             DisplayMoveError(_("You are playing Black"));
6845             return FALSE;
6846         }
6847         break;
6848
6849       case MachinePlaysBlack:
6850       case IcsPlayingWhite:
6851         if (appData.zippyPlay) return FALSE;
6852         if (!white_piece) {
6853             DisplayMoveError(_("You are playing White"));
6854             return FALSE;
6855         }
6856         break;
6857
6858       case PlayFromGameFile:
6859             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6860       case EditGame:
6861       case AnalyzeMode:
6862         if (!white_piece && WhiteOnMove(currentMove)) {
6863             DisplayMoveError(_("It is White's turn"));
6864             return FALSE;
6865         }
6866         if (white_piece && !WhiteOnMove(currentMove)) {
6867             DisplayMoveError(_("It is Black's turn"));
6868             return FALSE;
6869         }
6870         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6871             /* Editing correspondence game history */
6872             /* Could disallow this or prompt for confirmation */
6873             cmailOldMove = -1;
6874         }
6875         break;
6876
6877       case BeginningOfGame:
6878         if (appData.icsActive) return FALSE;
6879         if (!appData.noChessProgram) {
6880             if (!white_piece) {
6881                 DisplayMoveError(_("You are playing White"));
6882                 return FALSE;
6883             }
6884         }
6885         break;
6886
6887       case Training:
6888         if (!white_piece && WhiteOnMove(currentMove)) {
6889             DisplayMoveError(_("It is White's turn"));
6890             return FALSE;
6891         }
6892         if (white_piece && !WhiteOnMove(currentMove)) {
6893             DisplayMoveError(_("It is Black's turn"));
6894             return FALSE;
6895         }
6896         break;
6897
6898       default:
6899       case IcsExamining:
6900         break;
6901     }
6902     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6903         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6904         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6905         && gameMode != AnalyzeFile && gameMode != Training) {
6906         DisplayMoveError(_("Displayed position is not current"));
6907         return FALSE;
6908     }
6909     return TRUE;
6910 }
6911
6912 Boolean
6913 OnlyMove (int *x, int *y, Boolean captures)
6914 {
6915     DisambiguateClosure cl;
6916     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6917     switch(gameMode) {
6918       case MachinePlaysBlack:
6919       case IcsPlayingWhite:
6920       case BeginningOfGame:
6921         if(!WhiteOnMove(currentMove)) return FALSE;
6922         break;
6923       case MachinePlaysWhite:
6924       case IcsPlayingBlack:
6925         if(WhiteOnMove(currentMove)) return FALSE;
6926         break;
6927       case EditGame:
6928         break;
6929       default:
6930         return FALSE;
6931     }
6932     cl.pieceIn = EmptySquare;
6933     cl.rfIn = *y;
6934     cl.ffIn = *x;
6935     cl.rtIn = -1;
6936     cl.ftIn = -1;
6937     cl.promoCharIn = NULLCHAR;
6938     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6939     if( cl.kind == NormalMove ||
6940         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6941         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6942         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6943       fromX = cl.ff;
6944       fromY = cl.rf;
6945       *x = cl.ft;
6946       *y = cl.rt;
6947       return TRUE;
6948     }
6949     if(cl.kind != ImpossibleMove) return FALSE;
6950     cl.pieceIn = EmptySquare;
6951     cl.rfIn = -1;
6952     cl.ffIn = -1;
6953     cl.rtIn = *y;
6954     cl.ftIn = *x;
6955     cl.promoCharIn = NULLCHAR;
6956     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6957     if( cl.kind == NormalMove ||
6958         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6959         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6960         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6961       fromX = cl.ff;
6962       fromY = cl.rf;
6963       *x = cl.ft;
6964       *y = cl.rt;
6965       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6966       return TRUE;
6967     }
6968     return FALSE;
6969 }
6970
6971 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6972 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6973 int lastLoadGameUseList = FALSE;
6974 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6975 ChessMove lastLoadGameStart = EndOfFile;
6976 int doubleClick;
6977 Boolean addToBookFlag;
6978
6979 void
6980 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6981 {
6982     ChessMove moveType;
6983     ChessSquare pup;
6984     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6985
6986     /* Check if the user is playing in turn.  This is complicated because we
6987        let the user "pick up" a piece before it is his turn.  So the piece he
6988        tried to pick up may have been captured by the time he puts it down!
6989        Therefore we use the color the user is supposed to be playing in this
6990        test, not the color of the piece that is currently on the starting
6991        square---except in EditGame mode, where the user is playing both
6992        sides; fortunately there the capture race can't happen.  (It can
6993        now happen in IcsExamining mode, but that's just too bad.  The user
6994        will get a somewhat confusing message in that case.)
6995        */
6996
6997     switch (gameMode) {
6998       case AnalyzeFile:
6999       case TwoMachinesPlay:
7000       case EndOfGame:
7001       case IcsObserving:
7002       case IcsIdle:
7003         /* We switched into a game mode where moves are not accepted,
7004            perhaps while the mouse button was down. */
7005         return;
7006
7007       case MachinePlaysWhite:
7008         /* User is moving for Black */
7009         if (WhiteOnMove(currentMove)) {
7010             DisplayMoveError(_("It is White's turn"));
7011             return;
7012         }
7013         break;
7014
7015       case MachinePlaysBlack:
7016         /* User is moving for White */
7017         if (!WhiteOnMove(currentMove)) {
7018             DisplayMoveError(_("It is Black's turn"));
7019             return;
7020         }
7021         break;
7022
7023       case PlayFromGameFile:
7024             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7025       case EditGame:
7026       case IcsExamining:
7027       case BeginningOfGame:
7028       case AnalyzeMode:
7029       case Training:
7030         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7031         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7032             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7033             /* User is moving for Black */
7034             if (WhiteOnMove(currentMove)) {
7035                 DisplayMoveError(_("It is White's turn"));
7036                 return;
7037             }
7038         } else {
7039             /* User is moving for White */
7040             if (!WhiteOnMove(currentMove)) {
7041                 DisplayMoveError(_("It is Black's turn"));
7042                 return;
7043             }
7044         }
7045         break;
7046
7047       case IcsPlayingBlack:
7048         /* User is moving for Black */
7049         if (WhiteOnMove(currentMove)) {
7050             if (!appData.premove) {
7051                 DisplayMoveError(_("It is White's turn"));
7052             } else if (toX >= 0 && toY >= 0) {
7053                 premoveToX = toX;
7054                 premoveToY = toY;
7055                 premoveFromX = fromX;
7056                 premoveFromY = fromY;
7057                 premovePromoChar = promoChar;
7058                 gotPremove = 1;
7059                 if (appData.debugMode)
7060                     fprintf(debugFP, "Got premove: fromX %d,"
7061                             "fromY %d, toX %d, toY %d\n",
7062                             fromX, fromY, toX, toY);
7063             }
7064             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7065             return;
7066         }
7067         break;
7068
7069       case IcsPlayingWhite:
7070         /* User is moving for White */
7071         if (!WhiteOnMove(currentMove)) {
7072             if (!appData.premove) {
7073                 DisplayMoveError(_("It is Black's turn"));
7074             } else if (toX >= 0 && toY >= 0) {
7075                 premoveToX = toX;
7076                 premoveToY = toY;
7077                 premoveFromX = fromX;
7078                 premoveFromY = fromY;
7079                 premovePromoChar = promoChar;
7080                 gotPremove = 1;
7081                 if (appData.debugMode)
7082                     fprintf(debugFP, "Got premove: fromX %d,"
7083                             "fromY %d, toX %d, toY %d\n",
7084                             fromX, fromY, toX, toY);
7085             }
7086             DrawPosition(TRUE, boards[currentMove]);
7087             return;
7088         }
7089         break;
7090
7091       default:
7092         break;
7093
7094       case EditPosition:
7095         /* EditPosition, empty square, or different color piece;
7096            click-click move is possible */
7097         if (toX == -2 || toY == -2) {
7098             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7099             DrawPosition(FALSE, boards[currentMove]);
7100             return;
7101         } else if (toX >= 0 && toY >= 0) {
7102             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7103                 ChessSquare p = boards[0][rf][ff];
7104                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7105                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7106             }
7107             boards[0][toY][toX] = boards[0][fromY][fromX];
7108             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7109                 if(boards[0][fromY][0] != EmptySquare) {
7110                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7111                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7112                 }
7113             } else
7114             if(fromX == BOARD_RGHT+1) {
7115                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7116                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7117                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7118                 }
7119             } else
7120             boards[0][fromY][fromX] = gatingPiece;
7121             ClearHighlights();
7122             DrawPosition(FALSE, boards[currentMove]);
7123             return;
7124         }
7125         return;
7126     }
7127
7128     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7129     pup = boards[currentMove][toY][toX];
7130
7131     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7132     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7133          if( pup != EmptySquare ) return;
7134          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7135            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7136                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7137            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7138            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7139            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7140            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7141          fromY = DROP_RANK;
7142     }
7143
7144     /* [HGM] always test for legality, to get promotion info */
7145     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7146                                          fromY, fromX, toY, toX, promoChar);
7147
7148     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7149
7150     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7151
7152     /* [HGM] but possibly ignore an IllegalMove result */
7153     if (appData.testLegality) {
7154         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7155             DisplayMoveError(_("Illegal move"));
7156             return;
7157         }
7158     }
7159
7160     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7161         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7162              ClearPremoveHighlights(); // was included
7163         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7164         return;
7165     }
7166
7167     if(addToBookFlag) { // adding moves to book
7168         char buf[MSG_SIZ], move[MSG_SIZ];
7169         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7170         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7171                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7172         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7173         AddBookMove(buf);
7174         addToBookFlag = FALSE;
7175         ClearHighlights();
7176         return;
7177     }
7178
7179     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7180 }
7181
7182 /* Common tail of UserMoveEvent and DropMenuEvent */
7183 int
7184 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7185 {
7186     char *bookHit = 0;
7187
7188     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7189         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7190         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7191         if(WhiteOnMove(currentMove)) {
7192             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7193         } else {
7194             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7195         }
7196     }
7197
7198     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7199        move type in caller when we know the move is a legal promotion */
7200     if(moveType == NormalMove && promoChar)
7201         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7202
7203     /* [HGM] <popupFix> The following if has been moved here from
7204        UserMoveEvent(). Because it seemed to belong here (why not allow
7205        piece drops in training games?), and because it can only be
7206        performed after it is known to what we promote. */
7207     if (gameMode == Training) {
7208       /* compare the move played on the board to the next move in the
7209        * game. If they match, display the move and the opponent's response.
7210        * If they don't match, display an error message.
7211        */
7212       int saveAnimate;
7213       Board testBoard;
7214       CopyBoard(testBoard, boards[currentMove]);
7215       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7216
7217       if (CompareBoards(testBoard, boards[currentMove+1])) {
7218         ForwardInner(currentMove+1);
7219
7220         /* Autoplay the opponent's response.
7221          * if appData.animate was TRUE when Training mode was entered,
7222          * the response will be animated.
7223          */
7224         saveAnimate = appData.animate;
7225         appData.animate = animateTraining;
7226         ForwardInner(currentMove+1);
7227         appData.animate = saveAnimate;
7228
7229         /* check for the end of the game */
7230         if (currentMove >= forwardMostMove) {
7231           gameMode = PlayFromGameFile;
7232           ModeHighlight();
7233           SetTrainingModeOff();
7234           DisplayInformation(_("End of game"));
7235         }
7236       } else {
7237         DisplayError(_("Incorrect move"), 0);
7238       }
7239       return 1;
7240     }
7241
7242   /* Ok, now we know that the move is good, so we can kill
7243      the previous line in Analysis Mode */
7244   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7245                                 && currentMove < forwardMostMove) {
7246     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7247     else forwardMostMove = currentMove;
7248   }
7249
7250   ClearMap();
7251
7252   /* If we need the chess program but it's dead, restart it */
7253   ResurrectChessProgram();
7254
7255   /* A user move restarts a paused game*/
7256   if (pausing)
7257     PauseEvent();
7258
7259   thinkOutput[0] = NULLCHAR;
7260
7261   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7262
7263   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7264     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7265     return 1;
7266   }
7267
7268   if (gameMode == BeginningOfGame) {
7269     if (appData.noChessProgram) {
7270       gameMode = EditGame;
7271       SetGameInfo();
7272     } else {
7273       char buf[MSG_SIZ];
7274       gameMode = MachinePlaysBlack;
7275       StartClocks();
7276       SetGameInfo();
7277       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7278       DisplayTitle(buf);
7279       if (first.sendName) {
7280         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7281         SendToProgram(buf, &first);
7282       }
7283       StartClocks();
7284     }
7285     ModeHighlight();
7286   }
7287
7288   /* Relay move to ICS or chess engine */
7289   if (appData.icsActive) {
7290     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7291         gameMode == IcsExamining) {
7292       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7293         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7294         SendToICS("draw ");
7295         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7296       }
7297       // also send plain move, in case ICS does not understand atomic claims
7298       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7299       ics_user_moved = 1;
7300     }
7301   } else {
7302     if (first.sendTime && (gameMode == BeginningOfGame ||
7303                            gameMode == MachinePlaysWhite ||
7304                            gameMode == MachinePlaysBlack)) {
7305       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7306     }
7307     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7308          // [HGM] book: if program might be playing, let it use book
7309         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7310         first.maybeThinking = TRUE;
7311     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7312         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7313         SendBoard(&first, currentMove+1);
7314         if(second.analyzing) {
7315             if(!second.useSetboard) SendToProgram("undo\n", &second);
7316             SendBoard(&second, currentMove+1);
7317         }
7318     } else {
7319         SendMoveToProgram(forwardMostMove-1, &first);
7320         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7321     }
7322     if (currentMove == cmailOldMove + 1) {
7323       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7324     }
7325   }
7326
7327   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7328
7329   switch (gameMode) {
7330   case EditGame:
7331     if(appData.testLegality)
7332     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7333     case MT_NONE:
7334     case MT_CHECK:
7335       break;
7336     case MT_CHECKMATE:
7337     case MT_STAINMATE:
7338       if (WhiteOnMove(currentMove)) {
7339         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7340       } else {
7341         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7342       }
7343       break;
7344     case MT_STALEMATE:
7345       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7346       break;
7347     }
7348     break;
7349
7350   case MachinePlaysBlack:
7351   case MachinePlaysWhite:
7352     /* disable certain menu options while machine is thinking */
7353     SetMachineThinkingEnables();
7354     break;
7355
7356   default:
7357     break;
7358   }
7359
7360   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7361   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7362
7363   if(bookHit) { // [HGM] book: simulate book reply
7364         static char bookMove[MSG_SIZ]; // a bit generous?
7365
7366         programStats.nodes = programStats.depth = programStats.time =
7367         programStats.score = programStats.got_only_move = 0;
7368         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7369
7370         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7371         strcat(bookMove, bookHit);
7372         HandleMachineMove(bookMove, &first);
7373   }
7374   return 1;
7375 }
7376
7377 void
7378 MarkByFEN(char *fen)
7379 {
7380         int r, f;
7381         if(!appData.markers || !appData.highlightDragging) return;
7382         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7383         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7384         while(*fen) {
7385             int s = 0;
7386             marker[r][f] = 0;
7387             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7388             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7389             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7390             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7391             if(*fen == 'T') marker[r][f++] = 0; else
7392             if(*fen == 'Y') marker[r][f++] = 1; else
7393             if(*fen == 'G') marker[r][f++] = 3; else
7394             if(*fen == 'B') marker[r][f++] = 4; else
7395             if(*fen == 'C') marker[r][f++] = 5; else
7396             if(*fen == 'M') marker[r][f++] = 6; else
7397             if(*fen == 'W') marker[r][f++] = 7; else
7398             if(*fen == 'D') marker[r][f++] = 8; else
7399             if(*fen == 'R') marker[r][f++] = 2; else {
7400                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7401               f += s; fen -= s>0;
7402             }
7403             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7404             if(r < 0) break;
7405             fen++;
7406         }
7407         DrawPosition(TRUE, NULL);
7408 }
7409
7410 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7411
7412 void
7413 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7414 {
7415     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7416     Markers *m = (Markers *) closure;
7417     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7418                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7419         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7420                          || kind == WhiteCapturesEnPassant
7421                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7422     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7423 }
7424
7425 static int hoverSavedValid;
7426
7427 void
7428 MarkTargetSquares (int clear)
7429 {
7430   int x, y, sum=0;
7431   if(clear) { // no reason to ever suppress clearing
7432     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7433     hoverSavedValid = 0;
7434     if(!sum) return; // nothing was cleared,no redraw needed
7435   } else {
7436     int capt = 0;
7437     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7438        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7439     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7440     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7441       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7442       if(capt)
7443       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7444     }
7445   }
7446   DrawPosition(FALSE, NULL);
7447 }
7448
7449 int
7450 Explode (Board board, int fromX, int fromY, int toX, int toY)
7451 {
7452     if(gameInfo.variant == VariantAtomic &&
7453        (board[toY][toX] != EmptySquare ||                     // capture?
7454         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7455                          board[fromY][fromX] == BlackPawn   )
7456       )) {
7457         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7458         return TRUE;
7459     }
7460     return FALSE;
7461 }
7462
7463 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7464
7465 int
7466 CanPromote (ChessSquare piece, int y)
7467 {
7468         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7469         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7470         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7471         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7472            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7473           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7474            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7475         return (piece == BlackPawn && y <= zone ||
7476                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7477                 piece == BlackLance && y <= zone ||
7478                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7479 }
7480
7481 void
7482 HoverEvent (int xPix, int yPix, int x, int y)
7483 {
7484         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7485         int r, f;
7486         if(!first.highlight) return;
7487         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7488         if(x == oldX && y == oldY) return; // only do something if we enter new square
7489         oldFromX = fromX; oldFromY = fromY;
7490         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7491           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7492             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7493           hoverSavedValid = 1;
7494         } else if(oldX != x || oldY != y) {
7495           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7496           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7497           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7498             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7499           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7500             char buf[MSG_SIZ];
7501             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7502             SendToProgram(buf, &first);
7503           }
7504           oldX = x; oldY = y;
7505 //        SetHighlights(fromX, fromY, x, y);
7506         }
7507 }
7508
7509 void ReportClick(char *action, int x, int y)
7510 {
7511         char buf[MSG_SIZ]; // Inform engine of what user does
7512         int r, f;
7513         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7514           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7515             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7516         if(!first.highlight || gameMode == EditPosition) return;
7517         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7518         SendToProgram(buf, &first);
7519 }
7520
7521 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7522
7523 void
7524 LeftClick (ClickType clickType, int xPix, int yPix)
7525 {
7526     int x, y;
7527     Boolean saveAnimate;
7528     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7529     char promoChoice = NULLCHAR;
7530     ChessSquare piece;
7531     static TimeMark lastClickTime, prevClickTime;
7532
7533     x = EventToSquare(xPix, BOARD_WIDTH);
7534     y = EventToSquare(yPix, BOARD_HEIGHT);
7535     if (!flipView && y >= 0) {
7536         y = BOARD_HEIGHT - 1 - y;
7537     }
7538     if (flipView && x >= 0) {
7539         x = BOARD_WIDTH - 1 - x;
7540     }
7541
7542     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7543         static int dummy;
7544         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7545         right = TRUE;
7546         return;
7547     }
7548
7549     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7550
7551     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7552
7553     if (clickType == Press) ErrorPopDown();
7554     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7555
7556     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7557         defaultPromoChoice = promoSweep;
7558         promoSweep = EmptySquare;   // terminate sweep
7559         promoDefaultAltered = TRUE;
7560         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7561     }
7562
7563     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7564         if(clickType == Release) return; // ignore upclick of click-click destination
7565         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7566         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7567         if(gameInfo.holdingsWidth &&
7568                 (WhiteOnMove(currentMove)
7569                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7570                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7571             // click in right holdings, for determining promotion piece
7572             ChessSquare p = boards[currentMove][y][x];
7573             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7574             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7575             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7576                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7577                 fromX = fromY = -1;
7578                 return;
7579             }
7580         }
7581         DrawPosition(FALSE, boards[currentMove]);
7582         return;
7583     }
7584
7585     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7586     if(clickType == Press
7587             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7588               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7589               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7590         return;
7591
7592     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7593         // could be static click on premove from-square: abort premove
7594         gotPremove = 0;
7595         ClearPremoveHighlights();
7596     }
7597
7598     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7599         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7600
7601     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7602         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7603                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7604         defaultPromoChoice = DefaultPromoChoice(side);
7605     }
7606
7607     autoQueen = appData.alwaysPromoteToQueen;
7608
7609     if (fromX == -1) {
7610       int originalY = y;
7611       gatingPiece = EmptySquare;
7612       if (clickType != Press) {
7613         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7614             DragPieceEnd(xPix, yPix); dragging = 0;
7615             DrawPosition(FALSE, NULL);
7616         }
7617         return;
7618       }
7619       doubleClick = FALSE;
7620       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7621         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7622       }
7623       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7624       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7625          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7626          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7627             /* First square */
7628             if (OKToStartUserMove(fromX, fromY)) {
7629                 second = 0;
7630                 ReportClick("lift", x, y);
7631                 MarkTargetSquares(0);
7632                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7633                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7634                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7635                     promoSweep = defaultPromoChoice;
7636                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7637                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7638                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7639                 }
7640                 if (appData.highlightDragging) {
7641                     SetHighlights(fromX, fromY, -1, -1);
7642                 } else {
7643                     ClearHighlights();
7644                 }
7645             } else fromX = fromY = -1;
7646             return;
7647         }
7648     }
7649
7650     /* fromX != -1 */
7651     if (clickType == Press && gameMode != EditPosition) {
7652         ChessSquare fromP;
7653         ChessSquare toP;
7654         int frc;
7655
7656         // ignore off-board to clicks
7657         if(y < 0 || x < 0) return;
7658
7659         /* Check if clicking again on the same color piece */
7660         fromP = boards[currentMove][fromY][fromX];
7661         toP = boards[currentMove][y][x];
7662         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7663         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7664             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7665            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7666              WhitePawn <= toP && toP <= WhiteKing &&
7667              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7668              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7669             (BlackPawn <= fromP && fromP <= BlackKing &&
7670              BlackPawn <= toP && toP <= BlackKing &&
7671              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7672              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7673             /* Clicked again on same color piece -- changed his mind */
7674             second = (x == fromX && y == fromY);
7675             killX = killY = kill2X = kill2Y = -1;
7676             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7677                 second = FALSE; // first double-click rather than scond click
7678                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7679             }
7680             promoDefaultAltered = FALSE;
7681             MarkTargetSquares(1);
7682            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7683             if (appData.highlightDragging) {
7684                 SetHighlights(x, y, -1, -1);
7685             } else {
7686                 ClearHighlights();
7687             }
7688             if (OKToStartUserMove(x, y)) {
7689                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7690                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7691                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7692                  gatingPiece = boards[currentMove][fromY][fromX];
7693                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7694                 fromX = x;
7695                 fromY = y; dragging = 1;
7696                 if(!second) ReportClick("lift", x, y);
7697                 MarkTargetSquares(0);
7698                 DragPieceBegin(xPix, yPix, FALSE);
7699                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7700                     promoSweep = defaultPromoChoice;
7701                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7702                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7703                 }
7704             }
7705            }
7706            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7707            second = FALSE;
7708         }
7709         // ignore clicks on holdings
7710         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7711     }
7712
7713     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7714         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7715         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7716         return;
7717     }
7718
7719     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7720         DragPieceEnd(xPix, yPix); dragging = 0;
7721         if(clearFlag) {
7722             // a deferred attempt to click-click move an empty square on top of a piece
7723             boards[currentMove][y][x] = EmptySquare;
7724             ClearHighlights();
7725             DrawPosition(FALSE, boards[currentMove]);
7726             fromX = fromY = -1; clearFlag = 0;
7727             return;
7728         }
7729         if (appData.animateDragging) {
7730             /* Undo animation damage if any */
7731             DrawPosition(FALSE, NULL);
7732         }
7733         if (second) {
7734             /* Second up/down in same square; just abort move */
7735             second = 0;
7736             fromX = fromY = -1;
7737             gatingPiece = EmptySquare;
7738             MarkTargetSquares(1);
7739             ClearHighlights();
7740             gotPremove = 0;
7741             ClearPremoveHighlights();
7742         } else {
7743             /* First upclick in same square; start click-click mode */
7744             SetHighlights(x, y, -1, -1);
7745         }
7746         return;
7747     }
7748
7749     clearFlag = 0;
7750
7751     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7752        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7753         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7754         DisplayMessage(_("only marked squares are legal"),"");
7755         DrawPosition(TRUE, NULL);
7756         return; // ignore to-click
7757     }
7758
7759     /* we now have a different from- and (possibly off-board) to-square */
7760     /* Completed move */
7761     if(!sweepSelecting) {
7762         toX = x;
7763         toY = y;
7764     }
7765
7766     piece = boards[currentMove][fromY][fromX];
7767
7768     saveAnimate = appData.animate;
7769     if (clickType == Press) {
7770         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7771         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7772             // must be Edit Position mode with empty-square selected
7773             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7774             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7775             return;
7776         }
7777         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7778             return;
7779         }
7780         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7781             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7782         } else
7783         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7784         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7785           if(appData.sweepSelect) {
7786             promoSweep = defaultPromoChoice;
7787             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7788             selectFlag = 0; lastX = xPix; lastY = yPix;
7789             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7790             Sweep(0); // Pawn that is going to promote: preview promotion piece
7791             sweepSelecting = 1;
7792             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7793             MarkTargetSquares(1);
7794           }
7795           return; // promo popup appears on up-click
7796         }
7797         /* Finish clickclick move */
7798         if (appData.animate || appData.highlightLastMove) {
7799             SetHighlights(fromX, fromY, toX, toY);
7800         } else {
7801             ClearHighlights();
7802         }
7803     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7804         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7805         *promoRestrict = 0;
7806         if (appData.animate || appData.highlightLastMove) {
7807             SetHighlights(fromX, fromY, toX, toY);
7808         } else {
7809             ClearHighlights();
7810         }
7811     } else {
7812 #if 0
7813 // [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
7814         /* Finish drag move */
7815         if (appData.highlightLastMove) {
7816             SetHighlights(fromX, fromY, toX, toY);
7817         } else {
7818             ClearHighlights();
7819         }
7820 #endif
7821         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7822           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7823         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7824         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7825           dragging *= 2;            // flag button-less dragging if we are dragging
7826           MarkTargetSquares(1);
7827           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7828           else {
7829             kill2X = killX; kill2Y = killY;
7830             killX = x; killY = y;     // remember this square as intermediate
7831             ReportClick("put", x, y); // and inform engine
7832             ReportClick("lift", x, y);
7833             MarkTargetSquares(0);
7834             return;
7835           }
7836         }
7837         DragPieceEnd(xPix, yPix); dragging = 0;
7838         /* Don't animate move and drag both */
7839         appData.animate = FALSE;
7840     }
7841
7842     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7843     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7844         ChessSquare piece = boards[currentMove][fromY][fromX];
7845         if(gameMode == EditPosition && piece != EmptySquare &&
7846            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7847             int n;
7848
7849             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7850                 n = PieceToNumber(piece - (int)BlackPawn);
7851                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7852                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7853                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7854             } else
7855             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7856                 n = PieceToNumber(piece);
7857                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7858                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7859                 boards[currentMove][n][BOARD_WIDTH-2]++;
7860             }
7861             boards[currentMove][fromY][fromX] = EmptySquare;
7862         }
7863         ClearHighlights();
7864         fromX = fromY = -1;
7865         MarkTargetSquares(1);
7866         DrawPosition(TRUE, boards[currentMove]);
7867         return;
7868     }
7869
7870     // off-board moves should not be highlighted
7871     if(x < 0 || y < 0) ClearHighlights();
7872     else ReportClick("put", x, y);
7873
7874     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7875
7876     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7877
7878     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7879         SetHighlights(fromX, fromY, toX, toY);
7880         MarkTargetSquares(1);
7881         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7882             // [HGM] super: promotion to captured piece selected from holdings
7883             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7884             promotionChoice = TRUE;
7885             // kludge follows to temporarily execute move on display, without promoting yet
7886             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7887             boards[currentMove][toY][toX] = p;
7888             DrawPosition(FALSE, boards[currentMove]);
7889             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7890             boards[currentMove][toY][toX] = q;
7891             DisplayMessage("Click in holdings to choose piece", "");
7892             return;
7893         }
7894         PromotionPopUp(promoChoice);
7895     } else {
7896         int oldMove = currentMove;
7897         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7898         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7899         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7900         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7901            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7902             DrawPosition(TRUE, boards[currentMove]);
7903         MarkTargetSquares(1);
7904         fromX = fromY = -1;
7905     }
7906     appData.animate = saveAnimate;
7907     if (appData.animate || appData.animateDragging) {
7908         /* Undo animation damage if needed */
7909         DrawPosition(FALSE, NULL);
7910     }
7911 }
7912
7913 int
7914 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7915 {   // front-end-free part taken out of PieceMenuPopup
7916     int whichMenu; int xSqr, ySqr;
7917
7918     if(seekGraphUp) { // [HGM] seekgraph
7919         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7920         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7921         return -2;
7922     }
7923
7924     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7925          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7926         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7927         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7928         if(action == Press)   {
7929             originalFlip = flipView;
7930             flipView = !flipView; // temporarily flip board to see game from partners perspective
7931             DrawPosition(TRUE, partnerBoard);
7932             DisplayMessage(partnerStatus, "");
7933             partnerUp = TRUE;
7934         } else if(action == Release) {
7935             flipView = originalFlip;
7936             DrawPosition(TRUE, boards[currentMove]);
7937             partnerUp = FALSE;
7938         }
7939         return -2;
7940     }
7941
7942     xSqr = EventToSquare(x, BOARD_WIDTH);
7943     ySqr = EventToSquare(y, BOARD_HEIGHT);
7944     if (action == Release) {
7945         if(pieceSweep != EmptySquare) {
7946             EditPositionMenuEvent(pieceSweep, toX, toY);
7947             pieceSweep = EmptySquare;
7948         } else UnLoadPV(); // [HGM] pv
7949     }
7950     if (action != Press) return -2; // return code to be ignored
7951     switch (gameMode) {
7952       case IcsExamining:
7953         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7954       case EditPosition:
7955         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7956         if (xSqr < 0 || ySqr < 0) return -1;
7957         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7958         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7959         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7960         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7961         NextPiece(0);
7962         return 2; // grab
7963       case IcsObserving:
7964         if(!appData.icsEngineAnalyze) return -1;
7965       case IcsPlayingWhite:
7966       case IcsPlayingBlack:
7967         if(!appData.zippyPlay) goto noZip;
7968       case AnalyzeMode:
7969       case AnalyzeFile:
7970       case MachinePlaysWhite:
7971       case MachinePlaysBlack:
7972       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7973         if (!appData.dropMenu) {
7974           LoadPV(x, y);
7975           return 2; // flag front-end to grab mouse events
7976         }
7977         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7978            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7979       case EditGame:
7980       noZip:
7981         if (xSqr < 0 || ySqr < 0) return -1;
7982         if (!appData.dropMenu || appData.testLegality &&
7983             gameInfo.variant != VariantBughouse &&
7984             gameInfo.variant != VariantCrazyhouse) return -1;
7985         whichMenu = 1; // drop menu
7986         break;
7987       default:
7988         return -1;
7989     }
7990
7991     if (((*fromX = xSqr) < 0) ||
7992         ((*fromY = ySqr) < 0)) {
7993         *fromX = *fromY = -1;
7994         return -1;
7995     }
7996     if (flipView)
7997       *fromX = BOARD_WIDTH - 1 - *fromX;
7998     else
7999       *fromY = BOARD_HEIGHT - 1 - *fromY;
8000
8001     return whichMenu;
8002 }
8003
8004 void
8005 Wheel (int dir, int x, int y)
8006 {
8007     if(gameMode == EditPosition) {
8008         int xSqr = EventToSquare(x, BOARD_WIDTH);
8009         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8010         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8011         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8012         do {
8013             boards[currentMove][ySqr][xSqr] += dir;
8014             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8015             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8016         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8017         DrawPosition(FALSE, boards[currentMove]);
8018     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8019 }
8020
8021 void
8022 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8023 {
8024 //    char * hint = lastHint;
8025     FrontEndProgramStats stats;
8026
8027     stats.which = cps == &first ? 0 : 1;
8028     stats.depth = cpstats->depth;
8029     stats.nodes = cpstats->nodes;
8030     stats.score = cpstats->score;
8031     stats.time = cpstats->time;
8032     stats.pv = cpstats->movelist;
8033     stats.hint = lastHint;
8034     stats.an_move_index = 0;
8035     stats.an_move_count = 0;
8036
8037     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8038         stats.hint = cpstats->move_name;
8039         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8040         stats.an_move_count = cpstats->nr_moves;
8041     }
8042
8043     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
8044
8045     SetProgramStats( &stats );
8046 }
8047
8048 void
8049 ClearEngineOutputPane (int which)
8050 {
8051     static FrontEndProgramStats dummyStats;
8052     dummyStats.which = which;
8053     dummyStats.pv = "#";
8054     SetProgramStats( &dummyStats );
8055 }
8056
8057 #define MAXPLAYERS 500
8058
8059 char *
8060 TourneyStandings (int display)
8061 {
8062     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8063     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8064     char result, *p, *names[MAXPLAYERS];
8065
8066     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8067         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8068     names[0] = p = strdup(appData.participants);
8069     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8070
8071     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8072
8073     while(result = appData.results[nr]) {
8074         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8075         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8076         wScore = bScore = 0;
8077         switch(result) {
8078           case '+': wScore = 2; break;
8079           case '-': bScore = 2; break;
8080           case '=': wScore = bScore = 1; break;
8081           case ' ':
8082           case '*': return strdup("busy"); // tourney not finished
8083         }
8084         score[w] += wScore;
8085         score[b] += bScore;
8086         games[w]++;
8087         games[b]++;
8088         nr++;
8089     }
8090     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8091     for(w=0; w<nPlayers; w++) {
8092         bScore = -1;
8093         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8094         ranking[w] = b; points[w] = bScore; score[b] = -2;
8095     }
8096     p = malloc(nPlayers*34+1);
8097     for(w=0; w<nPlayers && w<display; w++)
8098         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8099     free(names[0]);
8100     return p;
8101 }
8102
8103 void
8104 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8105 {       // count all piece types
8106         int p, f, r;
8107         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8108         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8109         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8110                 p = board[r][f];
8111                 pCnt[p]++;
8112                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8113                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8114                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8115                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8116                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8117                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8118         }
8119 }
8120
8121 int
8122 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8123 {
8124         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8125         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8126
8127         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8128         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8129         if(myPawns == 2 && nMine == 3) // KPP
8130             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8131         if(myPawns == 1 && nMine == 2) // KP
8132             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8133         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8134             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8135         if(myPawns) return FALSE;
8136         if(pCnt[WhiteRook+side])
8137             return pCnt[BlackRook-side] ||
8138                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8139                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8140                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8141         if(pCnt[WhiteCannon+side]) {
8142             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8143             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8144         }
8145         if(pCnt[WhiteKnight+side])
8146             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8147         return FALSE;
8148 }
8149
8150 int
8151 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8152 {
8153         VariantClass v = gameInfo.variant;
8154
8155         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8156         if(v == VariantShatranj) return TRUE; // always winnable through baring
8157         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8158         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8159
8160         if(v == VariantXiangqi) {
8161                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8162
8163                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8164                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8165                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8166                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8167                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8168                 if(stale) // we have at least one last-rank P plus perhaps C
8169                     return majors // KPKX
8170                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8171                 else // KCA*E*
8172                     return pCnt[WhiteFerz+side] // KCAK
8173                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8174                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8175                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8176
8177         } else if(v == VariantKnightmate) {
8178                 if(nMine == 1) return FALSE;
8179                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8180         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8181                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8182
8183                 if(nMine == 1) return FALSE; // bare King
8184                 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
8185                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8186                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8187                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8188                 if(pCnt[WhiteKnight+side])
8189                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8190                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8191                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8192                 if(nBishops)
8193                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8194                 if(pCnt[WhiteAlfil+side])
8195                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8196                 if(pCnt[WhiteWazir+side])
8197                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8198         }
8199
8200         return TRUE;
8201 }
8202
8203 int
8204 CompareWithRights (Board b1, Board b2)
8205 {
8206     int rights = 0;
8207     if(!CompareBoards(b1, b2)) return FALSE;
8208     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8209     /* compare castling rights */
8210     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8211            rights++; /* King lost rights, while rook still had them */
8212     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8213         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8214            rights++; /* but at least one rook lost them */
8215     }
8216     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8217            rights++;
8218     if( b1[CASTLING][5] != NoRights ) {
8219         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8220            rights++;
8221     }
8222     return rights == 0;
8223 }
8224
8225 int
8226 Adjudicate (ChessProgramState *cps)
8227 {       // [HGM] some adjudications useful with buggy engines
8228         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8229         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8230         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8231         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8232         int k, drop, count = 0; static int bare = 1;
8233         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8234         Boolean canAdjudicate = !appData.icsActive;
8235
8236         // most tests only when we understand the game, i.e. legality-checking on
8237             if( appData.testLegality )
8238             {   /* [HGM] Some more adjudications for obstinate engines */
8239                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8240                 static int moveCount = 6;
8241                 ChessMove result;
8242                 char *reason = NULL;
8243
8244                 /* Count what is on board. */
8245                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8246
8247                 /* Some material-based adjudications that have to be made before stalemate test */
8248                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8249                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8250                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8251                      if(canAdjudicate && appData.checkMates) {
8252                          if(engineOpponent)
8253                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8254                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8255                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8256                          return 1;
8257                      }
8258                 }
8259
8260                 /* Bare King in Shatranj (loses) or Losers (wins) */
8261                 if( nrW == 1 || nrB == 1) {
8262                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8263                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8264                      if(canAdjudicate && appData.checkMates) {
8265                          if(engineOpponent)
8266                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8267                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8268                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8269                          return 1;
8270                      }
8271                   } else
8272                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8273                   {    /* bare King */
8274                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8275                         if(canAdjudicate && appData.checkMates) {
8276                             /* but only adjudicate if adjudication enabled */
8277                             if(engineOpponent)
8278                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8279                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8280                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8281                             return 1;
8282                         }
8283                   }
8284                 } else bare = 1;
8285
8286
8287             // don't wait for engine to announce game end if we can judge ourselves
8288             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8289               case MT_CHECK:
8290                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8291                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8292                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8293                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8294                             checkCnt++;
8295                         if(checkCnt >= 2) {
8296                             reason = "Xboard adjudication: 3rd check";
8297                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8298                             break;
8299                         }
8300                     }
8301                 }
8302               case MT_NONE:
8303               default:
8304                 break;
8305               case MT_STEALMATE:
8306               case MT_STALEMATE:
8307               case MT_STAINMATE:
8308                 reason = "Xboard adjudication: Stalemate";
8309                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8310                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8311                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8312                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8313                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8314                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8315                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8316                                                                         EP_CHECKMATE : EP_WINS);
8317                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8318                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8319                 }
8320                 break;
8321               case MT_CHECKMATE:
8322                 reason = "Xboard adjudication: Checkmate";
8323                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8324                 if(gameInfo.variant == VariantShogi) {
8325                     if(forwardMostMove > backwardMostMove
8326                        && moveList[forwardMostMove-1][1] == '@'
8327                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8328                         reason = "XBoard adjudication: pawn-drop mate";
8329                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8330                     }
8331                 }
8332                 break;
8333             }
8334
8335                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8336                     case EP_STALEMATE:
8337                         result = GameIsDrawn; break;
8338                     case EP_CHECKMATE:
8339                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8340                     case EP_WINS:
8341                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8342                     default:
8343                         result = EndOfFile;
8344                 }
8345                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8346                     if(engineOpponent)
8347                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8348                     GameEnds( result, reason, GE_XBOARD );
8349                     return 1;
8350                 }
8351
8352                 /* Next absolutely insufficient mating material. */
8353                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8354                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8355                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8356
8357                      /* always flag draws, for judging claims */
8358                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8359
8360                      if(canAdjudicate && appData.materialDraws) {
8361                          /* but only adjudicate them if adjudication enabled */
8362                          if(engineOpponent) {
8363                            SendToProgram("force\n", engineOpponent); // suppress reply
8364                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8365                          }
8366                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8367                          return 1;
8368                      }
8369                 }
8370
8371                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8372                 if(gameInfo.variant == VariantXiangqi ?
8373                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8374                  : nrW + nrB == 4 &&
8375                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8376                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8377                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8378                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8379                    ) ) {
8380                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8381                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8382                           if(engineOpponent) {
8383                             SendToProgram("force\n", engineOpponent); // suppress reply
8384                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8385                           }
8386                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8387                           return 1;
8388                      }
8389                 } else moveCount = 6;
8390             }
8391
8392         // Repetition draws and 50-move rule can be applied independently of legality testing
8393
8394                 /* Check for rep-draws */
8395                 count = 0;
8396                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8397                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8398                 for(k = forwardMostMove-2;
8399                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8400                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8401                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8402                     k-=2)
8403                 {   int rights=0;
8404                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8405                         /* compare castling rights */
8406                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8407                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8408                                 rights++; /* King lost rights, while rook still had them */
8409                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8410                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8411                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8412                                    rights++; /* but at least one rook lost them */
8413                         }
8414                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8415                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8416                                 rights++;
8417                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8418                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8419                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8420                                    rights++;
8421                         }
8422                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8423                             && appData.drawRepeats > 1) {
8424                              /* adjudicate after user-specified nr of repeats */
8425                              int result = GameIsDrawn;
8426                              char *details = "XBoard adjudication: repetition draw";
8427                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8428                                 // [HGM] xiangqi: check for forbidden perpetuals
8429                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8430                                 for(m=forwardMostMove; m>k; m-=2) {
8431                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8432                                         ourPerpetual = 0; // the current mover did not always check
8433                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8434                                         hisPerpetual = 0; // the opponent did not always check
8435                                 }
8436                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8437                                                                         ourPerpetual, hisPerpetual);
8438                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8439                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8440                                     details = "Xboard adjudication: perpetual checking";
8441                                 } else
8442                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8443                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8444                                 } else
8445                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8446                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8447                                         result = BlackWins;
8448                                         details = "Xboard adjudication: repetition";
8449                                     }
8450                                 } else // it must be XQ
8451                                 // Now check for perpetual chases
8452                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8453                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8454                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8455                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8456                                         static char resdet[MSG_SIZ];
8457                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8458                                         details = resdet;
8459                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8460                                     } else
8461                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8462                                         break; // Abort repetition-checking loop.
8463                                 }
8464                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8465                              }
8466                              if(engineOpponent) {
8467                                SendToProgram("force\n", engineOpponent); // suppress reply
8468                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8469                              }
8470                              GameEnds( result, details, GE_XBOARD );
8471                              return 1;
8472                         }
8473                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8474                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8475                     }
8476                 }
8477
8478                 /* Now we test for 50-move draws. Determine ply count */
8479                 count = forwardMostMove;
8480                 /* look for last irreversble move */
8481                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8482                     count--;
8483                 /* if we hit starting position, add initial plies */
8484                 if( count == backwardMostMove )
8485                     count -= initialRulePlies;
8486                 count = forwardMostMove - count;
8487                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8488                         // adjust reversible move counter for checks in Xiangqi
8489                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8490                         if(i < backwardMostMove) i = backwardMostMove;
8491                         while(i <= forwardMostMove) {
8492                                 lastCheck = inCheck; // check evasion does not count
8493                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8494                                 if(inCheck || lastCheck) count--; // check does not count
8495                                 i++;
8496                         }
8497                 }
8498                 if( count >= 100)
8499                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8500                          /* this is used to judge if draw claims are legal */
8501                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8502                          if(engineOpponent) {
8503                            SendToProgram("force\n", engineOpponent); // suppress reply
8504                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8505                          }
8506                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8507                          return 1;
8508                 }
8509
8510                 /* if draw offer is pending, treat it as a draw claim
8511                  * when draw condition present, to allow engines a way to
8512                  * claim draws before making their move to avoid a race
8513                  * condition occurring after their move
8514                  */
8515                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8516                          char *p = NULL;
8517                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8518                              p = "Draw claim: 50-move rule";
8519                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8520                              p = "Draw claim: 3-fold repetition";
8521                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8522                              p = "Draw claim: insufficient mating material";
8523                          if( p != NULL && canAdjudicate) {
8524                              if(engineOpponent) {
8525                                SendToProgram("force\n", engineOpponent); // suppress reply
8526                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8527                              }
8528                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8529                              return 1;
8530                          }
8531                 }
8532
8533                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8534                     if(engineOpponent) {
8535                       SendToProgram("force\n", engineOpponent); // suppress reply
8536                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8537                     }
8538                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8539                     return 1;
8540                 }
8541         return 0;
8542 }
8543
8544 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8545 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8546 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8547
8548 static int
8549 BitbaseProbe ()
8550 {
8551     int pieces[10], squares[10], cnt=0, r, f, res;
8552     static int loaded;
8553     static PPROBE_EGBB probeBB;
8554     if(!appData.testLegality) return 10;
8555     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8556     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8557     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8558     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8559         ChessSquare piece = boards[forwardMostMove][r][f];
8560         int black = (piece >= BlackPawn);
8561         int type = piece - black*BlackPawn;
8562         if(piece == EmptySquare) continue;
8563         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8564         if(type == WhiteKing) type = WhiteQueen + 1;
8565         type = egbbCode[type];
8566         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8567         pieces[cnt] = type + black*6;
8568         if(++cnt > 5) return 11;
8569     }
8570     pieces[cnt] = squares[cnt] = 0;
8571     // probe EGBB
8572     if(loaded == 2) return 13; // loading failed before
8573     if(loaded == 0) {
8574         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8575         HMODULE lib;
8576         PLOAD_EGBB loadBB;
8577         loaded = 2; // prepare for failure
8578         if(!path) return 13; // no egbb installed
8579         strncpy(buf, path + 8, MSG_SIZ);
8580         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8581         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8582         lib = LoadLibrary(buf);
8583         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8584         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8585         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8586         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8587         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8588         loaded = 1; // success!
8589     }
8590     res = probeBB(forwardMostMove & 1, pieces, squares);
8591     return res > 0 ? 1 : res < 0 ? -1 : 0;
8592 }
8593
8594 char *
8595 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8596 {   // [HGM] book: this routine intercepts moves to simulate book replies
8597     char *bookHit = NULL;
8598
8599     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8600         char buf[MSG_SIZ];
8601         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8602         SendToProgram(buf, cps);
8603     }
8604     //first determine if the incoming move brings opponent into his book
8605     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8606         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8607     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8608     if(bookHit != NULL && !cps->bookSuspend) {
8609         // make sure opponent is not going to reply after receiving move to book position
8610         SendToProgram("force\n", cps);
8611         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8612     }
8613     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8614     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8615     // now arrange restart after book miss
8616     if(bookHit) {
8617         // after a book hit we never send 'go', and the code after the call to this routine
8618         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8619         char buf[MSG_SIZ], *move = bookHit;
8620         if(cps->useSAN) {
8621             int fromX, fromY, toX, toY;
8622             char promoChar;
8623             ChessMove moveType;
8624             move = buf + 30;
8625             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8626                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8627                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8628                                     PosFlags(forwardMostMove),
8629                                     fromY, fromX, toY, toX, promoChar, move);
8630             } else {
8631                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8632                 bookHit = NULL;
8633             }
8634         }
8635         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8636         SendToProgram(buf, cps);
8637         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8638     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8639         SendToProgram("go\n", cps);
8640         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8641     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8642         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8643             SendToProgram("go\n", cps);
8644         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8645     }
8646     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8647 }
8648
8649 int
8650 LoadError (char *errmess, ChessProgramState *cps)
8651 {   // unloads engine and switches back to -ncp mode if it was first
8652     if(cps->initDone) return FALSE;
8653     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8654     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8655     cps->pr = NoProc;
8656     if(cps == &first) {
8657         appData.noChessProgram = TRUE;
8658         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8659         gameMode = BeginningOfGame; ModeHighlight();
8660         SetNCPMode();
8661     }
8662     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8663     DisplayMessage("", ""); // erase waiting message
8664     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8665     return TRUE;
8666 }
8667
8668 char *savedMessage;
8669 ChessProgramState *savedState;
8670 void
8671 DeferredBookMove (void)
8672 {
8673         if(savedState->lastPing != savedState->lastPong)
8674                     ScheduleDelayedEvent(DeferredBookMove, 10);
8675         else
8676         HandleMachineMove(savedMessage, savedState);
8677 }
8678
8679 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8680 static ChessProgramState *stalledEngine;
8681 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8682
8683 void
8684 HandleMachineMove (char *message, ChessProgramState *cps)
8685 {
8686     static char firstLeg[20], legs;
8687     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8688     char realname[MSG_SIZ];
8689     int fromX, fromY, toX, toY;
8690     ChessMove moveType;
8691     char promoChar, roar;
8692     char *p, *pv=buf1;
8693     int oldError;
8694     char *bookHit;
8695
8696     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8697         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8698         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8699             DisplayError(_("Invalid pairing from pairing engine"), 0);
8700             return;
8701         }
8702         pairingReceived = 1;
8703         NextMatchGame();
8704         return; // Skim the pairing messages here.
8705     }
8706
8707     oldError = cps->userError; cps->userError = 0;
8708
8709 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8710     /*
8711      * Kludge to ignore BEL characters
8712      */
8713     while (*message == '\007') message++;
8714
8715     /*
8716      * [HGM] engine debug message: ignore lines starting with '#' character
8717      */
8718     if(cps->debug && *message == '#') return;
8719
8720     /*
8721      * Look for book output
8722      */
8723     if (cps == &first && bookRequested) {
8724         if (message[0] == '\t' || message[0] == ' ') {
8725             /* Part of the book output is here; append it */
8726             strcat(bookOutput, message);
8727             strcat(bookOutput, "  \n");
8728             return;
8729         } else if (bookOutput[0] != NULLCHAR) {
8730             /* All of book output has arrived; display it */
8731             char *p = bookOutput;
8732             while (*p != NULLCHAR) {
8733                 if (*p == '\t') *p = ' ';
8734                 p++;
8735             }
8736             DisplayInformation(bookOutput);
8737             bookRequested = FALSE;
8738             /* Fall through to parse the current output */
8739         }
8740     }
8741
8742     /*
8743      * Look for machine move.
8744      */
8745     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8746         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8747     {
8748         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8749             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8750             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8751             stalledEngine = cps;
8752             if(appData.ponderNextMove) { // bring opponent out of ponder
8753                 if(gameMode == TwoMachinesPlay) {
8754                     if(cps->other->pause)
8755                         PauseEngine(cps->other);
8756                     else
8757                         SendToProgram("easy\n", cps->other);
8758                 }
8759             }
8760             StopClocks();
8761             return;
8762         }
8763
8764       if(cps->usePing) {
8765
8766         /* This method is only useful on engines that support ping */
8767         if(abortEngineThink) {
8768             if (appData.debugMode) {
8769                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8770             }
8771             SendToProgram("undo\n", cps);
8772             return;
8773         }
8774
8775         if (cps->lastPing != cps->lastPong) {
8776             /* Extra move from before last new; ignore */
8777             if (appData.debugMode) {
8778                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8779             }
8780           return;
8781         }
8782
8783       } else {
8784
8785         int machineWhite = FALSE;
8786
8787         switch (gameMode) {
8788           case BeginningOfGame:
8789             /* Extra move from before last reset; ignore */
8790             if (appData.debugMode) {
8791                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8792             }
8793             return;
8794
8795           case EndOfGame:
8796           case IcsIdle:
8797           default:
8798             /* Extra move after we tried to stop.  The mode test is
8799                not a reliable way of detecting this problem, but it's
8800                the best we can do on engines that don't support ping.
8801             */
8802             if (appData.debugMode) {
8803                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8804                         cps->which, gameMode);
8805             }
8806             SendToProgram("undo\n", cps);
8807             return;
8808
8809           case MachinePlaysWhite:
8810           case IcsPlayingWhite:
8811             machineWhite = TRUE;
8812             break;
8813
8814           case MachinePlaysBlack:
8815           case IcsPlayingBlack:
8816             machineWhite = FALSE;
8817             break;
8818
8819           case TwoMachinesPlay:
8820             machineWhite = (cps->twoMachinesColor[0] == 'w');
8821             break;
8822         }
8823         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8824             if (appData.debugMode) {
8825                 fprintf(debugFP,
8826                         "Ignoring move out of turn by %s, gameMode %d"
8827                         ", forwardMost %d\n",
8828                         cps->which, gameMode, forwardMostMove);
8829             }
8830             return;
8831         }
8832       }
8833
8834         if(cps->alphaRank) AlphaRank(machineMove, 4);
8835
8836         // [HGM] lion: (some very limited) support for Alien protocol
8837         killX = killY = kill2X = kill2Y = -1;
8838         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8839             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8840             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8841             return;
8842         }
8843         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8844             char *q = strchr(p+1, ',');            // second comma?
8845             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8846             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8847             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8848         }
8849         if(firstLeg[0]) { // there was a previous leg;
8850             // only support case where same piece makes two step
8851             char buf[20], *p = machineMove+1, *q = buf+1, f;
8852             safeStrCpy(buf, machineMove, 20);
8853             while(isdigit(*q)) q++; // find start of to-square
8854             safeStrCpy(machineMove, firstLeg, 20);
8855             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8856             if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
8857             else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
8858             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8859             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8860             firstLeg[0] = NULLCHAR; legs = 0;
8861         }
8862
8863         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8864                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8865             /* Machine move could not be parsed; ignore it. */
8866           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8867                     machineMove, _(cps->which));
8868             DisplayMoveError(buf1);
8869             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8870                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8871             if (gameMode == TwoMachinesPlay) {
8872               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8873                        buf1, GE_XBOARD);
8874             }
8875             return;
8876         }
8877
8878         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8879         /* So we have to redo legality test with true e.p. status here,  */
8880         /* to make sure an illegal e.p. capture does not slip through,   */
8881         /* to cause a forfeit on a justified illegal-move complaint      */
8882         /* of the opponent.                                              */
8883         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8884            ChessMove moveType;
8885            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8886                              fromY, fromX, toY, toX, promoChar);
8887             if(moveType == IllegalMove) {
8888               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8889                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8890                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8891                            buf1, GE_XBOARD);
8892                 return;
8893            } else if(!appData.fischerCastling)
8894            /* [HGM] Kludge to handle engines that send FRC-style castling
8895               when they shouldn't (like TSCP-Gothic) */
8896            switch(moveType) {
8897              case WhiteASideCastleFR:
8898              case BlackASideCastleFR:
8899                toX+=2;
8900                currentMoveString[2]++;
8901                break;
8902              case WhiteHSideCastleFR:
8903              case BlackHSideCastleFR:
8904                toX--;
8905                currentMoveString[2]--;
8906                break;
8907              default: ; // nothing to do, but suppresses warning of pedantic compilers
8908            }
8909         }
8910         hintRequested = FALSE;
8911         lastHint[0] = NULLCHAR;
8912         bookRequested = FALSE;
8913         /* Program may be pondering now */
8914         cps->maybeThinking = TRUE;
8915         if (cps->sendTime == 2) cps->sendTime = 1;
8916         if (cps->offeredDraw) cps->offeredDraw--;
8917
8918         /* [AS] Save move info*/
8919         pvInfoList[ forwardMostMove ].score = programStats.score;
8920         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8921         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8922
8923         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8924
8925         /* Test suites abort the 'game' after one move */
8926         if(*appData.finger) {
8927            static FILE *f;
8928            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8929            if(!f) f = fopen(appData.finger, "w");
8930            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8931            else { DisplayFatalError("Bad output file", errno, 0); return; }
8932            free(fen);
8933            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8934         }
8935         if(appData.epd) {
8936            if(solvingTime >= 0) {
8937               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8938               totalTime += solvingTime; first.matchWins++;
8939            } else {
8940               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8941               second.matchWins++;
8942            }
8943            OutputKibitz(2, buf1);
8944            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8945         }
8946
8947         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8948         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8949             int count = 0;
8950
8951             while( count < adjudicateLossPlies ) {
8952                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8953
8954                 if( count & 1 ) {
8955                     score = -score; /* Flip score for winning side */
8956                 }
8957
8958                 if( score > appData.adjudicateLossThreshold ) {
8959                     break;
8960                 }
8961
8962                 count++;
8963             }
8964
8965             if( count >= adjudicateLossPlies ) {
8966                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8967
8968                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8969                     "Xboard adjudication",
8970                     GE_XBOARD );
8971
8972                 return;
8973             }
8974         }
8975
8976         if(Adjudicate(cps)) {
8977             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8978             return; // [HGM] adjudicate: for all automatic game ends
8979         }
8980
8981 #if ZIPPY
8982         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8983             first.initDone) {
8984           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8985                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8986                 SendToICS("draw ");
8987                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8988           }
8989           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8990           ics_user_moved = 1;
8991           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8992                 char buf[3*MSG_SIZ];
8993
8994                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8995                         programStats.score / 100.,
8996                         programStats.depth,
8997                         programStats.time / 100.,
8998                         (unsigned int)programStats.nodes,
8999                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9000                         programStats.movelist);
9001                 SendToICS(buf);
9002           }
9003         }
9004 #endif
9005
9006         /* [AS] Clear stats for next move */
9007         ClearProgramStats();
9008         thinkOutput[0] = NULLCHAR;
9009         hiddenThinkOutputState = 0;
9010
9011         bookHit = NULL;
9012         if (gameMode == TwoMachinesPlay) {
9013             /* [HGM] relaying draw offers moved to after reception of move */
9014             /* and interpreting offer as claim if it brings draw condition */
9015             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9016                 SendToProgram("draw\n", cps->other);
9017             }
9018             if (cps->other->sendTime) {
9019                 SendTimeRemaining(cps->other,
9020                                   cps->other->twoMachinesColor[0] == 'w');
9021             }
9022             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9023             if (firstMove && !bookHit) {
9024                 firstMove = FALSE;
9025                 if (cps->other->useColors) {
9026                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9027                 }
9028                 SendToProgram("go\n", cps->other);
9029             }
9030             cps->other->maybeThinking = TRUE;
9031         }
9032
9033         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9034
9035         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9036
9037         if (!pausing && appData.ringBellAfterMoves) {
9038             if(!roar) RingBell();
9039         }
9040
9041         /*
9042          * Reenable menu items that were disabled while
9043          * machine was thinking
9044          */
9045         if (gameMode != TwoMachinesPlay)
9046             SetUserThinkingEnables();
9047
9048         // [HGM] book: after book hit opponent has received move and is now in force mode
9049         // force the book reply into it, and then fake that it outputted this move by jumping
9050         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9051         if(bookHit) {
9052                 static char bookMove[MSG_SIZ]; // a bit generous?
9053
9054                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9055                 strcat(bookMove, bookHit);
9056                 message = bookMove;
9057                 cps = cps->other;
9058                 programStats.nodes = programStats.depth = programStats.time =
9059                 programStats.score = programStats.got_only_move = 0;
9060                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9061
9062                 if(cps->lastPing != cps->lastPong) {
9063                     savedMessage = message; // args for deferred call
9064                     savedState = cps;
9065                     ScheduleDelayedEvent(DeferredBookMove, 10);
9066                     return;
9067                 }
9068                 goto FakeBookMove;
9069         }
9070
9071         return;
9072     }
9073
9074     /* Set special modes for chess engines.  Later something general
9075      *  could be added here; for now there is just one kludge feature,
9076      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9077      *  when "xboard" is given as an interactive command.
9078      */
9079     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9080         cps->useSigint = FALSE;
9081         cps->useSigterm = FALSE;
9082     }
9083     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9084       ParseFeatures(message+8, cps);
9085       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9086     }
9087
9088     if (!strncmp(message, "setup ", 6) && 
9089         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9090           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9091                                         ) { // [HGM] allow first engine to define opening position
9092       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9093       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9094       *buf = NULLCHAR;
9095       if(sscanf(message, "setup (%s", buf) == 1) {
9096         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9097         ASSIGN(appData.pieceToCharTable, buf);
9098       }
9099       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9100       if(dummy >= 3) {
9101         while(message[s] && message[s++] != ' ');
9102         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9103            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9104             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9105             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9106           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9107           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9108           startedFromSetupPosition = FALSE;
9109         }
9110       }
9111       if(startedFromSetupPosition) return;
9112       ParseFEN(boards[0], &dummy, message+s, FALSE);
9113       DrawPosition(TRUE, boards[0]);
9114       CopyBoard(initialPosition, boards[0]);
9115       startedFromSetupPosition = TRUE;
9116       return;
9117     }
9118     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9119       ChessSquare piece = WhitePawn;
9120       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9121       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9122       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9123       piece += CharToPiece(ID & 255) - WhitePawn;
9124       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9125       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9126       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9127       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9128       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9129       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9130                                                && gameInfo.variant != VariantGreat
9131                                                && gameInfo.variant != VariantFairy    ) return;
9132       if(piece < EmptySquare) {
9133         pieceDefs = TRUE;
9134         ASSIGN(pieceDesc[piece], buf1);
9135         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9136       }
9137       return;
9138     }
9139     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9140       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9141       Sweep(0);
9142       return;
9143     }
9144     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9145      * want this, I was asked to put it in, and obliged.
9146      */
9147     if (!strncmp(message, "setboard ", 9)) {
9148         Board initial_position;
9149
9150         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9151
9152         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9153             DisplayError(_("Bad FEN received from engine"), 0);
9154             return ;
9155         } else {
9156            Reset(TRUE, FALSE);
9157            CopyBoard(boards[0], initial_position);
9158            initialRulePlies = FENrulePlies;
9159            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9160            else gameMode = MachinePlaysBlack;
9161            DrawPosition(FALSE, boards[currentMove]);
9162         }
9163         return;
9164     }
9165
9166     /*
9167      * Look for communication commands
9168      */
9169     if (!strncmp(message, "telluser ", 9)) {
9170         if(message[9] == '\\' && message[10] == '\\')
9171             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9172         PlayTellSound();
9173         DisplayNote(message + 9);
9174         return;
9175     }
9176     if (!strncmp(message, "tellusererror ", 14)) {
9177         cps->userError = 1;
9178         if(message[14] == '\\' && message[15] == '\\')
9179             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9180         PlayTellSound();
9181         DisplayError(message + 14, 0);
9182         return;
9183     }
9184     if (!strncmp(message, "tellopponent ", 13)) {
9185       if (appData.icsActive) {
9186         if (loggedOn) {
9187           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9188           SendToICS(buf1);
9189         }
9190       } else {
9191         DisplayNote(message + 13);
9192       }
9193       return;
9194     }
9195     if (!strncmp(message, "tellothers ", 11)) {
9196       if (appData.icsActive) {
9197         if (loggedOn) {
9198           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9199           SendToICS(buf1);
9200         }
9201       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9202       return;
9203     }
9204     if (!strncmp(message, "tellall ", 8)) {
9205       if (appData.icsActive) {
9206         if (loggedOn) {
9207           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9208           SendToICS(buf1);
9209         }
9210       } else {
9211         DisplayNote(message + 8);
9212       }
9213       return;
9214     }
9215     if (strncmp(message, "warning", 7) == 0) {
9216         /* Undocumented feature, use tellusererror in new code */
9217         DisplayError(message, 0);
9218         return;
9219     }
9220     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9221         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9222         strcat(realname, " query");
9223         AskQuestion(realname, buf2, buf1, cps->pr);
9224         return;
9225     }
9226     /* Commands from the engine directly to ICS.  We don't allow these to be
9227      *  sent until we are logged on. Crafty kibitzes have been known to
9228      *  interfere with the login process.
9229      */
9230     if (loggedOn) {
9231         if (!strncmp(message, "tellics ", 8)) {
9232             SendToICS(message + 8);
9233             SendToICS("\n");
9234             return;
9235         }
9236         if (!strncmp(message, "tellicsnoalias ", 15)) {
9237             SendToICS(ics_prefix);
9238             SendToICS(message + 15);
9239             SendToICS("\n");
9240             return;
9241         }
9242         /* The following are for backward compatibility only */
9243         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9244             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9245             SendToICS(ics_prefix);
9246             SendToICS(message);
9247             SendToICS("\n");
9248             return;
9249         }
9250     }
9251     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9252         if(initPing == cps->lastPong) {
9253             if(gameInfo.variant == VariantUnknown) {
9254                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9255                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9256                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9257             }
9258             initPing = -1;
9259         }
9260         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9261             abortEngineThink = FALSE;
9262             DisplayMessage("", "");
9263             ThawUI();
9264         }
9265         return;
9266     }
9267     if(!strncmp(message, "highlight ", 10)) {
9268         if(appData.testLegality && !*engineVariant && appData.markers) return;
9269         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9270         return;
9271     }
9272     if(!strncmp(message, "click ", 6)) {
9273         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9274         if(appData.testLegality || !appData.oneClick) return;
9275         sscanf(message+6, "%c%d%c", &f, &y, &c);
9276         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9277         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9278         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9279         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9280         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9281         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9282             LeftClick(Release, lastLeftX, lastLeftY);
9283         controlKey  = (c == ',');
9284         LeftClick(Press, x, y);
9285         LeftClick(Release, x, y);
9286         first.highlight = f;
9287         return;
9288     }
9289     /*
9290      * If the move is illegal, cancel it and redraw the board.
9291      * Also deal with other error cases.  Matching is rather loose
9292      * here to accommodate engines written before the spec.
9293      */
9294     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9295         strncmp(message, "Error", 5) == 0) {
9296         if (StrStr(message, "name") ||
9297             StrStr(message, "rating") || StrStr(message, "?") ||
9298             StrStr(message, "result") || StrStr(message, "board") ||
9299             StrStr(message, "bk") || StrStr(message, "computer") ||
9300             StrStr(message, "variant") || StrStr(message, "hint") ||
9301             StrStr(message, "random") || StrStr(message, "depth") ||
9302             StrStr(message, "accepted")) {
9303             return;
9304         }
9305         if (StrStr(message, "protover")) {
9306           /* Program is responding to input, so it's apparently done
9307              initializing, and this error message indicates it is
9308              protocol version 1.  So we don't need to wait any longer
9309              for it to initialize and send feature commands. */
9310           FeatureDone(cps, 1);
9311           cps->protocolVersion = 1;
9312           return;
9313         }
9314         cps->maybeThinking = FALSE;
9315
9316         if (StrStr(message, "draw")) {
9317             /* Program doesn't have "draw" command */
9318             cps->sendDrawOffers = 0;
9319             return;
9320         }
9321         if (cps->sendTime != 1 &&
9322             (StrStr(message, "time") || StrStr(message, "otim"))) {
9323           /* Program apparently doesn't have "time" or "otim" command */
9324           cps->sendTime = 0;
9325           return;
9326         }
9327         if (StrStr(message, "analyze")) {
9328             cps->analysisSupport = FALSE;
9329             cps->analyzing = FALSE;
9330 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9331             EditGameEvent(); // [HGM] try to preserve loaded game
9332             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9333             DisplayError(buf2, 0);
9334             return;
9335         }
9336         if (StrStr(message, "(no matching move)st")) {
9337           /* Special kludge for GNU Chess 4 only */
9338           cps->stKludge = TRUE;
9339           SendTimeControl(cps, movesPerSession, timeControl,
9340                           timeIncrement, appData.searchDepth,
9341                           searchTime);
9342           return;
9343         }
9344         if (StrStr(message, "(no matching move)sd")) {
9345           /* Special kludge for GNU Chess 4 only */
9346           cps->sdKludge = TRUE;
9347           SendTimeControl(cps, movesPerSession, timeControl,
9348                           timeIncrement, appData.searchDepth,
9349                           searchTime);
9350           return;
9351         }
9352         if (!StrStr(message, "llegal")) {
9353             return;
9354         }
9355         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9356             gameMode == IcsIdle) return;
9357         if (forwardMostMove <= backwardMostMove) return;
9358         if (pausing) PauseEvent();
9359       if(appData.forceIllegal) {
9360             // [HGM] illegal: machine refused move; force position after move into it
9361           SendToProgram("force\n", cps);
9362           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9363                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9364                 // when black is to move, while there might be nothing on a2 or black
9365                 // might already have the move. So send the board as if white has the move.
9366                 // But first we must change the stm of the engine, as it refused the last move
9367                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9368                 if(WhiteOnMove(forwardMostMove)) {
9369                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9370                     SendBoard(cps, forwardMostMove); // kludgeless board
9371                 } else {
9372                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9373                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9374                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9375                 }
9376           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9377             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9378                  gameMode == TwoMachinesPlay)
9379               SendToProgram("go\n", cps);
9380             return;
9381       } else
9382         if (gameMode == PlayFromGameFile) {
9383             /* Stop reading this game file */
9384             gameMode = EditGame;
9385             ModeHighlight();
9386         }
9387         /* [HGM] illegal-move claim should forfeit game when Xboard */
9388         /* only passes fully legal moves                            */
9389         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9390             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9391                                 "False illegal-move claim", GE_XBOARD );
9392             return; // do not take back move we tested as valid
9393         }
9394         currentMove = forwardMostMove-1;
9395         DisplayMove(currentMove-1); /* before DisplayMoveError */
9396         SwitchClocks(forwardMostMove-1); // [HGM] race
9397         DisplayBothClocks();
9398         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9399                 parseList[currentMove], _(cps->which));
9400         DisplayMoveError(buf1);
9401         DrawPosition(FALSE, boards[currentMove]);
9402
9403         SetUserThinkingEnables();
9404         return;
9405     }
9406     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9407         /* Program has a broken "time" command that
9408            outputs a string not ending in newline.
9409            Don't use it. */
9410         cps->sendTime = 0;
9411     }
9412     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9413         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9414             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9415     }
9416
9417     /*
9418      * If chess program startup fails, exit with an error message.
9419      * Attempts to recover here are futile. [HGM] Well, we try anyway
9420      */
9421     if ((StrStr(message, "unknown host") != NULL)
9422         || (StrStr(message, "No remote directory") != NULL)
9423         || (StrStr(message, "not found") != NULL)
9424         || (StrStr(message, "No such file") != NULL)
9425         || (StrStr(message, "can't alloc") != NULL)
9426         || (StrStr(message, "Permission denied") != NULL)) {
9427
9428         cps->maybeThinking = FALSE;
9429         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9430                 _(cps->which), cps->program, cps->host, message);
9431         RemoveInputSource(cps->isr);
9432         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9433             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9434             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9435         }
9436         return;
9437     }
9438
9439     /*
9440      * Look for hint output
9441      */
9442     if (sscanf(message, "Hint: %s", buf1) == 1) {
9443         if (cps == &first && hintRequested) {
9444             hintRequested = FALSE;
9445             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9446                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9447                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9448                                     PosFlags(forwardMostMove),
9449                                     fromY, fromX, toY, toX, promoChar, buf1);
9450                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9451                 DisplayInformation(buf2);
9452             } else {
9453                 /* Hint move could not be parsed!? */
9454               snprintf(buf2, sizeof(buf2),
9455                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9456                         buf1, _(cps->which));
9457                 DisplayError(buf2, 0);
9458             }
9459         } else {
9460           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9461         }
9462         return;
9463     }
9464
9465     /*
9466      * Ignore other messages if game is not in progress
9467      */
9468     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9469         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9470
9471     /*
9472      * look for win, lose, draw, or draw offer
9473      */
9474     if (strncmp(message, "1-0", 3) == 0) {
9475         char *p, *q, *r = "";
9476         p = strchr(message, '{');
9477         if (p) {
9478             q = strchr(p, '}');
9479             if (q) {
9480                 *q = NULLCHAR;
9481                 r = p + 1;
9482             }
9483         }
9484         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9485         return;
9486     } else if (strncmp(message, "0-1", 3) == 0) {
9487         char *p, *q, *r = "";
9488         p = strchr(message, '{');
9489         if (p) {
9490             q = strchr(p, '}');
9491             if (q) {
9492                 *q = NULLCHAR;
9493                 r = p + 1;
9494             }
9495         }
9496         /* Kludge for Arasan 4.1 bug */
9497         if (strcmp(r, "Black resigns") == 0) {
9498             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9499             return;
9500         }
9501         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9502         return;
9503     } else if (strncmp(message, "1/2", 3) == 0) {
9504         char *p, *q, *r = "";
9505         p = strchr(message, '{');
9506         if (p) {
9507             q = strchr(p, '}');
9508             if (q) {
9509                 *q = NULLCHAR;
9510                 r = p + 1;
9511             }
9512         }
9513
9514         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9515         return;
9516
9517     } else if (strncmp(message, "White resign", 12) == 0) {
9518         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9519         return;
9520     } else if (strncmp(message, "Black resign", 12) == 0) {
9521         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9522         return;
9523     } else if (strncmp(message, "White matches", 13) == 0 ||
9524                strncmp(message, "Black matches", 13) == 0   ) {
9525         /* [HGM] ignore GNUShogi noises */
9526         return;
9527     } else if (strncmp(message, "White", 5) == 0 &&
9528                message[5] != '(' &&
9529                StrStr(message, "Black") == NULL) {
9530         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9531         return;
9532     } else if (strncmp(message, "Black", 5) == 0 &&
9533                message[5] != '(') {
9534         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9535         return;
9536     } else if (strcmp(message, "resign") == 0 ||
9537                strcmp(message, "computer resigns") == 0) {
9538         switch (gameMode) {
9539           case MachinePlaysBlack:
9540           case IcsPlayingBlack:
9541             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9542             break;
9543           case MachinePlaysWhite:
9544           case IcsPlayingWhite:
9545             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9546             break;
9547           case TwoMachinesPlay:
9548             if (cps->twoMachinesColor[0] == 'w')
9549               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9550             else
9551               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9552             break;
9553           default:
9554             /* can't happen */
9555             break;
9556         }
9557         return;
9558     } else if (strncmp(message, "opponent mates", 14) == 0) {
9559         switch (gameMode) {
9560           case MachinePlaysBlack:
9561           case IcsPlayingBlack:
9562             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9563             break;
9564           case MachinePlaysWhite:
9565           case IcsPlayingWhite:
9566             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9567             break;
9568           case TwoMachinesPlay:
9569             if (cps->twoMachinesColor[0] == 'w')
9570               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9571             else
9572               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9573             break;
9574           default:
9575             /* can't happen */
9576             break;
9577         }
9578         return;
9579     } else if (strncmp(message, "computer mates", 14) == 0) {
9580         switch (gameMode) {
9581           case MachinePlaysBlack:
9582           case IcsPlayingBlack:
9583             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9584             break;
9585           case MachinePlaysWhite:
9586           case IcsPlayingWhite:
9587             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9588             break;
9589           case TwoMachinesPlay:
9590             if (cps->twoMachinesColor[0] == 'w')
9591               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9592             else
9593               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9594             break;
9595           default:
9596             /* can't happen */
9597             break;
9598         }
9599         return;
9600     } else if (strncmp(message, "checkmate", 9) == 0) {
9601         if (WhiteOnMove(forwardMostMove)) {
9602             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9603         } else {
9604             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9605         }
9606         return;
9607     } else if (strstr(message, "Draw") != NULL ||
9608                strstr(message, "game is a draw") != NULL) {
9609         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9610         return;
9611     } else if (strstr(message, "offer") != NULL &&
9612                strstr(message, "draw") != NULL) {
9613 #if ZIPPY
9614         if (appData.zippyPlay && first.initDone) {
9615             /* Relay offer to ICS */
9616             SendToICS(ics_prefix);
9617             SendToICS("draw\n");
9618         }
9619 #endif
9620         cps->offeredDraw = 2; /* valid until this engine moves twice */
9621         if (gameMode == TwoMachinesPlay) {
9622             if (cps->other->offeredDraw) {
9623                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9624             /* [HGM] in two-machine mode we delay relaying draw offer      */
9625             /* until after we also have move, to see if it is really claim */
9626             }
9627         } else if (gameMode == MachinePlaysWhite ||
9628                    gameMode == MachinePlaysBlack) {
9629           if (userOfferedDraw) {
9630             DisplayInformation(_("Machine accepts your draw offer"));
9631             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9632           } else {
9633             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9634           }
9635         }
9636     }
9637
9638
9639     /*
9640      * Look for thinking output
9641      */
9642     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9643           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9644                                 ) {
9645         int plylev, mvleft, mvtot, curscore, time;
9646         char mvname[MOVE_LEN];
9647         u64 nodes; // [DM]
9648         char plyext;
9649         int ignore = FALSE;
9650         int prefixHint = FALSE;
9651         mvname[0] = NULLCHAR;
9652
9653         switch (gameMode) {
9654           case MachinePlaysBlack:
9655           case IcsPlayingBlack:
9656             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9657             break;
9658           case MachinePlaysWhite:
9659           case IcsPlayingWhite:
9660             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9661             break;
9662           case AnalyzeMode:
9663           case AnalyzeFile:
9664             break;
9665           case IcsObserving: /* [DM] icsEngineAnalyze */
9666             if (!appData.icsEngineAnalyze) ignore = TRUE;
9667             break;
9668           case TwoMachinesPlay:
9669             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9670                 ignore = TRUE;
9671             }
9672             break;
9673           default:
9674             ignore = TRUE;
9675             break;
9676         }
9677
9678         if (!ignore) {
9679             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9680             buf1[0] = NULLCHAR;
9681             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9682                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9683                 char score_buf[MSG_SIZ];
9684
9685                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9686                     nodes += u64Const(0x100000000);
9687
9688                 if (plyext != ' ' && plyext != '\t') {
9689                     time *= 100;
9690                 }
9691
9692                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9693                 if( cps->scoreIsAbsolute &&
9694                     ( gameMode == MachinePlaysBlack ||
9695                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9696                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9697                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9698                      !WhiteOnMove(currentMove)
9699                     ) )
9700                 {
9701                     curscore = -curscore;
9702                 }
9703
9704                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9705
9706                 if(*bestMove) { // rememer time best EPD move was first found
9707                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9708                     ChessMove mt;
9709                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9710                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9711                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9712                 }
9713
9714                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9715                         char buf[MSG_SIZ];
9716                         FILE *f;
9717                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9718                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9719                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9720                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9721                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9722                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9723                                 fclose(f);
9724                         }
9725                         else
9726                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9727                           DisplayError(_("failed writing PV"), 0);
9728                 }
9729
9730                 tempStats.depth = plylev;
9731                 tempStats.nodes = nodes;
9732                 tempStats.time = time;
9733                 tempStats.score = curscore;
9734                 tempStats.got_only_move = 0;
9735
9736                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9737                         int ticklen;
9738
9739                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9740                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9741                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9742                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9743                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9744                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9745                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9746                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9747                 }
9748
9749                 /* Buffer overflow protection */
9750                 if (pv[0] != NULLCHAR) {
9751                     if (strlen(pv) >= sizeof(tempStats.movelist)
9752                         && appData.debugMode) {
9753                         fprintf(debugFP,
9754                                 "PV is too long; using the first %u bytes.\n",
9755                                 (unsigned) sizeof(tempStats.movelist) - 1);
9756                     }
9757
9758                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9759                 } else {
9760                     sprintf(tempStats.movelist, " no PV\n");
9761                 }
9762
9763                 if (tempStats.seen_stat) {
9764                     tempStats.ok_to_send = 1;
9765                 }
9766
9767                 if (strchr(tempStats.movelist, '(') != NULL) {
9768                     tempStats.line_is_book = 1;
9769                     tempStats.nr_moves = 0;
9770                     tempStats.moves_left = 0;
9771                 } else {
9772                     tempStats.line_is_book = 0;
9773                 }
9774
9775                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9776                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9777
9778                 SendProgramStatsToFrontend( cps, &tempStats );
9779
9780                 /*
9781                     [AS] Protect the thinkOutput buffer from overflow... this
9782                     is only useful if buf1 hasn't overflowed first!
9783                 */
9784                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9785                 if(curscore >= MATE_SCORE) 
9786                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9787                 else if(curscore <= -MATE_SCORE) 
9788                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9789                 else
9790                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9791                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9792                          plylev,
9793                          (gameMode == TwoMachinesPlay ?
9794                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9795                          score_buf,
9796                          prefixHint ? lastHint : "",
9797                          prefixHint ? " " : "" );
9798
9799                 if( buf1[0] != NULLCHAR ) {
9800                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9801
9802                     if( strlen(pv) > max_len ) {
9803                         if( appData.debugMode) {
9804                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9805                         }
9806                         pv[max_len+1] = '\0';
9807                     }
9808
9809                     strcat( thinkOutput, pv);
9810                 }
9811
9812                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9813                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9814                     DisplayMove(currentMove - 1);
9815                 }
9816                 return;
9817
9818             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9819                 /* crafty (9.25+) says "(only move) <move>"
9820                  * if there is only 1 legal move
9821                  */
9822                 sscanf(p, "(only move) %s", buf1);
9823                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9824                 sprintf(programStats.movelist, "%s (only move)", buf1);
9825                 programStats.depth = 1;
9826                 programStats.nr_moves = 1;
9827                 programStats.moves_left = 1;
9828                 programStats.nodes = 1;
9829                 programStats.time = 1;
9830                 programStats.got_only_move = 1;
9831
9832                 /* Not really, but we also use this member to
9833                    mean "line isn't going to change" (Crafty
9834                    isn't searching, so stats won't change) */
9835                 programStats.line_is_book = 1;
9836
9837                 SendProgramStatsToFrontend( cps, &programStats );
9838
9839                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9840                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9841                     DisplayMove(currentMove - 1);
9842                 }
9843                 return;
9844             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9845                               &time, &nodes, &plylev, &mvleft,
9846                               &mvtot, mvname) >= 5) {
9847                 /* The stat01: line is from Crafty (9.29+) in response
9848                    to the "." command */
9849                 programStats.seen_stat = 1;
9850                 cps->maybeThinking = TRUE;
9851
9852                 if (programStats.got_only_move || !appData.periodicUpdates)
9853                   return;
9854
9855                 programStats.depth = plylev;
9856                 programStats.time = time;
9857                 programStats.nodes = nodes;
9858                 programStats.moves_left = mvleft;
9859                 programStats.nr_moves = mvtot;
9860                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9861                 programStats.ok_to_send = 1;
9862                 programStats.movelist[0] = '\0';
9863
9864                 SendProgramStatsToFrontend( cps, &programStats );
9865
9866                 return;
9867
9868             } else if (strncmp(message,"++",2) == 0) {
9869                 /* Crafty 9.29+ outputs this */
9870                 programStats.got_fail = 2;
9871                 return;
9872
9873             } else if (strncmp(message,"--",2) == 0) {
9874                 /* Crafty 9.29+ outputs this */
9875                 programStats.got_fail = 1;
9876                 return;
9877
9878             } else if (thinkOutput[0] != NULLCHAR &&
9879                        strncmp(message, "    ", 4) == 0) {
9880                 unsigned message_len;
9881
9882                 p = message;
9883                 while (*p && *p == ' ') p++;
9884
9885                 message_len = strlen( p );
9886
9887                 /* [AS] Avoid buffer overflow */
9888                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9889                     strcat(thinkOutput, " ");
9890                     strcat(thinkOutput, p);
9891                 }
9892
9893                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9894                     strcat(programStats.movelist, " ");
9895                     strcat(programStats.movelist, p);
9896                 }
9897
9898                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9899                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9900                     DisplayMove(currentMove - 1);
9901                 }
9902                 return;
9903             }
9904         }
9905         else {
9906             buf1[0] = NULLCHAR;
9907
9908             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9909                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9910             {
9911                 ChessProgramStats cpstats;
9912
9913                 if (plyext != ' ' && plyext != '\t') {
9914                     time *= 100;
9915                 }
9916
9917                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9918                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9919                     curscore = -curscore;
9920                 }
9921
9922                 cpstats.depth = plylev;
9923                 cpstats.nodes = nodes;
9924                 cpstats.time = time;
9925                 cpstats.score = curscore;
9926                 cpstats.got_only_move = 0;
9927                 cpstats.movelist[0] = '\0';
9928
9929                 if (buf1[0] != NULLCHAR) {
9930                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9931                 }
9932
9933                 cpstats.ok_to_send = 0;
9934                 cpstats.line_is_book = 0;
9935                 cpstats.nr_moves = 0;
9936                 cpstats.moves_left = 0;
9937
9938                 SendProgramStatsToFrontend( cps, &cpstats );
9939             }
9940         }
9941     }
9942 }
9943
9944
9945 /* Parse a game score from the character string "game", and
9946    record it as the history of the current game.  The game
9947    score is NOT assumed to start from the standard position.
9948    The display is not updated in any way.
9949    */
9950 void
9951 ParseGameHistory (char *game)
9952 {
9953     ChessMove moveType;
9954     int fromX, fromY, toX, toY, boardIndex;
9955     char promoChar;
9956     char *p, *q;
9957     char buf[MSG_SIZ];
9958
9959     if (appData.debugMode)
9960       fprintf(debugFP, "Parsing game history: %s\n", game);
9961
9962     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9963     gameInfo.site = StrSave(appData.icsHost);
9964     gameInfo.date = PGNDate();
9965     gameInfo.round = StrSave("-");
9966
9967     /* Parse out names of players */
9968     while (*game == ' ') game++;
9969     p = buf;
9970     while (*game != ' ') *p++ = *game++;
9971     *p = NULLCHAR;
9972     gameInfo.white = StrSave(buf);
9973     while (*game == ' ') game++;
9974     p = buf;
9975     while (*game != ' ' && *game != '\n') *p++ = *game++;
9976     *p = NULLCHAR;
9977     gameInfo.black = StrSave(buf);
9978
9979     /* Parse moves */
9980     boardIndex = blackPlaysFirst ? 1 : 0;
9981     yynewstr(game);
9982     for (;;) {
9983         yyboardindex = boardIndex;
9984         moveType = (ChessMove) Myylex();
9985         switch (moveType) {
9986           case IllegalMove:             /* maybe suicide chess, etc. */
9987   if (appData.debugMode) {
9988     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9989     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9990     setbuf(debugFP, NULL);
9991   }
9992           case WhitePromotion:
9993           case BlackPromotion:
9994           case WhiteNonPromotion:
9995           case BlackNonPromotion:
9996           case NormalMove:
9997           case FirstLeg:
9998           case WhiteCapturesEnPassant:
9999           case BlackCapturesEnPassant:
10000           case WhiteKingSideCastle:
10001           case WhiteQueenSideCastle:
10002           case BlackKingSideCastle:
10003           case BlackQueenSideCastle:
10004           case WhiteKingSideCastleWild:
10005           case WhiteQueenSideCastleWild:
10006           case BlackKingSideCastleWild:
10007           case BlackQueenSideCastleWild:
10008           /* PUSH Fabien */
10009           case WhiteHSideCastleFR:
10010           case WhiteASideCastleFR:
10011           case BlackHSideCastleFR:
10012           case BlackASideCastleFR:
10013           /* POP Fabien */
10014             fromX = currentMoveString[0] - AAA;
10015             fromY = currentMoveString[1] - ONE;
10016             toX = currentMoveString[2] - AAA;
10017             toY = currentMoveString[3] - ONE;
10018             promoChar = currentMoveString[4];
10019             break;
10020           case WhiteDrop:
10021           case BlackDrop:
10022             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10023             fromX = moveType == WhiteDrop ?
10024               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10025             (int) CharToPiece(ToLower(currentMoveString[0]));
10026             fromY = DROP_RANK;
10027             toX = currentMoveString[2] - AAA;
10028             toY = currentMoveString[3] - ONE;
10029             promoChar = NULLCHAR;
10030             break;
10031           case AmbiguousMove:
10032             /* bug? */
10033             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10034   if (appData.debugMode) {
10035     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10036     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10037     setbuf(debugFP, NULL);
10038   }
10039             DisplayError(buf, 0);
10040             return;
10041           case ImpossibleMove:
10042             /* bug? */
10043             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10044   if (appData.debugMode) {
10045     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10046     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10047     setbuf(debugFP, NULL);
10048   }
10049             DisplayError(buf, 0);
10050             return;
10051           case EndOfFile:
10052             if (boardIndex < backwardMostMove) {
10053                 /* Oops, gap.  How did that happen? */
10054                 DisplayError(_("Gap in move list"), 0);
10055                 return;
10056             }
10057             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10058             if (boardIndex > forwardMostMove) {
10059                 forwardMostMove = boardIndex;
10060             }
10061             return;
10062           case ElapsedTime:
10063             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10064                 strcat(parseList[boardIndex-1], " ");
10065                 strcat(parseList[boardIndex-1], yy_text);
10066             }
10067             continue;
10068           case Comment:
10069           case PGNTag:
10070           case NAG:
10071           default:
10072             /* ignore */
10073             continue;
10074           case WhiteWins:
10075           case BlackWins:
10076           case GameIsDrawn:
10077           case GameUnfinished:
10078             if (gameMode == IcsExamining) {
10079                 if (boardIndex < backwardMostMove) {
10080                     /* Oops, gap.  How did that happen? */
10081                     return;
10082                 }
10083                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10084                 return;
10085             }
10086             gameInfo.result = moveType;
10087             p = strchr(yy_text, '{');
10088             if (p == NULL) p = strchr(yy_text, '(');
10089             if (p == NULL) {
10090                 p = yy_text;
10091                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10092             } else {
10093                 q = strchr(p, *p == '{' ? '}' : ')');
10094                 if (q != NULL) *q = NULLCHAR;
10095                 p++;
10096             }
10097             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10098             gameInfo.resultDetails = StrSave(p);
10099             continue;
10100         }
10101         if (boardIndex >= forwardMostMove &&
10102             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10103             backwardMostMove = blackPlaysFirst ? 1 : 0;
10104             return;
10105         }
10106         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10107                                  fromY, fromX, toY, toX, promoChar,
10108                                  parseList[boardIndex]);
10109         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10110         /* currentMoveString is set as a side-effect of yylex */
10111         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10112         strcat(moveList[boardIndex], "\n");
10113         boardIndex++;
10114         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10115         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10116           case MT_NONE:
10117           case MT_STALEMATE:
10118           default:
10119             break;
10120           case MT_CHECK:
10121             if(!IS_SHOGI(gameInfo.variant))
10122                 strcat(parseList[boardIndex - 1], "+");
10123             break;
10124           case MT_CHECKMATE:
10125           case MT_STAINMATE:
10126             strcat(parseList[boardIndex - 1], "#");
10127             break;
10128         }
10129     }
10130 }
10131
10132
10133 /* Apply a move to the given board  */
10134 void
10135 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10136 {
10137   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10138   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10139
10140     /* [HGM] compute & store e.p. status and castling rights for new position */
10141     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10142
10143       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10144       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10145       board[EP_STATUS] = EP_NONE;
10146       board[EP_FILE] = board[EP_RANK] = 100;
10147
10148   if (fromY == DROP_RANK) {
10149         /* must be first */
10150         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10151             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10152             return;
10153         }
10154         piece = board[toY][toX] = (ChessSquare) fromX;
10155   } else {
10156 //      ChessSquare victim;
10157       int i;
10158
10159       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10160 //           victim = board[killY][killX],
10161            killed = board[killY][killX],
10162            board[killY][killX] = EmptySquare,
10163            board[EP_STATUS] = EP_CAPTURE;
10164            if( kill2X >= 0 && kill2Y >= 0)
10165              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10166       }
10167
10168       if( board[toY][toX] != EmptySquare ) {
10169            board[EP_STATUS] = EP_CAPTURE;
10170            if( (fromX != toX || fromY != toY) && // not igui!
10171                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10172                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10173                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10174            }
10175       }
10176
10177       pawn = board[fromY][fromX];
10178       if( pawn == WhiteLance || pawn == BlackLance ) {
10179            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10180                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10181                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10182            }
10183       }
10184       if( pawn == WhitePawn ) {
10185            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10186                board[EP_STATUS] = EP_PAWN_MOVE;
10187            if( toY-fromY>=2) {
10188                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10189                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10190                         gameInfo.variant != VariantBerolina || toX < fromX)
10191                       board[EP_STATUS] = toX | berolina;
10192                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10193                         gameInfo.variant != VariantBerolina || toX > fromX)
10194                       board[EP_STATUS] = toX;
10195            }
10196       } else
10197       if( pawn == BlackPawn ) {
10198            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10199                board[EP_STATUS] = EP_PAWN_MOVE;
10200            if( toY-fromY<= -2) {
10201                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10202                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10203                         gameInfo.variant != VariantBerolina || toX < fromX)
10204                       board[EP_STATUS] = toX | berolina;
10205                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10206                         gameInfo.variant != VariantBerolina || toX > fromX)
10207                       board[EP_STATUS] = toX;
10208            }
10209        }
10210
10211        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10212        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10213        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10214        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10215
10216        for(i=0; i<nrCastlingRights; i++) {
10217            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10218               board[CASTLING][i] == toX   && castlingRank[i] == toY
10219              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10220        }
10221
10222        if(gameInfo.variant == VariantSChess) { // update virginity
10223            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10224            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10225            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10226            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10227        }
10228
10229      if (fromX == toX && fromY == toY && killX < 0) return;
10230
10231      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10232      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10233      if(gameInfo.variant == VariantKnightmate)
10234          king += (int) WhiteUnicorn - (int) WhiteKing;
10235
10236     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10237        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10238         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10239         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10240         board[EP_STATUS] = EP_NONE; // capture was fake!
10241     } else
10242     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10243         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10244         board[toY][toX] = piece;
10245         board[EP_STATUS] = EP_NONE; // capture was fake!
10246     } else
10247     /* Code added by Tord: */
10248     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10249     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10250         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10251       board[EP_STATUS] = EP_NONE; // capture was fake!
10252       board[fromY][fromX] = EmptySquare;
10253       board[toY][toX] = EmptySquare;
10254       if((toX > fromX) != (piece == WhiteRook)) {
10255         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10256       } else {
10257         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10258       }
10259     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10260                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10261       board[EP_STATUS] = EP_NONE;
10262       board[fromY][fromX] = EmptySquare;
10263       board[toY][toX] = EmptySquare;
10264       if((toX > fromX) != (piece == BlackRook)) {
10265         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10266       } else {
10267         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10268       }
10269     /* End of code added by Tord */
10270
10271     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10272         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10273         board[toY][toX] = piece;
10274     } else if (board[fromY][fromX] == king
10275         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10276         && toY == fromY && toX > fromX+1) {
10277         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10278         board[fromY][toX-1] = board[fromY][rookX];
10279         board[fromY][rookX] = EmptySquare;
10280         board[fromY][fromX] = EmptySquare;
10281         board[toY][toX] = king;
10282     } else if (board[fromY][fromX] == king
10283         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10284                && toY == fromY && toX < fromX-1) {
10285         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10286         board[fromY][toX+1] = board[fromY][rookX];
10287         board[fromY][rookX] = EmptySquare;
10288         board[fromY][fromX] = EmptySquare;
10289         board[toY][toX] = king;
10290     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10291                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10292                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10293                ) {
10294         /* white pawn promotion */
10295         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10296         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10297             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10298         board[fromY][fromX] = EmptySquare;
10299     } else if ((fromY >= BOARD_HEIGHT>>1)
10300                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10301                && (toX != fromX)
10302                && gameInfo.variant != VariantXiangqi
10303                && gameInfo.variant != VariantBerolina
10304                && (pawn == WhitePawn)
10305                && (board[toY][toX] == EmptySquare)) {
10306         board[fromY][fromX] = EmptySquare;
10307         board[toY][toX] = piece;
10308         if(toY == epRank - 128 + 1)
10309             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10310         else
10311             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10312     } else if ((fromY == BOARD_HEIGHT-4)
10313                && (toX == fromX)
10314                && gameInfo.variant == VariantBerolina
10315                && (board[fromY][fromX] == WhitePawn)
10316                && (board[toY][toX] == EmptySquare)) {
10317         board[fromY][fromX] = EmptySquare;
10318         board[toY][toX] = WhitePawn;
10319         if(oldEP & EP_BEROLIN_A) {
10320                 captured = board[fromY][fromX-1];
10321                 board[fromY][fromX-1] = EmptySquare;
10322         }else{  captured = board[fromY][fromX+1];
10323                 board[fromY][fromX+1] = EmptySquare;
10324         }
10325     } else if (board[fromY][fromX] == king
10326         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10327                && toY == fromY && toX > fromX+1) {
10328         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10329         board[fromY][toX-1] = board[fromY][rookX];
10330         board[fromY][rookX] = EmptySquare;
10331         board[fromY][fromX] = EmptySquare;
10332         board[toY][toX] = king;
10333     } else if (board[fromY][fromX] == king
10334         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10335                && toY == fromY && toX < fromX-1) {
10336         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10337         board[fromY][toX+1] = board[fromY][rookX];
10338         board[fromY][rookX] = EmptySquare;
10339         board[fromY][fromX] = EmptySquare;
10340         board[toY][toX] = king;
10341     } else if (fromY == 7 && fromX == 3
10342                && board[fromY][fromX] == BlackKing
10343                && toY == 7 && toX == 5) {
10344         board[fromY][fromX] = EmptySquare;
10345         board[toY][toX] = BlackKing;
10346         board[fromY][7] = EmptySquare;
10347         board[toY][4] = BlackRook;
10348     } else if (fromY == 7 && fromX == 3
10349                && board[fromY][fromX] == BlackKing
10350                && toY == 7 && toX == 1) {
10351         board[fromY][fromX] = EmptySquare;
10352         board[toY][toX] = BlackKing;
10353         board[fromY][0] = EmptySquare;
10354         board[toY][2] = BlackRook;
10355     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10356                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10357                && toY < promoRank && promoChar
10358                ) {
10359         /* black pawn promotion */
10360         board[toY][toX] = CharToPiece(ToLower(promoChar));
10361         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10362             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10363         board[fromY][fromX] = EmptySquare;
10364     } else if ((fromY < BOARD_HEIGHT>>1)
10365                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10366                && (toX != fromX)
10367                && gameInfo.variant != VariantXiangqi
10368                && gameInfo.variant != VariantBerolina
10369                && (pawn == BlackPawn)
10370                && (board[toY][toX] == EmptySquare)) {
10371         board[fromY][fromX] = EmptySquare;
10372         board[toY][toX] = piece;
10373         if(toY == epRank - 128 - 1)
10374             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10375         else
10376             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10377     } else if ((fromY == 3)
10378                && (toX == fromX)
10379                && gameInfo.variant == VariantBerolina
10380                && (board[fromY][fromX] == BlackPawn)
10381                && (board[toY][toX] == EmptySquare)) {
10382         board[fromY][fromX] = EmptySquare;
10383         board[toY][toX] = BlackPawn;
10384         if(oldEP & EP_BEROLIN_A) {
10385                 captured = board[fromY][fromX-1];
10386                 board[fromY][fromX-1] = EmptySquare;
10387         }else{  captured = board[fromY][fromX+1];
10388                 board[fromY][fromX+1] = EmptySquare;
10389         }
10390     } else {
10391         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10392         board[fromY][fromX] = EmptySquare;
10393         board[toY][toX] = piece;
10394     }
10395   }
10396
10397     if (gameInfo.holdingsWidth != 0) {
10398
10399       /* !!A lot more code needs to be written to support holdings  */
10400       /* [HGM] OK, so I have written it. Holdings are stored in the */
10401       /* penultimate board files, so they are automaticlly stored   */
10402       /* in the game history.                                       */
10403       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10404                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10405         /* Delete from holdings, by decreasing count */
10406         /* and erasing image if necessary            */
10407         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10408         if(p < (int) BlackPawn) { /* white drop */
10409              p -= (int)WhitePawn;
10410                  p = PieceToNumber((ChessSquare)p);
10411              if(p >= gameInfo.holdingsSize) p = 0;
10412              if(--board[p][BOARD_WIDTH-2] <= 0)
10413                   board[p][BOARD_WIDTH-1] = EmptySquare;
10414              if((int)board[p][BOARD_WIDTH-2] < 0)
10415                         board[p][BOARD_WIDTH-2] = 0;
10416         } else {                  /* black drop */
10417              p -= (int)BlackPawn;
10418                  p = PieceToNumber((ChessSquare)p);
10419              if(p >= gameInfo.holdingsSize) p = 0;
10420              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10421                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10422              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10423                         board[BOARD_HEIGHT-1-p][1] = 0;
10424         }
10425       }
10426       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10427           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10428         /* [HGM] holdings: Add to holdings, if holdings exist */
10429         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10430                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10431                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10432         }
10433         p = (int) captured;
10434         if (p >= (int) BlackPawn) {
10435           p -= (int)BlackPawn;
10436           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10437                   /* Restore shogi-promoted piece to its original  first */
10438                   captured = (ChessSquare) (DEMOTED(captured));
10439                   p = DEMOTED(p);
10440           }
10441           p = PieceToNumber((ChessSquare)p);
10442           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10443           board[p][BOARD_WIDTH-2]++;
10444           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10445         } else {
10446           p -= (int)WhitePawn;
10447           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10448                   captured = (ChessSquare) (DEMOTED(captured));
10449                   p = DEMOTED(p);
10450           }
10451           p = PieceToNumber((ChessSquare)p);
10452           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10453           board[BOARD_HEIGHT-1-p][1]++;
10454           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10455         }
10456       }
10457     } else if (gameInfo.variant == VariantAtomic) {
10458       if (captured != EmptySquare) {
10459         int y, x;
10460         for (y = toY-1; y <= toY+1; y++) {
10461           for (x = toX-1; x <= toX+1; x++) {
10462             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10463                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10464               board[y][x] = EmptySquare;
10465             }
10466           }
10467         }
10468         board[toY][toX] = EmptySquare;
10469       }
10470     }
10471
10472     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10473         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10474     } else
10475     if(promoChar == '+') {
10476         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10477         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10478         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10479           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10480     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10481         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10482         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10483            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10484         board[toY][toX] = newPiece;
10485     }
10486     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10487                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10488         // [HGM] superchess: take promotion piece out of holdings
10489         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10490         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10491             if(!--board[k][BOARD_WIDTH-2])
10492                 board[k][BOARD_WIDTH-1] = EmptySquare;
10493         } else {
10494             if(!--board[BOARD_HEIGHT-1-k][1])
10495                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10496         }
10497     }
10498 }
10499
10500 /* Updates forwardMostMove */
10501 void
10502 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10503 {
10504     int x = toX, y = toY;
10505     char *s = parseList[forwardMostMove];
10506     ChessSquare p = boards[forwardMostMove][toY][toX];
10507 //    forwardMostMove++; // [HGM] bare: moved downstream
10508
10509     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10510     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10511     (void) CoordsToAlgebraic(boards[forwardMostMove],
10512                              PosFlags(forwardMostMove),
10513                              fromY, fromX, y, x, (killX < 0)*promoChar,
10514                              s);
10515     if(kill2X >= 0 && kill2Y >= 0)
10516         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10517     if(killX >= 0 && killY >= 0)
10518         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10519                                            toX + AAA, toY + ONE - '0', promoChar);
10520
10521     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10522         int timeLeft; static int lastLoadFlag=0; int king, piece;
10523         piece = boards[forwardMostMove][fromY][fromX];
10524         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10525         if(gameInfo.variant == VariantKnightmate)
10526             king += (int) WhiteUnicorn - (int) WhiteKing;
10527         if(forwardMostMove == 0) {
10528             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10529                 fprintf(serverMoves, "%s;", UserName());
10530             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10531                 fprintf(serverMoves, "%s;", second.tidy);
10532             fprintf(serverMoves, "%s;", first.tidy);
10533             if(gameMode == MachinePlaysWhite)
10534                 fprintf(serverMoves, "%s;", UserName());
10535             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10536                 fprintf(serverMoves, "%s;", second.tidy);
10537         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10538         lastLoadFlag = loadFlag;
10539         // print base move
10540         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10541         // print castling suffix
10542         if( toY == fromY && piece == king ) {
10543             if(toX-fromX > 1)
10544                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10545             if(fromX-toX >1)
10546                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10547         }
10548         // e.p. suffix
10549         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10550              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10551              boards[forwardMostMove][toY][toX] == EmptySquare
10552              && fromX != toX && fromY != toY)
10553                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10554         // promotion suffix
10555         if(promoChar != NULLCHAR) {
10556             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10557                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10558                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10559             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10560         }
10561         if(!loadFlag) {
10562                 char buf[MOVE_LEN*2], *p; int len;
10563             fprintf(serverMoves, "/%d/%d",
10564                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10565             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10566             else                      timeLeft = blackTimeRemaining/1000;
10567             fprintf(serverMoves, "/%d", timeLeft);
10568                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10569                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10570                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10571                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10572             fprintf(serverMoves, "/%s", buf);
10573         }
10574         fflush(serverMoves);
10575     }
10576
10577     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10578         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10579       return;
10580     }
10581     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10582     if (commentList[forwardMostMove+1] != NULL) {
10583         free(commentList[forwardMostMove+1]);
10584         commentList[forwardMostMove+1] = NULL;
10585     }
10586     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10587     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10588     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10589     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10590     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10591     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10592     adjustedClock = FALSE;
10593     gameInfo.result = GameUnfinished;
10594     if (gameInfo.resultDetails != NULL) {
10595         free(gameInfo.resultDetails);
10596         gameInfo.resultDetails = NULL;
10597     }
10598     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10599                               moveList[forwardMostMove - 1]);
10600     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10601       case MT_NONE:
10602       case MT_STALEMATE:
10603       default:
10604         break;
10605       case MT_CHECK:
10606         if(!IS_SHOGI(gameInfo.variant))
10607             strcat(parseList[forwardMostMove - 1], "+");
10608         break;
10609       case MT_CHECKMATE:
10610       case MT_STAINMATE:
10611         strcat(parseList[forwardMostMove - 1], "#");
10612         break;
10613     }
10614 }
10615
10616 /* Updates currentMove if not pausing */
10617 void
10618 ShowMove (int fromX, int fromY, int toX, int toY)
10619 {
10620     int instant = (gameMode == PlayFromGameFile) ?
10621         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10622     if(appData.noGUI) return;
10623     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10624         if (!instant) {
10625             if (forwardMostMove == currentMove + 1) {
10626                 AnimateMove(boards[forwardMostMove - 1],
10627                             fromX, fromY, toX, toY);
10628             }
10629         }
10630         currentMove = forwardMostMove;
10631     }
10632
10633     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10634
10635     if (instant) return;
10636
10637     DisplayMove(currentMove - 1);
10638     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10639             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10640                 SetHighlights(fromX, fromY, toX, toY);
10641             }
10642     }
10643     DrawPosition(FALSE, boards[currentMove]);
10644     DisplayBothClocks();
10645     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10646 }
10647
10648 void
10649 SendEgtPath (ChessProgramState *cps)
10650 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10651         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10652
10653         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10654
10655         while(*p) {
10656             char c, *q = name+1, *r, *s;
10657
10658             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10659             while(*p && *p != ',') *q++ = *p++;
10660             *q++ = ':'; *q = 0;
10661             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10662                 strcmp(name, ",nalimov:") == 0 ) {
10663                 // take nalimov path from the menu-changeable option first, if it is defined
10664               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10665                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10666             } else
10667             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10668                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10669                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10670                 s = r = StrStr(s, ":") + 1; // beginning of path info
10671                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10672                 c = *r; *r = 0;             // temporarily null-terminate path info
10673                     *--q = 0;               // strip of trailig ':' from name
10674                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10675                 *r = c;
10676                 SendToProgram(buf,cps);     // send egtbpath command for this format
10677             }
10678             if(*p == ',') p++; // read away comma to position for next format name
10679         }
10680 }
10681
10682 static int
10683 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10684 {
10685       int width = 8, height = 8, holdings = 0;             // most common sizes
10686       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10687       // correct the deviations default for each variant
10688       if( v == VariantXiangqi ) width = 9,  height = 10;
10689       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10690       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10691       if( v == VariantCapablanca || v == VariantCapaRandom ||
10692           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10693                                 width = 10;
10694       if( v == VariantCourier ) width = 12;
10695       if( v == VariantSuper )                            holdings = 8;
10696       if( v == VariantGreat )   width = 10,              holdings = 8;
10697       if( v == VariantSChess )                           holdings = 7;
10698       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10699       if( v == VariantChuChess) width = 10, height = 10;
10700       if( v == VariantChu )     width = 12, height = 12;
10701       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10702              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10703              holdingsSize >= 0 && holdingsSize != holdings;
10704 }
10705
10706 char variantError[MSG_SIZ];
10707
10708 char *
10709 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10710 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10711       char *p, *variant = VariantName(v);
10712       static char b[MSG_SIZ];
10713       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10714            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10715                                                holdingsSize, variant); // cook up sized variant name
10716            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10717            if(StrStr(list, b) == NULL) {
10718                // specific sized variant not known, check if general sizing allowed
10719                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10720                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10721                             boardWidth, boardHeight, holdingsSize, engine);
10722                    return NULL;
10723                }
10724                /* [HGM] here we really should compare with the maximum supported board size */
10725            }
10726       } else snprintf(b, MSG_SIZ,"%s", variant);
10727       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10728       p = StrStr(list, b);
10729       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10730       if(p == NULL) {
10731           // occurs not at all in list, or only as sub-string
10732           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10733           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10734               int l = strlen(variantError);
10735               char *q;
10736               while(p != list && p[-1] != ',') p--;
10737               q = strchr(p, ',');
10738               if(q) *q = NULLCHAR;
10739               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10740               if(q) *q= ',';
10741           }
10742           return NULL;
10743       }
10744       return b;
10745 }
10746
10747 void
10748 InitChessProgram (ChessProgramState *cps, int setup)
10749 /* setup needed to setup FRC opening position */
10750 {
10751     char buf[MSG_SIZ], *b;
10752     if (appData.noChessProgram) return;
10753     hintRequested = FALSE;
10754     bookRequested = FALSE;
10755
10756     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10757     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10758     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10759     if(cps->memSize) { /* [HGM] memory */
10760       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10761         SendToProgram(buf, cps);
10762     }
10763     SendEgtPath(cps); /* [HGM] EGT */
10764     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10765       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10766         SendToProgram(buf, cps);
10767     }
10768
10769     setboardSpoiledMachineBlack = FALSE;
10770     SendToProgram(cps->initString, cps);
10771     if (gameInfo.variant != VariantNormal &&
10772         gameInfo.variant != VariantLoadable
10773         /* [HGM] also send variant if board size non-standard */
10774         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10775
10776       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10777                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10778       if (b == NULL) {
10779         VariantClass v;
10780         char c, *q = cps->variants, *p = strchr(q, ',');
10781         if(p) *p = NULLCHAR;
10782         v = StringToVariant(q);
10783         DisplayError(variantError, 0);
10784         if(v != VariantUnknown && cps == &first) {
10785             int w, h, s;
10786             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10787                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10788             ASSIGN(appData.variant, q);
10789             Reset(TRUE, FALSE);
10790         }
10791         if(p) *p = ',';
10792         return;
10793       }
10794
10795       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10796       SendToProgram(buf, cps);
10797     }
10798     currentlyInitializedVariant = gameInfo.variant;
10799
10800     /* [HGM] send opening position in FRC to first engine */
10801     if(setup) {
10802           SendToProgram("force\n", cps);
10803           SendBoard(cps, 0);
10804           /* engine is now in force mode! Set flag to wake it up after first move. */
10805           setboardSpoiledMachineBlack = 1;
10806     }
10807
10808     if (cps->sendICS) {
10809       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10810       SendToProgram(buf, cps);
10811     }
10812     cps->maybeThinking = FALSE;
10813     cps->offeredDraw = 0;
10814     if (!appData.icsActive) {
10815         SendTimeControl(cps, movesPerSession, timeControl,
10816                         timeIncrement, appData.searchDepth,
10817                         searchTime);
10818     }
10819     if (appData.showThinking
10820         // [HGM] thinking: four options require thinking output to be sent
10821         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10822                                 ) {
10823         SendToProgram("post\n", cps);
10824     }
10825     SendToProgram("hard\n", cps);
10826     if (!appData.ponderNextMove) {
10827         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10828            it without being sure what state we are in first.  "hard"
10829            is not a toggle, so that one is OK.
10830          */
10831         SendToProgram("easy\n", cps);
10832     }
10833     if (cps->usePing) {
10834       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10835       SendToProgram(buf, cps);
10836     }
10837     cps->initDone = TRUE;
10838     ClearEngineOutputPane(cps == &second);
10839 }
10840
10841
10842 void
10843 ResendOptions (ChessProgramState *cps)
10844 { // send the stored value of the options
10845   int i;
10846   char buf[MSG_SIZ];
10847   Option *opt = cps->option;
10848   for(i=0; i<cps->nrOptions; i++, opt++) {
10849       switch(opt->type) {
10850         case Spin:
10851         case Slider:
10852         case CheckBox:
10853             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10854           break;
10855         case ComboBox:
10856           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10857           break;
10858         default:
10859             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10860           break;
10861         case Button:
10862         case SaveButton:
10863           continue;
10864       }
10865       SendToProgram(buf, cps);
10866   }
10867 }
10868
10869 void
10870 StartChessProgram (ChessProgramState *cps)
10871 {
10872     char buf[MSG_SIZ];
10873     int err;
10874
10875     if (appData.noChessProgram) return;
10876     cps->initDone = FALSE;
10877
10878     if (strcmp(cps->host, "localhost") == 0) {
10879         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10880     } else if (*appData.remoteShell == NULLCHAR) {
10881         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10882     } else {
10883         if (*appData.remoteUser == NULLCHAR) {
10884           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10885                     cps->program);
10886         } else {
10887           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10888                     cps->host, appData.remoteUser, cps->program);
10889         }
10890         err = StartChildProcess(buf, "", &cps->pr);
10891     }
10892
10893     if (err != 0) {
10894       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10895         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10896         if(cps != &first) return;
10897         appData.noChessProgram = TRUE;
10898         ThawUI();
10899         SetNCPMode();
10900 //      DisplayFatalError(buf, err, 1);
10901 //      cps->pr = NoProc;
10902 //      cps->isr = NULL;
10903         return;
10904     }
10905
10906     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10907     if (cps->protocolVersion > 1) {
10908       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10909       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10910         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10911         cps->comboCnt = 0;  //                and values of combo boxes
10912       }
10913       SendToProgram(buf, cps);
10914       if(cps->reload) ResendOptions(cps);
10915     } else {
10916       SendToProgram("xboard\n", cps);
10917     }
10918 }
10919
10920 void
10921 TwoMachinesEventIfReady P((void))
10922 {
10923   static int curMess = 0;
10924   if (first.lastPing != first.lastPong) {
10925     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10926     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10927     return;
10928   }
10929   if (second.lastPing != second.lastPong) {
10930     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10931     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10932     return;
10933   }
10934   DisplayMessage("", ""); curMess = 0;
10935   TwoMachinesEvent();
10936 }
10937
10938 char *
10939 MakeName (char *template)
10940 {
10941     time_t clock;
10942     struct tm *tm;
10943     static char buf[MSG_SIZ];
10944     char *p = buf;
10945     int i;
10946
10947     clock = time((time_t *)NULL);
10948     tm = localtime(&clock);
10949
10950     while(*p++ = *template++) if(p[-1] == '%') {
10951         switch(*template++) {
10952           case 0:   *p = 0; return buf;
10953           case 'Y': i = tm->tm_year+1900; break;
10954           case 'y': i = tm->tm_year-100; break;
10955           case 'M': i = tm->tm_mon+1; break;
10956           case 'd': i = tm->tm_mday; break;
10957           case 'h': i = tm->tm_hour; break;
10958           case 'm': i = tm->tm_min; break;
10959           case 's': i = tm->tm_sec; break;
10960           default:  i = 0;
10961         }
10962         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10963     }
10964     return buf;
10965 }
10966
10967 int
10968 CountPlayers (char *p)
10969 {
10970     int n = 0;
10971     while(p = strchr(p, '\n')) p++, n++; // count participants
10972     return n;
10973 }
10974
10975 FILE *
10976 WriteTourneyFile (char *results, FILE *f)
10977 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10978     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10979     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10980         // create a file with tournament description
10981         fprintf(f, "-participants {%s}\n", appData.participants);
10982         fprintf(f, "-seedBase %d\n", appData.seedBase);
10983         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10984         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10985         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10986         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10987         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10988         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10989         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10990         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10991         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10992         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10993         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10994         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10995         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10996         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10997         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10998         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10999         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11000         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11001         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11002         fprintf(f, "-smpCores %d\n", appData.smpCores);
11003         if(searchTime > 0)
11004                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11005         else {
11006                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11007                 fprintf(f, "-tc %s\n", appData.timeControl);
11008                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11009         }
11010         fprintf(f, "-results \"%s\"\n", results);
11011     }
11012     return f;
11013 }
11014
11015 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11016
11017 void
11018 Substitute (char *participants, int expunge)
11019 {
11020     int i, changed, changes=0, nPlayers=0;
11021     char *p, *q, *r, buf[MSG_SIZ];
11022     if(participants == NULL) return;
11023     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11024     r = p = participants; q = appData.participants;
11025     while(*p && *p == *q) {
11026         if(*p == '\n') r = p+1, nPlayers++;
11027         p++; q++;
11028     }
11029     if(*p) { // difference
11030         while(*p && *p++ != '\n');
11031         while(*q && *q++ != '\n');
11032       changed = nPlayers;
11033         changes = 1 + (strcmp(p, q) != 0);
11034     }
11035     if(changes == 1) { // a single engine mnemonic was changed
11036         q = r; while(*q) nPlayers += (*q++ == '\n');
11037         p = buf; while(*r && (*p = *r++) != '\n') p++;
11038         *p = NULLCHAR;
11039         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11040         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11041         if(mnemonic[i]) { // The substitute is valid
11042             FILE *f;
11043             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11044                 flock(fileno(f), LOCK_EX);
11045                 ParseArgsFromFile(f);
11046                 fseek(f, 0, SEEK_SET);
11047                 FREE(appData.participants); appData.participants = participants;
11048                 if(expunge) { // erase results of replaced engine
11049                     int len = strlen(appData.results), w, b, dummy;
11050                     for(i=0; i<len; i++) {
11051                         Pairing(i, nPlayers, &w, &b, &dummy);
11052                         if((w == changed || b == changed) && appData.results[i] == '*') {
11053                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11054                             fclose(f);
11055                             return;
11056                         }
11057                     }
11058                     for(i=0; i<len; i++) {
11059                         Pairing(i, nPlayers, &w, &b, &dummy);
11060                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11061                     }
11062                 }
11063                 WriteTourneyFile(appData.results, f);
11064                 fclose(f); // release lock
11065                 return;
11066             }
11067         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11068     }
11069     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11070     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11071     free(participants);
11072     return;
11073 }
11074
11075 int
11076 CheckPlayers (char *participants)
11077 {
11078         int i;
11079         char buf[MSG_SIZ], *p;
11080         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11081         while(p = strchr(participants, '\n')) {
11082             *p = NULLCHAR;
11083             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11084             if(!mnemonic[i]) {
11085                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11086                 *p = '\n';
11087                 DisplayError(buf, 0);
11088                 return 1;
11089             }
11090             *p = '\n';
11091             participants = p + 1;
11092         }
11093         return 0;
11094 }
11095
11096 int
11097 CreateTourney (char *name)
11098 {
11099         FILE *f;
11100         if(matchMode && strcmp(name, appData.tourneyFile)) {
11101              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11102         }
11103         if(name[0] == NULLCHAR) {
11104             if(appData.participants[0])
11105                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11106             return 0;
11107         }
11108         f = fopen(name, "r");
11109         if(f) { // file exists
11110             ASSIGN(appData.tourneyFile, name);
11111             ParseArgsFromFile(f); // parse it
11112         } else {
11113             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11114             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11115                 DisplayError(_("Not enough participants"), 0);
11116                 return 0;
11117             }
11118             if(CheckPlayers(appData.participants)) return 0;
11119             ASSIGN(appData.tourneyFile, name);
11120             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11121             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11122         }
11123         fclose(f);
11124         appData.noChessProgram = FALSE;
11125         appData.clockMode = TRUE;
11126         SetGNUMode();
11127         return 1;
11128 }
11129
11130 int
11131 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11132 {
11133     char buf[MSG_SIZ], *p, *q;
11134     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11135     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11136     skip = !all && group[0]; // if group requested, we start in skip mode
11137     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11138         p = names; q = buf; header = 0;
11139         while(*p && *p != '\n') *q++ = *p++;
11140         *q = 0;
11141         if(*p == '\n') p++;
11142         if(buf[0] == '#') {
11143             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11144             depth++; // we must be entering a new group
11145             if(all) continue; // suppress printing group headers when complete list requested
11146             header = 1;
11147             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11148         }
11149         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11150         if(engineList[i]) free(engineList[i]);
11151         engineList[i] = strdup(buf);
11152         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11153         if(engineMnemonic[i]) free(engineMnemonic[i]);
11154         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11155             strcat(buf, " (");
11156             sscanf(q + 8, "%s", buf + strlen(buf));
11157             strcat(buf, ")");
11158         }
11159         engineMnemonic[i] = strdup(buf);
11160         i++;
11161     }
11162     engineList[i] = engineMnemonic[i] = NULL;
11163     return i;
11164 }
11165
11166 // following implemented as macro to avoid type limitations
11167 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11168
11169 void
11170 SwapEngines (int n)
11171 {   // swap settings for first engine and other engine (so far only some selected options)
11172     int h;
11173     char *p;
11174     if(n == 0) return;
11175     SWAP(directory, p)
11176     SWAP(chessProgram, p)
11177     SWAP(isUCI, h)
11178     SWAP(hasOwnBookUCI, h)
11179     SWAP(protocolVersion, h)
11180     SWAP(reuse, h)
11181     SWAP(scoreIsAbsolute, h)
11182     SWAP(timeOdds, h)
11183     SWAP(logo, p)
11184     SWAP(pgnName, p)
11185     SWAP(pvSAN, h)
11186     SWAP(engOptions, p)
11187     SWAP(engInitString, p)
11188     SWAP(computerString, p)
11189     SWAP(features, p)
11190     SWAP(fenOverride, p)
11191     SWAP(NPS, h)
11192     SWAP(accumulateTC, h)
11193     SWAP(drawDepth, h)
11194     SWAP(host, p)
11195     SWAP(pseudo, h)
11196 }
11197
11198 int
11199 GetEngineLine (char *s, int n)
11200 {
11201     int i;
11202     char buf[MSG_SIZ];
11203     extern char *icsNames;
11204     if(!s || !*s) return 0;
11205     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11206     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11207     if(!mnemonic[i]) return 0;
11208     if(n == 11) return 1; // just testing if there was a match
11209     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11210     if(n == 1) SwapEngines(n);
11211     ParseArgsFromString(buf);
11212     if(n == 1) SwapEngines(n);
11213     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11214         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11215         ParseArgsFromString(buf);
11216     }
11217     return 1;
11218 }
11219
11220 int
11221 SetPlayer (int player, char *p)
11222 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11223     int i;
11224     char buf[MSG_SIZ], *engineName;
11225     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11226     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11227     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11228     if(mnemonic[i]) {
11229         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11230         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11231         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11232         ParseArgsFromString(buf);
11233     } else { // no engine with this nickname is installed!
11234         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11235         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11236         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11237         ModeHighlight();
11238         DisplayError(buf, 0);
11239         return 0;
11240     }
11241     free(engineName);
11242     return i;
11243 }
11244
11245 char *recentEngines;
11246
11247 void
11248 RecentEngineEvent (int nr)
11249 {
11250     int n;
11251 //    SwapEngines(1); // bump first to second
11252 //    ReplaceEngine(&second, 1); // and load it there
11253     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11254     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11255     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11256         ReplaceEngine(&first, 0);
11257         FloatToFront(&appData.recentEngineList, command[n]);
11258     }
11259 }
11260
11261 int
11262 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11263 {   // determine players from game number
11264     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11265
11266     if(appData.tourneyType == 0) {
11267         roundsPerCycle = (nPlayers - 1) | 1;
11268         pairingsPerRound = nPlayers / 2;
11269     } else if(appData.tourneyType > 0) {
11270         roundsPerCycle = nPlayers - appData.tourneyType;
11271         pairingsPerRound = appData.tourneyType;
11272     }
11273     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11274     gamesPerCycle = gamesPerRound * roundsPerCycle;
11275     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11276     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11277     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11278     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11279     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11280     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11281
11282     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11283     if(appData.roundSync) *syncInterval = gamesPerRound;
11284
11285     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11286
11287     if(appData.tourneyType == 0) {
11288         if(curPairing == (nPlayers-1)/2 ) {
11289             *whitePlayer = curRound;
11290             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11291         } else {
11292             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11293             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11294             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11295             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11296         }
11297     } else if(appData.tourneyType > 1) {
11298         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11299         *whitePlayer = curRound + appData.tourneyType;
11300     } else if(appData.tourneyType > 0) {
11301         *whitePlayer = curPairing;
11302         *blackPlayer = curRound + appData.tourneyType;
11303     }
11304
11305     // take care of white/black alternation per round.
11306     // For cycles and games this is already taken care of by default, derived from matchGame!
11307     return curRound & 1;
11308 }
11309
11310 int
11311 NextTourneyGame (int nr, int *swapColors)
11312 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11313     char *p, *q;
11314     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11315     FILE *tf;
11316     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11317     tf = fopen(appData.tourneyFile, "r");
11318     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11319     ParseArgsFromFile(tf); fclose(tf);
11320     InitTimeControls(); // TC might be altered from tourney file
11321
11322     nPlayers = CountPlayers(appData.participants); // count participants
11323     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11324     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11325
11326     if(syncInterval) {
11327         p = q = appData.results;
11328         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11329         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11330             DisplayMessage(_("Waiting for other game(s)"),"");
11331             waitingForGame = TRUE;
11332             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11333             return 0;
11334         }
11335         waitingForGame = FALSE;
11336     }
11337
11338     if(appData.tourneyType < 0) {
11339         if(nr>=0 && !pairingReceived) {
11340             char buf[1<<16];
11341             if(pairing.pr == NoProc) {
11342                 if(!appData.pairingEngine[0]) {
11343                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11344                     return 0;
11345                 }
11346                 StartChessProgram(&pairing); // starts the pairing engine
11347             }
11348             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11349             SendToProgram(buf, &pairing);
11350             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11351             SendToProgram(buf, &pairing);
11352             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11353         }
11354         pairingReceived = 0;                              // ... so we continue here
11355         *swapColors = 0;
11356         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11357         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11358         matchGame = 1; roundNr = nr / syncInterval + 1;
11359     }
11360
11361     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11362
11363     // redefine engines, engine dir, etc.
11364     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11365     if(first.pr == NoProc) {
11366       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11367       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11368     }
11369     if(second.pr == NoProc) {
11370       SwapEngines(1);
11371       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11372       SwapEngines(1);         // and make that valid for second engine by swapping
11373       InitEngine(&second, 1);
11374     }
11375     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11376     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11377     return OK;
11378 }
11379
11380 void
11381 NextMatchGame ()
11382 {   // performs game initialization that does not invoke engines, and then tries to start the game
11383     int res, firstWhite, swapColors = 0;
11384     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11385     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
11386         char buf[MSG_SIZ];
11387         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11388         if(strcmp(buf, currentDebugFile)) { // name has changed
11389             FILE *f = fopen(buf, "w");
11390             if(f) { // if opening the new file failed, just keep using the old one
11391                 ASSIGN(currentDebugFile, buf);
11392                 fclose(debugFP);
11393                 debugFP = f;
11394             }
11395             if(appData.serverFileName) {
11396                 if(serverFP) fclose(serverFP);
11397                 serverFP = fopen(appData.serverFileName, "w");
11398                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11399                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11400             }
11401         }
11402     }
11403     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11404     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11405     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11406     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11407     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11408     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11409     Reset(FALSE, first.pr != NoProc);
11410     res = LoadGameOrPosition(matchGame); // setup game
11411     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11412     if(!res) return; // abort when bad game/pos file
11413     TwoMachinesEvent();
11414 }
11415
11416 void
11417 UserAdjudicationEvent (int result)
11418 {
11419     ChessMove gameResult = GameIsDrawn;
11420
11421     if( result > 0 ) {
11422         gameResult = WhiteWins;
11423     }
11424     else if( result < 0 ) {
11425         gameResult = BlackWins;
11426     }
11427
11428     if( gameMode == TwoMachinesPlay ) {
11429         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11430     }
11431 }
11432
11433
11434 // [HGM] save: calculate checksum of game to make games easily identifiable
11435 int
11436 StringCheckSum (char *s)
11437 {
11438         int i = 0;
11439         if(s==NULL) return 0;
11440         while(*s) i = i*259 + *s++;
11441         return i;
11442 }
11443
11444 int
11445 GameCheckSum ()
11446 {
11447         int i, sum=0;
11448         for(i=backwardMostMove; i<forwardMostMove; i++) {
11449                 sum += pvInfoList[i].depth;
11450                 sum += StringCheckSum(parseList[i]);
11451                 sum += StringCheckSum(commentList[i]);
11452                 sum *= 261;
11453         }
11454         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11455         return sum + StringCheckSum(commentList[i]);
11456 } // end of save patch
11457
11458 void
11459 GameEnds (ChessMove result, char *resultDetails, int whosays)
11460 {
11461     GameMode nextGameMode;
11462     int isIcsGame;
11463     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11464
11465     if(endingGame) return; /* [HGM] crash: forbid recursion */
11466     endingGame = 1;
11467     if(twoBoards) { // [HGM] dual: switch back to one board
11468         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11469         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11470     }
11471     if (appData.debugMode) {
11472       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11473               result, resultDetails ? resultDetails : "(null)", whosays);
11474     }
11475
11476     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11477
11478     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11479
11480     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11481         /* If we are playing on ICS, the server decides when the
11482            game is over, but the engine can offer to draw, claim
11483            a draw, or resign.
11484          */
11485 #if ZIPPY
11486         if (appData.zippyPlay && first.initDone) {
11487             if (result == GameIsDrawn) {
11488                 /* In case draw still needs to be claimed */
11489                 SendToICS(ics_prefix);
11490                 SendToICS("draw\n");
11491             } else if (StrCaseStr(resultDetails, "resign")) {
11492                 SendToICS(ics_prefix);
11493                 SendToICS("resign\n");
11494             }
11495         }
11496 #endif
11497         endingGame = 0; /* [HGM] crash */
11498         return;
11499     }
11500
11501     /* If we're loading the game from a file, stop */
11502     if (whosays == GE_FILE) {
11503       (void) StopLoadGameTimer();
11504       gameFileFP = NULL;
11505     }
11506
11507     /* Cancel draw offers */
11508     first.offeredDraw = second.offeredDraw = 0;
11509
11510     /* If this is an ICS game, only ICS can really say it's done;
11511        if not, anyone can. */
11512     isIcsGame = (gameMode == IcsPlayingWhite ||
11513                  gameMode == IcsPlayingBlack ||
11514                  gameMode == IcsObserving    ||
11515                  gameMode == IcsExamining);
11516
11517     if (!isIcsGame || whosays == GE_ICS) {
11518         /* OK -- not an ICS game, or ICS said it was done */
11519         StopClocks();
11520         if (!isIcsGame && !appData.noChessProgram)
11521           SetUserThinkingEnables();
11522
11523         /* [HGM] if a machine claims the game end we verify this claim */
11524         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11525             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11526                 char claimer;
11527                 ChessMove trueResult = (ChessMove) -1;
11528
11529                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11530                                             first.twoMachinesColor[0] :
11531                                             second.twoMachinesColor[0] ;
11532
11533                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11534                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11535                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11536                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11537                 } else
11538                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11539                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11540                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11541                 } else
11542                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11543                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11544                 }
11545
11546                 // now verify win claims, but not in drop games, as we don't understand those yet
11547                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11548                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11549                     (result == WhiteWins && claimer == 'w' ||
11550                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11551                       if (appData.debugMode) {
11552                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11553                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11554                       }
11555                       if(result != trueResult) {
11556                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11557                               result = claimer == 'w' ? BlackWins : WhiteWins;
11558                               resultDetails = buf;
11559                       }
11560                 } else
11561                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11562                     && (forwardMostMove <= backwardMostMove ||
11563                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11564                         (claimer=='b')==(forwardMostMove&1))
11565                                                                                   ) {
11566                       /* [HGM] verify: draws that were not flagged are false claims */
11567                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11568                       result = claimer == 'w' ? BlackWins : WhiteWins;
11569                       resultDetails = buf;
11570                 }
11571                 /* (Claiming a loss is accepted no questions asked!) */
11572             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11573                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11574                 result = GameUnfinished;
11575                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11576             }
11577             /* [HGM] bare: don't allow bare King to win */
11578             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11579                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11580                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11581                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11582                && result != GameIsDrawn)
11583             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11584                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11585                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11586                         if(p >= 0 && p <= (int)WhiteKing) k++;
11587                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11588                 }
11589                 if (appData.debugMode) {
11590                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11591                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11592                 }
11593                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11594                         result = GameIsDrawn;
11595                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11596                         resultDetails = buf;
11597                 }
11598             }
11599         }
11600
11601
11602         if(serverMoves != NULL && !loadFlag) { char c = '=';
11603             if(result==WhiteWins) c = '+';
11604             if(result==BlackWins) c = '-';
11605             if(resultDetails != NULL)
11606                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11607         }
11608         if (resultDetails != NULL) {
11609             gameInfo.result = result;
11610             gameInfo.resultDetails = StrSave(resultDetails);
11611
11612             /* display last move only if game was not loaded from file */
11613             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11614                 DisplayMove(currentMove - 1);
11615
11616             if (forwardMostMove != 0) {
11617                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11618                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11619                                                                 ) {
11620                     if (*appData.saveGameFile != NULLCHAR) {
11621                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11622                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11623                         else
11624                         SaveGameToFile(appData.saveGameFile, TRUE);
11625                     } else if (appData.autoSaveGames) {
11626                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11627                     }
11628                     if (*appData.savePositionFile != NULLCHAR) {
11629                         SavePositionToFile(appData.savePositionFile);
11630                     }
11631                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11632                 }
11633             }
11634
11635             /* Tell program how game ended in case it is learning */
11636             /* [HGM] Moved this to after saving the PGN, just in case */
11637             /* engine died and we got here through time loss. In that */
11638             /* case we will get a fatal error writing the pipe, which */
11639             /* would otherwise lose us the PGN.                       */
11640             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11641             /* output during GameEnds should never be fatal anymore   */
11642             if (gameMode == MachinePlaysWhite ||
11643                 gameMode == MachinePlaysBlack ||
11644                 gameMode == TwoMachinesPlay ||
11645                 gameMode == IcsPlayingWhite ||
11646                 gameMode == IcsPlayingBlack ||
11647                 gameMode == BeginningOfGame) {
11648                 char buf[MSG_SIZ];
11649                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11650                         resultDetails);
11651                 if (first.pr != NoProc) {
11652                     SendToProgram(buf, &first);
11653                 }
11654                 if (second.pr != NoProc &&
11655                     gameMode == TwoMachinesPlay) {
11656                     SendToProgram(buf, &second);
11657                 }
11658             }
11659         }
11660
11661         if (appData.icsActive) {
11662             if (appData.quietPlay &&
11663                 (gameMode == IcsPlayingWhite ||
11664                  gameMode == IcsPlayingBlack)) {
11665                 SendToICS(ics_prefix);
11666                 SendToICS("set shout 1\n");
11667             }
11668             nextGameMode = IcsIdle;
11669             ics_user_moved = FALSE;
11670             /* clean up premove.  It's ugly when the game has ended and the
11671              * premove highlights are still on the board.
11672              */
11673             if (gotPremove) {
11674               gotPremove = FALSE;
11675               ClearPremoveHighlights();
11676               DrawPosition(FALSE, boards[currentMove]);
11677             }
11678             if (whosays == GE_ICS) {
11679                 switch (result) {
11680                 case WhiteWins:
11681                     if (gameMode == IcsPlayingWhite)
11682                         PlayIcsWinSound();
11683                     else if(gameMode == IcsPlayingBlack)
11684                         PlayIcsLossSound();
11685                     break;
11686                 case BlackWins:
11687                     if (gameMode == IcsPlayingBlack)
11688                         PlayIcsWinSound();
11689                     else if(gameMode == IcsPlayingWhite)
11690                         PlayIcsLossSound();
11691                     break;
11692                 case GameIsDrawn:
11693                     PlayIcsDrawSound();
11694                     break;
11695                 default:
11696                     PlayIcsUnfinishedSound();
11697                 }
11698             }
11699             if(appData.quitNext) { ExitEvent(0); return; }
11700         } else if (gameMode == EditGame ||
11701                    gameMode == PlayFromGameFile ||
11702                    gameMode == AnalyzeMode ||
11703                    gameMode == AnalyzeFile) {
11704             nextGameMode = gameMode;
11705         } else {
11706             nextGameMode = EndOfGame;
11707         }
11708         pausing = FALSE;
11709         ModeHighlight();
11710     } else {
11711         nextGameMode = gameMode;
11712     }
11713
11714     if (appData.noChessProgram) {
11715         gameMode = nextGameMode;
11716         ModeHighlight();
11717         endingGame = 0; /* [HGM] crash */
11718         return;
11719     }
11720
11721     if (first.reuse) {
11722         /* Put first chess program into idle state */
11723         if (first.pr != NoProc &&
11724             (gameMode == MachinePlaysWhite ||
11725              gameMode == MachinePlaysBlack ||
11726              gameMode == TwoMachinesPlay ||
11727              gameMode == IcsPlayingWhite ||
11728              gameMode == IcsPlayingBlack ||
11729              gameMode == BeginningOfGame)) {
11730             SendToProgram("force\n", &first);
11731             if (first.usePing) {
11732               char buf[MSG_SIZ];
11733               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11734               SendToProgram(buf, &first);
11735             }
11736         }
11737     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11738         /* Kill off first chess program */
11739         if (first.isr != NULL)
11740           RemoveInputSource(first.isr);
11741         first.isr = NULL;
11742
11743         if (first.pr != NoProc) {
11744             ExitAnalyzeMode();
11745             DoSleep( appData.delayBeforeQuit );
11746             SendToProgram("quit\n", &first);
11747             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11748             first.reload = TRUE;
11749         }
11750         first.pr = NoProc;
11751     }
11752     if (second.reuse) {
11753         /* Put second chess program into idle state */
11754         if (second.pr != NoProc &&
11755             gameMode == TwoMachinesPlay) {
11756             SendToProgram("force\n", &second);
11757             if (second.usePing) {
11758               char buf[MSG_SIZ];
11759               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11760               SendToProgram(buf, &second);
11761             }
11762         }
11763     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11764         /* Kill off second chess program */
11765         if (second.isr != NULL)
11766           RemoveInputSource(second.isr);
11767         second.isr = NULL;
11768
11769         if (second.pr != NoProc) {
11770             DoSleep( appData.delayBeforeQuit );
11771             SendToProgram("quit\n", &second);
11772             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11773             second.reload = TRUE;
11774         }
11775         second.pr = NoProc;
11776     }
11777
11778     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11779         char resChar = '=';
11780         switch (result) {
11781         case WhiteWins:
11782           resChar = '+';
11783           if (first.twoMachinesColor[0] == 'w') {
11784             first.matchWins++;
11785           } else {
11786             second.matchWins++;
11787           }
11788           break;
11789         case BlackWins:
11790           resChar = '-';
11791           if (first.twoMachinesColor[0] == 'b') {
11792             first.matchWins++;
11793           } else {
11794             second.matchWins++;
11795           }
11796           break;
11797         case GameUnfinished:
11798           resChar = ' ';
11799         default:
11800           break;
11801         }
11802
11803         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11804         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11805             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11806             ReserveGame(nextGame, resChar); // sets nextGame
11807             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11808             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11809         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11810
11811         if (nextGame <= appData.matchGames && !abortMatch) {
11812             gameMode = nextGameMode;
11813             matchGame = nextGame; // this will be overruled in tourney mode!
11814             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11815             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11816             endingGame = 0; /* [HGM] crash */
11817             return;
11818         } else {
11819             gameMode = nextGameMode;
11820             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11821                      first.tidy, second.tidy,
11822                      first.matchWins, second.matchWins,
11823                      appData.matchGames - (first.matchWins + second.matchWins));
11824             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11825             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11826             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11827             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11828                 first.twoMachinesColor = "black\n";
11829                 second.twoMachinesColor = "white\n";
11830             } else {
11831                 first.twoMachinesColor = "white\n";
11832                 second.twoMachinesColor = "black\n";
11833             }
11834         }
11835     }
11836     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11837         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11838       ExitAnalyzeMode();
11839     gameMode = nextGameMode;
11840     ModeHighlight();
11841     endingGame = 0;  /* [HGM] crash */
11842     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11843         if(matchMode == TRUE) { // match through command line: exit with or without popup
11844             if(ranking) {
11845                 ToNrEvent(forwardMostMove);
11846                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11847                 else ExitEvent(0);
11848             } else DisplayFatalError(buf, 0, 0);
11849         } else { // match through menu; just stop, with or without popup
11850             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11851             ModeHighlight();
11852             if(ranking){
11853                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11854             } else DisplayNote(buf);
11855       }
11856       if(ranking) free(ranking);
11857     }
11858 }
11859
11860 /* Assumes program was just initialized (initString sent).
11861    Leaves program in force mode. */
11862 void
11863 FeedMovesToProgram (ChessProgramState *cps, int upto)
11864 {
11865     int i;
11866
11867     if (appData.debugMode)
11868       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11869               startedFromSetupPosition ? "position and " : "",
11870               backwardMostMove, upto, cps->which);
11871     if(currentlyInitializedVariant != gameInfo.variant) {
11872       char buf[MSG_SIZ];
11873         // [HGM] variantswitch: make engine aware of new variant
11874         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11875                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11876                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11877         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11878         SendToProgram(buf, cps);
11879         currentlyInitializedVariant = gameInfo.variant;
11880     }
11881     SendToProgram("force\n", cps);
11882     if (startedFromSetupPosition) {
11883         SendBoard(cps, backwardMostMove);
11884     if (appData.debugMode) {
11885         fprintf(debugFP, "feedMoves\n");
11886     }
11887     }
11888     for (i = backwardMostMove; i < upto; i++) {
11889         SendMoveToProgram(i, cps);
11890     }
11891 }
11892
11893
11894 int
11895 ResurrectChessProgram ()
11896 {
11897      /* The chess program may have exited.
11898         If so, restart it and feed it all the moves made so far. */
11899     static int doInit = 0;
11900
11901     if (appData.noChessProgram) return 1;
11902
11903     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11904         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11905         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11906         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11907     } else {
11908         if (first.pr != NoProc) return 1;
11909         StartChessProgram(&first);
11910     }
11911     InitChessProgram(&first, FALSE);
11912     FeedMovesToProgram(&first, currentMove);
11913
11914     if (!first.sendTime) {
11915         /* can't tell gnuchess what its clock should read,
11916            so we bow to its notion. */
11917         ResetClocks();
11918         timeRemaining[0][currentMove] = whiteTimeRemaining;
11919         timeRemaining[1][currentMove] = blackTimeRemaining;
11920     }
11921
11922     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11923                 appData.icsEngineAnalyze) && first.analysisSupport) {
11924       SendToProgram("analyze\n", &first);
11925       first.analyzing = TRUE;
11926     }
11927     return 1;
11928 }
11929
11930 /*
11931  * Button procedures
11932  */
11933 void
11934 Reset (int redraw, int init)
11935 {
11936     int i;
11937
11938     if (appData.debugMode) {
11939         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11940                 redraw, init, gameMode);
11941     }
11942     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11943     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11944     CleanupTail(); // [HGM] vari: delete any stored variations
11945     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11946     pausing = pauseExamInvalid = FALSE;
11947     startedFromSetupPosition = blackPlaysFirst = FALSE;
11948     firstMove = TRUE;
11949     whiteFlag = blackFlag = FALSE;
11950     userOfferedDraw = FALSE;
11951     hintRequested = bookRequested = FALSE;
11952     first.maybeThinking = FALSE;
11953     second.maybeThinking = FALSE;
11954     first.bookSuspend = FALSE; // [HGM] book
11955     second.bookSuspend = FALSE;
11956     thinkOutput[0] = NULLCHAR;
11957     lastHint[0] = NULLCHAR;
11958     ClearGameInfo(&gameInfo);
11959     gameInfo.variant = StringToVariant(appData.variant);
11960     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11961     ics_user_moved = ics_clock_paused = FALSE;
11962     ics_getting_history = H_FALSE;
11963     ics_gamenum = -1;
11964     white_holding[0] = black_holding[0] = NULLCHAR;
11965     ClearProgramStats();
11966     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11967
11968     ResetFrontEnd();
11969     ClearHighlights();
11970     flipView = appData.flipView;
11971     ClearPremoveHighlights();
11972     gotPremove = FALSE;
11973     alarmSounded = FALSE;
11974     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
11975
11976     GameEnds(EndOfFile, NULL, GE_PLAYER);
11977     if(appData.serverMovesName != NULL) {
11978         /* [HGM] prepare to make moves file for broadcasting */
11979         clock_t t = clock();
11980         if(serverMoves != NULL) fclose(serverMoves);
11981         serverMoves = fopen(appData.serverMovesName, "r");
11982         if(serverMoves != NULL) {
11983             fclose(serverMoves);
11984             /* delay 15 sec before overwriting, so all clients can see end */
11985             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11986         }
11987         serverMoves = fopen(appData.serverMovesName, "w");
11988     }
11989
11990     ExitAnalyzeMode();
11991     gameMode = BeginningOfGame;
11992     ModeHighlight();
11993     if(appData.icsActive) gameInfo.variant = VariantNormal;
11994     currentMove = forwardMostMove = backwardMostMove = 0;
11995     MarkTargetSquares(1);
11996     InitPosition(redraw);
11997     for (i = 0; i < MAX_MOVES; i++) {
11998         if (commentList[i] != NULL) {
11999             free(commentList[i]);
12000             commentList[i] = NULL;
12001         }
12002     }
12003     ResetClocks();
12004     timeRemaining[0][0] = whiteTimeRemaining;
12005     timeRemaining[1][0] = blackTimeRemaining;
12006
12007     if (first.pr == NoProc) {
12008         StartChessProgram(&first);
12009     }
12010     if (init) {
12011             InitChessProgram(&first, startedFromSetupPosition);
12012     }
12013     DisplayTitle("");
12014     DisplayMessage("", "");
12015     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12016     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12017     ClearMap();        // [HGM] exclude: invalidate map
12018 }
12019
12020 void
12021 AutoPlayGameLoop ()
12022 {
12023     for (;;) {
12024         if (!AutoPlayOneMove())
12025           return;
12026         if (matchMode || appData.timeDelay == 0)
12027           continue;
12028         if (appData.timeDelay < 0)
12029           return;
12030         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12031         break;
12032     }
12033 }
12034
12035 void
12036 AnalyzeNextGame()
12037 {
12038     ReloadGame(1); // next game
12039 }
12040
12041 int
12042 AutoPlayOneMove ()
12043 {
12044     int fromX, fromY, toX, toY;
12045
12046     if (appData.debugMode) {
12047       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12048     }
12049
12050     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12051       return FALSE;
12052
12053     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12054       pvInfoList[currentMove].depth = programStats.depth;
12055       pvInfoList[currentMove].score = programStats.score;
12056       pvInfoList[currentMove].time  = 0;
12057       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12058       else { // append analysis of final position as comment
12059         char buf[MSG_SIZ];
12060         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12061         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12062       }
12063       programStats.depth = 0;
12064     }
12065
12066     if (currentMove >= forwardMostMove) {
12067       if(gameMode == AnalyzeFile) {
12068           if(appData.loadGameIndex == -1) {
12069             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12070           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12071           } else {
12072           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12073         }
12074       }
12075 //      gameMode = EndOfGame;
12076 //      ModeHighlight();
12077
12078       /* [AS] Clear current move marker at the end of a game */
12079       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12080
12081       return FALSE;
12082     }
12083
12084     toX = moveList[currentMove][2] - AAA;
12085     toY = moveList[currentMove][3] - ONE;
12086
12087     if (moveList[currentMove][1] == '@') {
12088         if (appData.highlightLastMove) {
12089             SetHighlights(-1, -1, toX, toY);
12090         }
12091     } else {
12092         int viaX = moveList[currentMove][5] - AAA;
12093         int viaY = moveList[currentMove][6] - ONE;
12094         fromX = moveList[currentMove][0] - AAA;
12095         fromY = moveList[currentMove][1] - ONE;
12096
12097         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12098
12099         if(moveList[currentMove][4] == ';') { // multi-leg
12100             ChessSquare piece = boards[currentMove][viaY][viaX];
12101             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12102             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12103             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12104             boards[currentMove][viaY][viaX] = piece;
12105         } else
12106         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12107
12108         if (appData.highlightLastMove) {
12109             SetHighlights(fromX, fromY, toX, toY);
12110         }
12111     }
12112     DisplayMove(currentMove);
12113     SendMoveToProgram(currentMove++, &first);
12114     DisplayBothClocks();
12115     DrawPosition(FALSE, boards[currentMove]);
12116     // [HGM] PV info: always display, routine tests if empty
12117     DisplayComment(currentMove - 1, commentList[currentMove]);
12118     return TRUE;
12119 }
12120
12121
12122 int
12123 LoadGameOneMove (ChessMove readAhead)
12124 {
12125     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12126     char promoChar = NULLCHAR;
12127     ChessMove moveType;
12128     char move[MSG_SIZ];
12129     char *p, *q;
12130
12131     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12132         gameMode != AnalyzeMode && gameMode != Training) {
12133         gameFileFP = NULL;
12134         return FALSE;
12135     }
12136
12137     yyboardindex = forwardMostMove;
12138     if (readAhead != EndOfFile) {
12139       moveType = readAhead;
12140     } else {
12141       if (gameFileFP == NULL)
12142           return FALSE;
12143       moveType = (ChessMove) Myylex();
12144     }
12145
12146     done = FALSE;
12147     switch (moveType) {
12148       case Comment:
12149         if (appData.debugMode)
12150           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12151         p = yy_text;
12152
12153         /* append the comment but don't display it */
12154         AppendComment(currentMove, p, FALSE);
12155         return TRUE;
12156
12157       case WhiteCapturesEnPassant:
12158       case BlackCapturesEnPassant:
12159       case WhitePromotion:
12160       case BlackPromotion:
12161       case WhiteNonPromotion:
12162       case BlackNonPromotion:
12163       case NormalMove:
12164       case FirstLeg:
12165       case WhiteKingSideCastle:
12166       case WhiteQueenSideCastle:
12167       case BlackKingSideCastle:
12168       case BlackQueenSideCastle:
12169       case WhiteKingSideCastleWild:
12170       case WhiteQueenSideCastleWild:
12171       case BlackKingSideCastleWild:
12172       case BlackQueenSideCastleWild:
12173       /* PUSH Fabien */
12174       case WhiteHSideCastleFR:
12175       case WhiteASideCastleFR:
12176       case BlackHSideCastleFR:
12177       case BlackASideCastleFR:
12178       /* POP Fabien */
12179         if (appData.debugMode)
12180           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12181         fromX = currentMoveString[0] - AAA;
12182         fromY = currentMoveString[1] - ONE;
12183         toX = currentMoveString[2] - AAA;
12184         toY = currentMoveString[3] - ONE;
12185         promoChar = currentMoveString[4];
12186         if(promoChar == ';') promoChar = currentMoveString[7];
12187         break;
12188
12189       case WhiteDrop:
12190       case BlackDrop:
12191         if (appData.debugMode)
12192           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12193         fromX = moveType == WhiteDrop ?
12194           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12195         (int) CharToPiece(ToLower(currentMoveString[0]));
12196         fromY = DROP_RANK;
12197         toX = currentMoveString[2] - AAA;
12198         toY = currentMoveString[3] - ONE;
12199         break;
12200
12201       case WhiteWins:
12202       case BlackWins:
12203       case GameIsDrawn:
12204       case GameUnfinished:
12205         if (appData.debugMode)
12206           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12207         p = strchr(yy_text, '{');
12208         if (p == NULL) p = strchr(yy_text, '(');
12209         if (p == NULL) {
12210             p = yy_text;
12211             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12212         } else {
12213             q = strchr(p, *p == '{' ? '}' : ')');
12214             if (q != NULL) *q = NULLCHAR;
12215             p++;
12216         }
12217         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12218         GameEnds(moveType, p, GE_FILE);
12219         done = TRUE;
12220         if (cmailMsgLoaded) {
12221             ClearHighlights();
12222             flipView = WhiteOnMove(currentMove);
12223             if (moveType == GameUnfinished) flipView = !flipView;
12224             if (appData.debugMode)
12225               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12226         }
12227         break;
12228
12229       case EndOfFile:
12230         if (appData.debugMode)
12231           fprintf(debugFP, "Parser hit end of file\n");
12232         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12233           case MT_NONE:
12234           case MT_CHECK:
12235             break;
12236           case MT_CHECKMATE:
12237           case MT_STAINMATE:
12238             if (WhiteOnMove(currentMove)) {
12239                 GameEnds(BlackWins, "Black mates", GE_FILE);
12240             } else {
12241                 GameEnds(WhiteWins, "White mates", GE_FILE);
12242             }
12243             break;
12244           case MT_STALEMATE:
12245             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12246             break;
12247         }
12248         done = TRUE;
12249         break;
12250
12251       case MoveNumberOne:
12252         if (lastLoadGameStart == GNUChessGame) {
12253             /* GNUChessGames have numbers, but they aren't move numbers */
12254             if (appData.debugMode)
12255               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12256                       yy_text, (int) moveType);
12257             return LoadGameOneMove(EndOfFile); /* tail recursion */
12258         }
12259         /* else fall thru */
12260
12261       case XBoardGame:
12262       case GNUChessGame:
12263       case PGNTag:
12264         /* Reached start of next game in file */
12265         if (appData.debugMode)
12266           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12267         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12268           case MT_NONE:
12269           case MT_CHECK:
12270             break;
12271           case MT_CHECKMATE:
12272           case MT_STAINMATE:
12273             if (WhiteOnMove(currentMove)) {
12274                 GameEnds(BlackWins, "Black mates", GE_FILE);
12275             } else {
12276                 GameEnds(WhiteWins, "White mates", GE_FILE);
12277             }
12278             break;
12279           case MT_STALEMATE:
12280             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12281             break;
12282         }
12283         done = TRUE;
12284         break;
12285
12286       case PositionDiagram:     /* should not happen; ignore */
12287       case ElapsedTime:         /* ignore */
12288       case NAG:                 /* ignore */
12289         if (appData.debugMode)
12290           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12291                   yy_text, (int) moveType);
12292         return LoadGameOneMove(EndOfFile); /* tail recursion */
12293
12294       case IllegalMove:
12295         if (appData.testLegality) {
12296             if (appData.debugMode)
12297               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12298             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12299                     (forwardMostMove / 2) + 1,
12300                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12301             DisplayError(move, 0);
12302             done = TRUE;
12303         } else {
12304             if (appData.debugMode)
12305               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12306                       yy_text, currentMoveString);
12307             if(currentMoveString[1] == '@') {
12308                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12309                 fromY = DROP_RANK;
12310             } else {
12311                 fromX = currentMoveString[0] - AAA;
12312                 fromY = currentMoveString[1] - ONE;
12313             }
12314             toX = currentMoveString[2] - AAA;
12315             toY = currentMoveString[3] - ONE;
12316             promoChar = currentMoveString[4];
12317         }
12318         break;
12319
12320       case AmbiguousMove:
12321         if (appData.debugMode)
12322           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12323         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12324                 (forwardMostMove / 2) + 1,
12325                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12326         DisplayError(move, 0);
12327         done = TRUE;
12328         break;
12329
12330       default:
12331       case ImpossibleMove:
12332         if (appData.debugMode)
12333           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12334         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12335                 (forwardMostMove / 2) + 1,
12336                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12337         DisplayError(move, 0);
12338         done = TRUE;
12339         break;
12340     }
12341
12342     if (done) {
12343         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12344             DrawPosition(FALSE, boards[currentMove]);
12345             DisplayBothClocks();
12346             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12347               DisplayComment(currentMove - 1, commentList[currentMove]);
12348         }
12349         (void) StopLoadGameTimer();
12350         gameFileFP = NULL;
12351         cmailOldMove = forwardMostMove;
12352         return FALSE;
12353     } else {
12354         /* currentMoveString is set as a side-effect of yylex */
12355
12356         thinkOutput[0] = NULLCHAR;
12357         MakeMove(fromX, fromY, toX, toY, promoChar);
12358         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12359         currentMove = forwardMostMove;
12360         return TRUE;
12361     }
12362 }
12363
12364 /* Load the nth game from the given file */
12365 int
12366 LoadGameFromFile (char *filename, int n, char *title, int useList)
12367 {
12368     FILE *f;
12369     char buf[MSG_SIZ];
12370
12371     if (strcmp(filename, "-") == 0) {
12372         f = stdin;
12373         title = "stdin";
12374     } else {
12375         f = fopen(filename, "rb");
12376         if (f == NULL) {
12377           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12378             DisplayError(buf, errno);
12379             return FALSE;
12380         }
12381     }
12382     if (fseek(f, 0, 0) == -1) {
12383         /* f is not seekable; probably a pipe */
12384         useList = FALSE;
12385     }
12386     if (useList && n == 0) {
12387         int error = GameListBuild(f);
12388         if (error) {
12389             DisplayError(_("Cannot build game list"), error);
12390         } else if (!ListEmpty(&gameList) &&
12391                    ((ListGame *) gameList.tailPred)->number > 1) {
12392             GameListPopUp(f, title);
12393             return TRUE;
12394         }
12395         GameListDestroy();
12396         n = 1;
12397     }
12398     if (n == 0) n = 1;
12399     return LoadGame(f, n, title, FALSE);
12400 }
12401
12402
12403 void
12404 MakeRegisteredMove ()
12405 {
12406     int fromX, fromY, toX, toY;
12407     char promoChar;
12408     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12409         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12410           case CMAIL_MOVE:
12411           case CMAIL_DRAW:
12412             if (appData.debugMode)
12413               fprintf(debugFP, "Restoring %s for game %d\n",
12414                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12415
12416             thinkOutput[0] = NULLCHAR;
12417             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12418             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12419             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12420             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12421             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12422             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12423             MakeMove(fromX, fromY, toX, toY, promoChar);
12424             ShowMove(fromX, fromY, toX, toY);
12425
12426             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12427               case MT_NONE:
12428               case MT_CHECK:
12429                 break;
12430
12431               case MT_CHECKMATE:
12432               case MT_STAINMATE:
12433                 if (WhiteOnMove(currentMove)) {
12434                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12435                 } else {
12436                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12437                 }
12438                 break;
12439
12440               case MT_STALEMATE:
12441                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12442                 break;
12443             }
12444
12445             break;
12446
12447           case CMAIL_RESIGN:
12448             if (WhiteOnMove(currentMove)) {
12449                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12450             } else {
12451                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12452             }
12453             break;
12454
12455           case CMAIL_ACCEPT:
12456             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12457             break;
12458
12459           default:
12460             break;
12461         }
12462     }
12463
12464     return;
12465 }
12466
12467 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12468 int
12469 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12470 {
12471     int retVal;
12472
12473     if (gameNumber > nCmailGames) {
12474         DisplayError(_("No more games in this message"), 0);
12475         return FALSE;
12476     }
12477     if (f == lastLoadGameFP) {
12478         int offset = gameNumber - lastLoadGameNumber;
12479         if (offset == 0) {
12480             cmailMsg[0] = NULLCHAR;
12481             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12482                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12483                 nCmailMovesRegistered--;
12484             }
12485             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12486             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12487                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12488             }
12489         } else {
12490             if (! RegisterMove()) return FALSE;
12491         }
12492     }
12493
12494     retVal = LoadGame(f, gameNumber, title, useList);
12495
12496     /* Make move registered during previous look at this game, if any */
12497     MakeRegisteredMove();
12498
12499     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12500         commentList[currentMove]
12501           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12502         DisplayComment(currentMove - 1, commentList[currentMove]);
12503     }
12504
12505     return retVal;
12506 }
12507
12508 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12509 int
12510 ReloadGame (int offset)
12511 {
12512     int gameNumber = lastLoadGameNumber + offset;
12513     if (lastLoadGameFP == NULL) {
12514         DisplayError(_("No game has been loaded yet"), 0);
12515         return FALSE;
12516     }
12517     if (gameNumber <= 0) {
12518         DisplayError(_("Can't back up any further"), 0);
12519         return FALSE;
12520     }
12521     if (cmailMsgLoaded) {
12522         return CmailLoadGame(lastLoadGameFP, gameNumber,
12523                              lastLoadGameTitle, lastLoadGameUseList);
12524     } else {
12525         return LoadGame(lastLoadGameFP, gameNumber,
12526                         lastLoadGameTitle, lastLoadGameUseList);
12527     }
12528 }
12529
12530 int keys[EmptySquare+1];
12531
12532 int
12533 PositionMatches (Board b1, Board b2)
12534 {
12535     int r, f, sum=0;
12536     switch(appData.searchMode) {
12537         case 1: return CompareWithRights(b1, b2);
12538         case 2:
12539             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12540                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12541             }
12542             return TRUE;
12543         case 3:
12544             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12545               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12546                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12547             }
12548             return sum==0;
12549         case 4:
12550             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12551                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12552             }
12553             return sum==0;
12554     }
12555     return TRUE;
12556 }
12557
12558 #define Q_PROMO  4
12559 #define Q_EP     3
12560 #define Q_BCASTL 2
12561 #define Q_WCASTL 1
12562
12563 int pieceList[256], quickBoard[256];
12564 ChessSquare pieceType[256] = { EmptySquare };
12565 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12566 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12567 int soughtTotal, turn;
12568 Boolean epOK, flipSearch;
12569
12570 typedef struct {
12571     unsigned char piece, to;
12572 } Move;
12573
12574 #define DSIZE (250000)
12575
12576 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12577 Move *moveDatabase = initialSpace;
12578 unsigned int movePtr, dataSize = DSIZE;
12579
12580 int
12581 MakePieceList (Board board, int *counts)
12582 {
12583     int r, f, n=Q_PROMO, total=0;
12584     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12585     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12586         int sq = f + (r<<4);
12587         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12588             quickBoard[sq] = ++n;
12589             pieceList[n] = sq;
12590             pieceType[n] = board[r][f];
12591             counts[board[r][f]]++;
12592             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12593             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12594             total++;
12595         }
12596     }
12597     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12598     return total;
12599 }
12600
12601 void
12602 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12603 {
12604     int sq = fromX + (fromY<<4);
12605     int piece = quickBoard[sq], rook;
12606     quickBoard[sq] = 0;
12607     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12608     if(piece == pieceList[1] && fromY == toY) {
12609       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12610         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12611         moveDatabase[movePtr++].piece = Q_WCASTL;
12612         quickBoard[sq] = piece;
12613         piece = quickBoard[from]; quickBoard[from] = 0;
12614         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12615       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12616         quickBoard[sq] = 0; // remove Rook
12617         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12618         moveDatabase[movePtr++].piece = Q_WCASTL;
12619         quickBoard[sq] = pieceList[1]; // put King
12620         piece = rook;
12621         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12622       }
12623     } else
12624     if(piece == pieceList[2] && fromY == toY) {
12625       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12626         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12627         moveDatabase[movePtr++].piece = Q_BCASTL;
12628         quickBoard[sq] = piece;
12629         piece = quickBoard[from]; quickBoard[from] = 0;
12630         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12631       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12632         quickBoard[sq] = 0; // remove Rook
12633         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12634         moveDatabase[movePtr++].piece = Q_BCASTL;
12635         quickBoard[sq] = pieceList[2]; // put King
12636         piece = rook;
12637         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12638       }
12639     } else
12640     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12641         quickBoard[(fromY<<4)+toX] = 0;
12642         moveDatabase[movePtr].piece = Q_EP;
12643         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12644         moveDatabase[movePtr].to = sq;
12645     } else
12646     if(promoPiece != pieceType[piece]) {
12647         moveDatabase[movePtr++].piece = Q_PROMO;
12648         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12649     }
12650     moveDatabase[movePtr].piece = piece;
12651     quickBoard[sq] = piece;
12652     movePtr++;
12653 }
12654
12655 int
12656 PackGame (Board board)
12657 {
12658     Move *newSpace = NULL;
12659     moveDatabase[movePtr].piece = 0; // terminate previous game
12660     if(movePtr > dataSize) {
12661         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12662         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12663         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12664         if(newSpace) {
12665             int i;
12666             Move *p = moveDatabase, *q = newSpace;
12667             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12668             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12669             moveDatabase = newSpace;
12670         } else { // calloc failed, we must be out of memory. Too bad...
12671             dataSize = 0; // prevent calloc events for all subsequent games
12672             return 0;     // and signal this one isn't cached
12673         }
12674     }
12675     movePtr++;
12676     MakePieceList(board, counts);
12677     return movePtr;
12678 }
12679
12680 int
12681 QuickCompare (Board board, int *minCounts, int *maxCounts)
12682 {   // compare according to search mode
12683     int r, f;
12684     switch(appData.searchMode)
12685     {
12686       case 1: // exact position match
12687         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12688         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12689             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12690         }
12691         break;
12692       case 2: // can have extra material on empty squares
12693         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12694             if(board[r][f] == EmptySquare) continue;
12695             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12696         }
12697         break;
12698       case 3: // material with exact Pawn structure
12699         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12700             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12701             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12702         } // fall through to material comparison
12703       case 4: // exact material
12704         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12705         break;
12706       case 6: // material range with given imbalance
12707         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12708         // fall through to range comparison
12709       case 5: // material range
12710         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12711     }
12712     return TRUE;
12713 }
12714
12715 int
12716 QuickScan (Board board, Move *move)
12717 {   // reconstruct game,and compare all positions in it
12718     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12719     do {
12720         int piece = move->piece;
12721         int to = move->to, from = pieceList[piece];
12722         if(found < 0) { // if already found just scan to game end for final piece count
12723           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12724            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12725            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12726                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12727             ) {
12728             static int lastCounts[EmptySquare+1];
12729             int i;
12730             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12731             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12732           } else stretch = 0;
12733           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12734           if(found >= 0 && !appData.minPieces) return found;
12735         }
12736         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12737           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12738           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12739             piece = (++move)->piece;
12740             from = pieceList[piece];
12741             counts[pieceType[piece]]--;
12742             pieceType[piece] = (ChessSquare) move->to;
12743             counts[move->to]++;
12744           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12745             counts[pieceType[quickBoard[to]]]--;
12746             quickBoard[to] = 0; total--;
12747             move++;
12748             continue;
12749           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12750             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12751             from  = pieceList[piece]; // so this must be King
12752             quickBoard[from] = 0;
12753             pieceList[piece] = to;
12754             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12755             quickBoard[from] = 0; // rook
12756             quickBoard[to] = piece;
12757             to = move->to; piece = move->piece;
12758             goto aftercastle;
12759           }
12760         }
12761         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12762         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12763         quickBoard[from] = 0;
12764       aftercastle:
12765         quickBoard[to] = piece;
12766         pieceList[piece] = to;
12767         cnt++; turn ^= 3;
12768         move++;
12769     } while(1);
12770 }
12771
12772 void
12773 InitSearch ()
12774 {
12775     int r, f;
12776     flipSearch = FALSE;
12777     CopyBoard(soughtBoard, boards[currentMove]);
12778     soughtTotal = MakePieceList(soughtBoard, maxSought);
12779     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12780     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12781     CopyBoard(reverseBoard, boards[currentMove]);
12782     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12783         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12784         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12785         reverseBoard[r][f] = piece;
12786     }
12787     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12788     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12789     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12790                  || (boards[currentMove][CASTLING][2] == NoRights ||
12791                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12792                  && (boards[currentMove][CASTLING][5] == NoRights ||
12793                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12794       ) {
12795         flipSearch = TRUE;
12796         CopyBoard(flipBoard, soughtBoard);
12797         CopyBoard(rotateBoard, reverseBoard);
12798         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12799             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12800             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12801         }
12802     }
12803     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12804     if(appData.searchMode >= 5) {
12805         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12806         MakePieceList(soughtBoard, minSought);
12807         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12808     }
12809     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12810         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12811 }
12812
12813 GameInfo dummyInfo;
12814 static int creatingBook;
12815
12816 int
12817 GameContainsPosition (FILE *f, ListGame *lg)
12818 {
12819     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12820     int fromX, fromY, toX, toY;
12821     char promoChar;
12822     static int initDone=FALSE;
12823
12824     // weed out games based on numerical tag comparison
12825     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12826     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12827     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12828     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12829     if(!initDone) {
12830         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12831         initDone = TRUE;
12832     }
12833     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12834     else CopyBoard(boards[scratch], initialPosition); // default start position
12835     if(lg->moves) {
12836         turn = btm + 1;
12837         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12838         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12839     }
12840     if(btm) plyNr++;
12841     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12842     fseek(f, lg->offset, 0);
12843     yynewfile(f);
12844     while(1) {
12845         yyboardindex = scratch;
12846         quickFlag = plyNr+1;
12847         next = Myylex();
12848         quickFlag = 0;
12849         switch(next) {
12850             case PGNTag:
12851                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12852             default:
12853                 continue;
12854
12855             case XBoardGame:
12856             case GNUChessGame:
12857                 if(plyNr) return -1; // after we have seen moves, this is for new game
12858               continue;
12859
12860             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12861             case ImpossibleMove:
12862             case WhiteWins: // game ends here with these four
12863             case BlackWins:
12864             case GameIsDrawn:
12865             case GameUnfinished:
12866                 return -1;
12867
12868             case IllegalMove:
12869                 if(appData.testLegality) return -1;
12870             case WhiteCapturesEnPassant:
12871             case BlackCapturesEnPassant:
12872             case WhitePromotion:
12873             case BlackPromotion:
12874             case WhiteNonPromotion:
12875             case BlackNonPromotion:
12876             case NormalMove:
12877             case FirstLeg:
12878             case WhiteKingSideCastle:
12879             case WhiteQueenSideCastle:
12880             case BlackKingSideCastle:
12881             case BlackQueenSideCastle:
12882             case WhiteKingSideCastleWild:
12883             case WhiteQueenSideCastleWild:
12884             case BlackKingSideCastleWild:
12885             case BlackQueenSideCastleWild:
12886             case WhiteHSideCastleFR:
12887             case WhiteASideCastleFR:
12888             case BlackHSideCastleFR:
12889             case BlackASideCastleFR:
12890                 fromX = currentMoveString[0] - AAA;
12891                 fromY = currentMoveString[1] - ONE;
12892                 toX = currentMoveString[2] - AAA;
12893                 toY = currentMoveString[3] - ONE;
12894                 promoChar = currentMoveString[4];
12895                 break;
12896             case WhiteDrop:
12897             case BlackDrop:
12898                 fromX = next == WhiteDrop ?
12899                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12900                   (int) CharToPiece(ToLower(currentMoveString[0]));
12901                 fromY = DROP_RANK;
12902                 toX = currentMoveString[2] - AAA;
12903                 toY = currentMoveString[3] - ONE;
12904                 promoChar = 0;
12905                 break;
12906         }
12907         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12908         plyNr++;
12909         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12910         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12911         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12912         if(appData.findMirror) {
12913             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12914             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12915         }
12916     }
12917 }
12918
12919 /* Load the nth game from open file f */
12920 int
12921 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12922 {
12923     ChessMove cm;
12924     char buf[MSG_SIZ];
12925     int gn = gameNumber;
12926     ListGame *lg = NULL;
12927     int numPGNTags = 0;
12928     int err, pos = -1;
12929     GameMode oldGameMode;
12930     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12931     char oldName[MSG_SIZ];
12932
12933     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12934
12935     if (appData.debugMode)
12936         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12937
12938     if (gameMode == Training )
12939         SetTrainingModeOff();
12940
12941     oldGameMode = gameMode;
12942     if (gameMode != BeginningOfGame) {
12943       Reset(FALSE, TRUE);
12944     }
12945     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
12946
12947     gameFileFP = f;
12948     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12949         fclose(lastLoadGameFP);
12950     }
12951
12952     if (useList) {
12953         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12954
12955         if (lg) {
12956             fseek(f, lg->offset, 0);
12957             GameListHighlight(gameNumber);
12958             pos = lg->position;
12959             gn = 1;
12960         }
12961         else {
12962             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12963               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12964             else
12965             DisplayError(_("Game number out of range"), 0);
12966             return FALSE;
12967         }
12968     } else {
12969         GameListDestroy();
12970         if (fseek(f, 0, 0) == -1) {
12971             if (f == lastLoadGameFP ?
12972                 gameNumber == lastLoadGameNumber + 1 :
12973                 gameNumber == 1) {
12974                 gn = 1;
12975             } else {
12976                 DisplayError(_("Can't seek on game file"), 0);
12977                 return FALSE;
12978             }
12979         }
12980     }
12981     lastLoadGameFP = f;
12982     lastLoadGameNumber = gameNumber;
12983     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12984     lastLoadGameUseList = useList;
12985
12986     yynewfile(f);
12987
12988     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12989       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12990                 lg->gameInfo.black);
12991             DisplayTitle(buf);
12992     } else if (*title != NULLCHAR) {
12993         if (gameNumber > 1) {
12994           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12995             DisplayTitle(buf);
12996         } else {
12997             DisplayTitle(title);
12998         }
12999     }
13000
13001     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13002         gameMode = PlayFromGameFile;
13003         ModeHighlight();
13004     }
13005
13006     currentMove = forwardMostMove = backwardMostMove = 0;
13007     CopyBoard(boards[0], initialPosition);
13008     StopClocks();
13009
13010     /*
13011      * Skip the first gn-1 games in the file.
13012      * Also skip over anything that precedes an identifiable
13013      * start of game marker, to avoid being confused by
13014      * garbage at the start of the file.  Currently
13015      * recognized start of game markers are the move number "1",
13016      * the pattern "gnuchess .* game", the pattern
13017      * "^[#;%] [^ ]* game file", and a PGN tag block.
13018      * A game that starts with one of the latter two patterns
13019      * will also have a move number 1, possibly
13020      * following a position diagram.
13021      * 5-4-02: Let's try being more lenient and allowing a game to
13022      * start with an unnumbered move.  Does that break anything?
13023      */
13024     cm = lastLoadGameStart = EndOfFile;
13025     while (gn > 0) {
13026         yyboardindex = forwardMostMove;
13027         cm = (ChessMove) Myylex();
13028         switch (cm) {
13029           case EndOfFile:
13030             if (cmailMsgLoaded) {
13031                 nCmailGames = CMAIL_MAX_GAMES - gn;
13032             } else {
13033                 Reset(TRUE, TRUE);
13034                 DisplayError(_("Game not found in file"), 0);
13035             }
13036             return FALSE;
13037
13038           case GNUChessGame:
13039           case XBoardGame:
13040             gn--;
13041             lastLoadGameStart = cm;
13042             break;
13043
13044           case MoveNumberOne:
13045             switch (lastLoadGameStart) {
13046               case GNUChessGame:
13047               case XBoardGame:
13048               case PGNTag:
13049                 break;
13050               case MoveNumberOne:
13051               case EndOfFile:
13052                 gn--;           /* count this game */
13053                 lastLoadGameStart = cm;
13054                 break;
13055               default:
13056                 /* impossible */
13057                 break;
13058             }
13059             break;
13060
13061           case PGNTag:
13062             switch (lastLoadGameStart) {
13063               case GNUChessGame:
13064               case PGNTag:
13065               case MoveNumberOne:
13066               case EndOfFile:
13067                 gn--;           /* count this game */
13068                 lastLoadGameStart = cm;
13069                 break;
13070               case XBoardGame:
13071                 lastLoadGameStart = cm; /* game counted already */
13072                 break;
13073               default:
13074                 /* impossible */
13075                 break;
13076             }
13077             if (gn > 0) {
13078                 do {
13079                     yyboardindex = forwardMostMove;
13080                     cm = (ChessMove) Myylex();
13081                 } while (cm == PGNTag || cm == Comment);
13082             }
13083             break;
13084
13085           case WhiteWins:
13086           case BlackWins:
13087           case GameIsDrawn:
13088             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13089                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13090                     != CMAIL_OLD_RESULT) {
13091                     nCmailResults ++ ;
13092                     cmailResult[  CMAIL_MAX_GAMES
13093                                 - gn - 1] = CMAIL_OLD_RESULT;
13094                 }
13095             }
13096             break;
13097
13098           case NormalMove:
13099           case FirstLeg:
13100             /* Only a NormalMove can be at the start of a game
13101              * without a position diagram. */
13102             if (lastLoadGameStart == EndOfFile ) {
13103               gn--;
13104               lastLoadGameStart = MoveNumberOne;
13105             }
13106             break;
13107
13108           default:
13109             break;
13110         }
13111     }
13112
13113     if (appData.debugMode)
13114       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13115
13116     if (cm == XBoardGame) {
13117         /* Skip any header junk before position diagram and/or move 1 */
13118         for (;;) {
13119             yyboardindex = forwardMostMove;
13120             cm = (ChessMove) Myylex();
13121
13122             if (cm == EndOfFile ||
13123                 cm == GNUChessGame || cm == XBoardGame) {
13124                 /* Empty game; pretend end-of-file and handle later */
13125                 cm = EndOfFile;
13126                 break;
13127             }
13128
13129             if (cm == MoveNumberOne || cm == PositionDiagram ||
13130                 cm == PGNTag || cm == Comment)
13131               break;
13132         }
13133     } else if (cm == GNUChessGame) {
13134         if (gameInfo.event != NULL) {
13135             free(gameInfo.event);
13136         }
13137         gameInfo.event = StrSave(yy_text);
13138     }
13139
13140     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13141     while (cm == PGNTag) {
13142         if (appData.debugMode)
13143           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13144         err = ParsePGNTag(yy_text, &gameInfo);
13145         if (!err) numPGNTags++;
13146
13147         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13148         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13149             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13150             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13151             InitPosition(TRUE);
13152             oldVariant = gameInfo.variant;
13153             if (appData.debugMode)
13154               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13155         }
13156
13157
13158         if (gameInfo.fen != NULL) {
13159           Board initial_position;
13160           startedFromSetupPosition = TRUE;
13161           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13162             Reset(TRUE, TRUE);
13163             DisplayError(_("Bad FEN position in file"), 0);
13164             return FALSE;
13165           }
13166           CopyBoard(boards[0], initial_position);
13167           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13168             CopyBoard(initialPosition, initial_position);
13169           if (blackPlaysFirst) {
13170             currentMove = forwardMostMove = backwardMostMove = 1;
13171             CopyBoard(boards[1], initial_position);
13172             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13173             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13174             timeRemaining[0][1] = whiteTimeRemaining;
13175             timeRemaining[1][1] = blackTimeRemaining;
13176             if (commentList[0] != NULL) {
13177               commentList[1] = commentList[0];
13178               commentList[0] = NULL;
13179             }
13180           } else {
13181             currentMove = forwardMostMove = backwardMostMove = 0;
13182           }
13183           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13184           {   int i;
13185               initialRulePlies = FENrulePlies;
13186               for( i=0; i< nrCastlingRights; i++ )
13187                   initialRights[i] = initial_position[CASTLING][i];
13188           }
13189           yyboardindex = forwardMostMove;
13190           free(gameInfo.fen);
13191           gameInfo.fen = NULL;
13192         }
13193
13194         yyboardindex = forwardMostMove;
13195         cm = (ChessMove) Myylex();
13196
13197         /* Handle comments interspersed among the tags */
13198         while (cm == Comment) {
13199             char *p;
13200             if (appData.debugMode)
13201               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13202             p = yy_text;
13203             AppendComment(currentMove, p, FALSE);
13204             yyboardindex = forwardMostMove;
13205             cm = (ChessMove) Myylex();
13206         }
13207     }
13208
13209     /* don't rely on existence of Event tag since if game was
13210      * pasted from clipboard the Event tag may not exist
13211      */
13212     if (numPGNTags > 0){
13213         char *tags;
13214         if (gameInfo.variant == VariantNormal) {
13215           VariantClass v = StringToVariant(gameInfo.event);
13216           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13217           if(v < VariantShogi) gameInfo.variant = v;
13218         }
13219         if (!matchMode) {
13220           if( appData.autoDisplayTags ) {
13221             tags = PGNTags(&gameInfo);
13222             TagsPopUp(tags, CmailMsg());
13223             free(tags);
13224           }
13225         }
13226     } else {
13227         /* Make something up, but don't display it now */
13228         SetGameInfo();
13229         TagsPopDown();
13230     }
13231
13232     if (cm == PositionDiagram) {
13233         int i, j;
13234         char *p;
13235         Board initial_position;
13236
13237         if (appData.debugMode)
13238           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13239
13240         if (!startedFromSetupPosition) {
13241             p = yy_text;
13242             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13243               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13244                 switch (*p) {
13245                   case '{':
13246                   case '[':
13247                   case '-':
13248                   case ' ':
13249                   case '\t':
13250                   case '\n':
13251                   case '\r':
13252                     break;
13253                   default:
13254                     initial_position[i][j++] = CharToPiece(*p);
13255                     break;
13256                 }
13257             while (*p == ' ' || *p == '\t' ||
13258                    *p == '\n' || *p == '\r') p++;
13259
13260             if (strncmp(p, "black", strlen("black"))==0)
13261               blackPlaysFirst = TRUE;
13262             else
13263               blackPlaysFirst = FALSE;
13264             startedFromSetupPosition = TRUE;
13265
13266             CopyBoard(boards[0], initial_position);
13267             if (blackPlaysFirst) {
13268                 currentMove = forwardMostMove = backwardMostMove = 1;
13269                 CopyBoard(boards[1], initial_position);
13270                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13271                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13272                 timeRemaining[0][1] = whiteTimeRemaining;
13273                 timeRemaining[1][1] = blackTimeRemaining;
13274                 if (commentList[0] != NULL) {
13275                     commentList[1] = commentList[0];
13276                     commentList[0] = NULL;
13277                 }
13278             } else {
13279                 currentMove = forwardMostMove = backwardMostMove = 0;
13280             }
13281         }
13282         yyboardindex = forwardMostMove;
13283         cm = (ChessMove) Myylex();
13284     }
13285
13286   if(!creatingBook) {
13287     if (first.pr == NoProc) {
13288         StartChessProgram(&first);
13289     }
13290     InitChessProgram(&first, FALSE);
13291     if(gameInfo.variant == VariantUnknown && *oldName) {
13292         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13293         gameInfo.variant = v;
13294     }
13295     SendToProgram("force\n", &first);
13296     if (startedFromSetupPosition) {
13297         SendBoard(&first, forwardMostMove);
13298     if (appData.debugMode) {
13299         fprintf(debugFP, "Load Game\n");
13300     }
13301         DisplayBothClocks();
13302     }
13303   }
13304
13305     /* [HGM] server: flag to write setup moves in broadcast file as one */
13306     loadFlag = appData.suppressLoadMoves;
13307
13308     while (cm == Comment) {
13309         char *p;
13310         if (appData.debugMode)
13311           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13312         p = yy_text;
13313         AppendComment(currentMove, p, FALSE);
13314         yyboardindex = forwardMostMove;
13315         cm = (ChessMove) Myylex();
13316     }
13317
13318     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13319         cm == WhiteWins || cm == BlackWins ||
13320         cm == GameIsDrawn || cm == GameUnfinished) {
13321         DisplayMessage("", _("No moves in game"));
13322         if (cmailMsgLoaded) {
13323             if (appData.debugMode)
13324               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13325             ClearHighlights();
13326             flipView = FALSE;
13327         }
13328         DrawPosition(FALSE, boards[currentMove]);
13329         DisplayBothClocks();
13330         gameMode = EditGame;
13331         ModeHighlight();
13332         gameFileFP = NULL;
13333         cmailOldMove = 0;
13334         return TRUE;
13335     }
13336
13337     // [HGM] PV info: routine tests if comment empty
13338     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13339         DisplayComment(currentMove - 1, commentList[currentMove]);
13340     }
13341     if (!matchMode && appData.timeDelay != 0)
13342       DrawPosition(FALSE, boards[currentMove]);
13343
13344     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13345       programStats.ok_to_send = 1;
13346     }
13347
13348     /* if the first token after the PGN tags is a move
13349      * and not move number 1, retrieve it from the parser
13350      */
13351     if (cm != MoveNumberOne)
13352         LoadGameOneMove(cm);
13353
13354     /* load the remaining moves from the file */
13355     while (LoadGameOneMove(EndOfFile)) {
13356       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13357       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13358     }
13359
13360     /* rewind to the start of the game */
13361     currentMove = backwardMostMove;
13362
13363     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13364
13365     if (oldGameMode == AnalyzeFile) {
13366       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13367       AnalyzeFileEvent();
13368     } else
13369     if (oldGameMode == AnalyzeMode) {
13370       AnalyzeFileEvent();
13371     }
13372
13373     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13374         long int w, b; // [HGM] adjourn: restore saved clock times
13375         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13376         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13377             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13378             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13379         }
13380     }
13381
13382     if(creatingBook) return TRUE;
13383     if (!matchMode && pos > 0) {
13384         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13385     } else
13386     if (matchMode || appData.timeDelay == 0) {
13387       ToEndEvent();
13388     } else if (appData.timeDelay > 0) {
13389       AutoPlayGameLoop();
13390     }
13391
13392     if (appData.debugMode)
13393         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13394
13395     loadFlag = 0; /* [HGM] true game starts */
13396     return TRUE;
13397 }
13398
13399 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13400 int
13401 ReloadPosition (int offset)
13402 {
13403     int positionNumber = lastLoadPositionNumber + offset;
13404     if (lastLoadPositionFP == NULL) {
13405         DisplayError(_("No position has been loaded yet"), 0);
13406         return FALSE;
13407     }
13408     if (positionNumber <= 0) {
13409         DisplayError(_("Can't back up any further"), 0);
13410         return FALSE;
13411     }
13412     return LoadPosition(lastLoadPositionFP, positionNumber,
13413                         lastLoadPositionTitle);
13414 }
13415
13416 /* Load the nth position from the given file */
13417 int
13418 LoadPositionFromFile (char *filename, int n, char *title)
13419 {
13420     FILE *f;
13421     char buf[MSG_SIZ];
13422
13423     if (strcmp(filename, "-") == 0) {
13424         return LoadPosition(stdin, n, "stdin");
13425     } else {
13426         f = fopen(filename, "rb");
13427         if (f == NULL) {
13428             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13429             DisplayError(buf, errno);
13430             return FALSE;
13431         } else {
13432             return LoadPosition(f, n, title);
13433         }
13434     }
13435 }
13436
13437 /* Load the nth position from the given open file, and close it */
13438 int
13439 LoadPosition (FILE *f, int positionNumber, char *title)
13440 {
13441     char *p, line[MSG_SIZ];
13442     Board initial_position;
13443     int i, j, fenMode, pn;
13444
13445     if (gameMode == Training )
13446         SetTrainingModeOff();
13447
13448     if (gameMode != BeginningOfGame) {
13449         Reset(FALSE, TRUE);
13450     }
13451     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13452         fclose(lastLoadPositionFP);
13453     }
13454     if (positionNumber == 0) positionNumber = 1;
13455     lastLoadPositionFP = f;
13456     lastLoadPositionNumber = positionNumber;
13457     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13458     if (first.pr == NoProc && !appData.noChessProgram) {
13459       StartChessProgram(&first);
13460       InitChessProgram(&first, FALSE);
13461     }
13462     pn = positionNumber;
13463     if (positionNumber < 0) {
13464         /* Negative position number means to seek to that byte offset */
13465         if (fseek(f, -positionNumber, 0) == -1) {
13466             DisplayError(_("Can't seek on position file"), 0);
13467             return FALSE;
13468         };
13469         pn = 1;
13470     } else {
13471         if (fseek(f, 0, 0) == -1) {
13472             if (f == lastLoadPositionFP ?
13473                 positionNumber == lastLoadPositionNumber + 1 :
13474                 positionNumber == 1) {
13475                 pn = 1;
13476             } else {
13477                 DisplayError(_("Can't seek on position file"), 0);
13478                 return FALSE;
13479             }
13480         }
13481     }
13482     /* See if this file is FEN or old-style xboard */
13483     if (fgets(line, MSG_SIZ, f) == NULL) {
13484         DisplayError(_("Position not found in file"), 0);
13485         return FALSE;
13486     }
13487     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13488     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13489
13490     if (pn >= 2) {
13491         if (fenMode || line[0] == '#') pn--;
13492         while (pn > 0) {
13493             /* skip positions before number pn */
13494             if (fgets(line, MSG_SIZ, f) == NULL) {
13495                 Reset(TRUE, TRUE);
13496                 DisplayError(_("Position not found in file"), 0);
13497                 return FALSE;
13498             }
13499             if (fenMode || line[0] == '#') pn--;
13500         }
13501     }
13502
13503     if (fenMode) {
13504         char *p;
13505         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13506             DisplayError(_("Bad FEN position in file"), 0);
13507             return FALSE;
13508         }
13509         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13510             sscanf(p+3, "%s", bestMove);
13511         } else *bestMove = NULLCHAR;
13512     } else {
13513         (void) fgets(line, MSG_SIZ, f);
13514         (void) fgets(line, MSG_SIZ, f);
13515
13516         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13517             (void) fgets(line, MSG_SIZ, f);
13518             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13519                 if (*p == ' ')
13520                   continue;
13521                 initial_position[i][j++] = CharToPiece(*p);
13522             }
13523         }
13524
13525         blackPlaysFirst = FALSE;
13526         if (!feof(f)) {
13527             (void) fgets(line, MSG_SIZ, f);
13528             if (strncmp(line, "black", strlen("black"))==0)
13529               blackPlaysFirst = TRUE;
13530         }
13531     }
13532     startedFromSetupPosition = TRUE;
13533
13534     CopyBoard(boards[0], initial_position);
13535     if (blackPlaysFirst) {
13536         currentMove = forwardMostMove = backwardMostMove = 1;
13537         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13538         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13539         CopyBoard(boards[1], initial_position);
13540         DisplayMessage("", _("Black to play"));
13541     } else {
13542         currentMove = forwardMostMove = backwardMostMove = 0;
13543         DisplayMessage("", _("White to play"));
13544     }
13545     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13546     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13547         SendToProgram("force\n", &first);
13548         SendBoard(&first, forwardMostMove);
13549     }
13550     if (appData.debugMode) {
13551 int i, j;
13552   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13553   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13554         fprintf(debugFP, "Load Position\n");
13555     }
13556
13557     if (positionNumber > 1) {
13558       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13559         DisplayTitle(line);
13560     } else {
13561         DisplayTitle(title);
13562     }
13563     gameMode = EditGame;
13564     ModeHighlight();
13565     ResetClocks();
13566     timeRemaining[0][1] = whiteTimeRemaining;
13567     timeRemaining[1][1] = blackTimeRemaining;
13568     DrawPosition(FALSE, boards[currentMove]);
13569
13570     return TRUE;
13571 }
13572
13573
13574 void
13575 CopyPlayerNameIntoFileName (char **dest, char *src)
13576 {
13577     while (*src != NULLCHAR && *src != ',') {
13578         if (*src == ' ') {
13579             *(*dest)++ = '_';
13580             src++;
13581         } else {
13582             *(*dest)++ = *src++;
13583         }
13584     }
13585 }
13586
13587 char *
13588 DefaultFileName (char *ext)
13589 {
13590     static char def[MSG_SIZ];
13591     char *p;
13592
13593     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13594         p = def;
13595         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13596         *p++ = '-';
13597         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13598         *p++ = '.';
13599         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13600     } else {
13601         def[0] = NULLCHAR;
13602     }
13603     return def;
13604 }
13605
13606 /* Save the current game to the given file */
13607 int
13608 SaveGameToFile (char *filename, int append)
13609 {
13610     FILE *f;
13611     char buf[MSG_SIZ];
13612     int result, i, t,tot=0;
13613
13614     if (strcmp(filename, "-") == 0) {
13615         return SaveGame(stdout, 0, NULL);
13616     } else {
13617         for(i=0; i<10; i++) { // upto 10 tries
13618              f = fopen(filename, append ? "a" : "w");
13619              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13620              if(f || errno != 13) break;
13621              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13622              tot += t;
13623         }
13624         if (f == NULL) {
13625             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13626             DisplayError(buf, errno);
13627             return FALSE;
13628         } else {
13629             safeStrCpy(buf, lastMsg, MSG_SIZ);
13630             DisplayMessage(_("Waiting for access to save file"), "");
13631             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13632             DisplayMessage(_("Saving game"), "");
13633             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13634             result = SaveGame(f, 0, NULL);
13635             DisplayMessage(buf, "");
13636             return result;
13637         }
13638     }
13639 }
13640
13641 char *
13642 SavePart (char *str)
13643 {
13644     static char buf[MSG_SIZ];
13645     char *p;
13646
13647     p = strchr(str, ' ');
13648     if (p == NULL) return str;
13649     strncpy(buf, str, p - str);
13650     buf[p - str] = NULLCHAR;
13651     return buf;
13652 }
13653
13654 #define PGN_MAX_LINE 75
13655
13656 #define PGN_SIDE_WHITE  0
13657 #define PGN_SIDE_BLACK  1
13658
13659 static int
13660 FindFirstMoveOutOfBook (int side)
13661 {
13662     int result = -1;
13663
13664     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13665         int index = backwardMostMove;
13666         int has_book_hit = 0;
13667
13668         if( (index % 2) != side ) {
13669             index++;
13670         }
13671
13672         while( index < forwardMostMove ) {
13673             /* Check to see if engine is in book */
13674             int depth = pvInfoList[index].depth;
13675             int score = pvInfoList[index].score;
13676             int in_book = 0;
13677
13678             if( depth <= 2 ) {
13679                 in_book = 1;
13680             }
13681             else if( score == 0 && depth == 63 ) {
13682                 in_book = 1; /* Zappa */
13683             }
13684             else if( score == 2 && depth == 99 ) {
13685                 in_book = 1; /* Abrok */
13686             }
13687
13688             has_book_hit += in_book;
13689
13690             if( ! in_book ) {
13691                 result = index;
13692
13693                 break;
13694             }
13695
13696             index += 2;
13697         }
13698     }
13699
13700     return result;
13701 }
13702
13703 void
13704 GetOutOfBookInfo (char * buf)
13705 {
13706     int oob[2];
13707     int i;
13708     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13709
13710     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13711     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13712
13713     *buf = '\0';
13714
13715     if( oob[0] >= 0 || oob[1] >= 0 ) {
13716         for( i=0; i<2; i++ ) {
13717             int idx = oob[i];
13718
13719             if( idx >= 0 ) {
13720                 if( i > 0 && oob[0] >= 0 ) {
13721                     strcat( buf, "   " );
13722                 }
13723
13724                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13725                 sprintf( buf+strlen(buf), "%s%.2f",
13726                     pvInfoList[idx].score >= 0 ? "+" : "",
13727                     pvInfoList[idx].score / 100.0 );
13728             }
13729         }
13730     }
13731 }
13732
13733 /* Save game in PGN style */
13734 static void
13735 SaveGamePGN2 (FILE *f)
13736 {
13737     int i, offset, linelen, newblock;
13738 //    char *movetext;
13739     char numtext[32];
13740     int movelen, numlen, blank;
13741     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13742
13743     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13744
13745     PrintPGNTags(f, &gameInfo);
13746
13747     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13748
13749     if (backwardMostMove > 0 || startedFromSetupPosition) {
13750         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13751         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13752         fprintf(f, "\n{--------------\n");
13753         PrintPosition(f, backwardMostMove);
13754         fprintf(f, "--------------}\n");
13755         free(fen);
13756     }
13757     else {
13758         /* [AS] Out of book annotation */
13759         if( appData.saveOutOfBookInfo ) {
13760             char buf[64];
13761
13762             GetOutOfBookInfo( buf );
13763
13764             if( buf[0] != '\0' ) {
13765                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13766             }
13767         }
13768
13769         fprintf(f, "\n");
13770     }
13771
13772     i = backwardMostMove;
13773     linelen = 0;
13774     newblock = TRUE;
13775
13776     while (i < forwardMostMove) {
13777         /* Print comments preceding this move */
13778         if (commentList[i] != NULL) {
13779             if (linelen > 0) fprintf(f, "\n");
13780             fprintf(f, "%s", commentList[i]);
13781             linelen = 0;
13782             newblock = TRUE;
13783         }
13784
13785         /* Format move number */
13786         if ((i % 2) == 0)
13787           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13788         else
13789           if (newblock)
13790             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13791           else
13792             numtext[0] = NULLCHAR;
13793
13794         numlen = strlen(numtext);
13795         newblock = FALSE;
13796
13797         /* Print move number */
13798         blank = linelen > 0 && numlen > 0;
13799         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13800             fprintf(f, "\n");
13801             linelen = 0;
13802             blank = 0;
13803         }
13804         if (blank) {
13805             fprintf(f, " ");
13806             linelen++;
13807         }
13808         fprintf(f, "%s", numtext);
13809         linelen += numlen;
13810
13811         /* Get move */
13812         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13813         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13814
13815         /* Print move */
13816         blank = linelen > 0 && movelen > 0;
13817         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13818             fprintf(f, "\n");
13819             linelen = 0;
13820             blank = 0;
13821         }
13822         if (blank) {
13823             fprintf(f, " ");
13824             linelen++;
13825         }
13826         fprintf(f, "%s", move_buffer);
13827         linelen += movelen;
13828
13829         /* [AS] Add PV info if present */
13830         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13831             /* [HGM] add time */
13832             char buf[MSG_SIZ]; int seconds;
13833
13834             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13835
13836             if( seconds <= 0)
13837               buf[0] = 0;
13838             else
13839               if( seconds < 30 )
13840                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13841               else
13842                 {
13843                   seconds = (seconds + 4)/10; // round to full seconds
13844                   if( seconds < 60 )
13845                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13846                   else
13847                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13848                 }
13849
13850             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13851                       pvInfoList[i].score >= 0 ? "+" : "",
13852                       pvInfoList[i].score / 100.0,
13853                       pvInfoList[i].depth,
13854                       buf );
13855
13856             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13857
13858             /* Print score/depth */
13859             blank = linelen > 0 && movelen > 0;
13860             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13861                 fprintf(f, "\n");
13862                 linelen = 0;
13863                 blank = 0;
13864             }
13865             if (blank) {
13866                 fprintf(f, " ");
13867                 linelen++;
13868             }
13869             fprintf(f, "%s", move_buffer);
13870             linelen += movelen;
13871         }
13872
13873         i++;
13874     }
13875
13876     /* Start a new line */
13877     if (linelen > 0) fprintf(f, "\n");
13878
13879     /* Print comments after last move */
13880     if (commentList[i] != NULL) {
13881         fprintf(f, "%s\n", commentList[i]);
13882     }
13883
13884     /* Print result */
13885     if (gameInfo.resultDetails != NULL &&
13886         gameInfo.resultDetails[0] != NULLCHAR) {
13887         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13888         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13889            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13890             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13891         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13892     } else {
13893         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13894     }
13895 }
13896
13897 /* Save game in PGN style and close the file */
13898 int
13899 SaveGamePGN (FILE *f)
13900 {
13901     SaveGamePGN2(f);
13902     fclose(f);
13903     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13904     return TRUE;
13905 }
13906
13907 /* Save game in old style and close the file */
13908 int
13909 SaveGameOldStyle (FILE *f)
13910 {
13911     int i, offset;
13912     time_t tm;
13913
13914     tm = time((time_t *) NULL);
13915
13916     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13917     PrintOpponents(f);
13918
13919     if (backwardMostMove > 0 || startedFromSetupPosition) {
13920         fprintf(f, "\n[--------------\n");
13921         PrintPosition(f, backwardMostMove);
13922         fprintf(f, "--------------]\n");
13923     } else {
13924         fprintf(f, "\n");
13925     }
13926
13927     i = backwardMostMove;
13928     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13929
13930     while (i < forwardMostMove) {
13931         if (commentList[i] != NULL) {
13932             fprintf(f, "[%s]\n", commentList[i]);
13933         }
13934
13935         if ((i % 2) == 1) {
13936             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13937             i++;
13938         } else {
13939             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13940             i++;
13941             if (commentList[i] != NULL) {
13942                 fprintf(f, "\n");
13943                 continue;
13944             }
13945             if (i >= forwardMostMove) {
13946                 fprintf(f, "\n");
13947                 break;
13948             }
13949             fprintf(f, "%s\n", parseList[i]);
13950             i++;
13951         }
13952     }
13953
13954     if (commentList[i] != NULL) {
13955         fprintf(f, "[%s]\n", commentList[i]);
13956     }
13957
13958     /* This isn't really the old style, but it's close enough */
13959     if (gameInfo.resultDetails != NULL &&
13960         gameInfo.resultDetails[0] != NULLCHAR) {
13961         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13962                 gameInfo.resultDetails);
13963     } else {
13964         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13965     }
13966
13967     fclose(f);
13968     return TRUE;
13969 }
13970
13971 /* Save the current game to open file f and close the file */
13972 int
13973 SaveGame (FILE *f, int dummy, char *dummy2)
13974 {
13975     if (gameMode == EditPosition) EditPositionDone(TRUE);
13976     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13977     if (appData.oldSaveStyle)
13978       return SaveGameOldStyle(f);
13979     else
13980       return SaveGamePGN(f);
13981 }
13982
13983 /* Save the current position to the given file */
13984 int
13985 SavePositionToFile (char *filename)
13986 {
13987     FILE *f;
13988     char buf[MSG_SIZ];
13989
13990     if (strcmp(filename, "-") == 0) {
13991         return SavePosition(stdout, 0, NULL);
13992     } else {
13993         f = fopen(filename, "a");
13994         if (f == NULL) {
13995             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13996             DisplayError(buf, errno);
13997             return FALSE;
13998         } else {
13999             safeStrCpy(buf, lastMsg, MSG_SIZ);
14000             DisplayMessage(_("Waiting for access to save file"), "");
14001             flock(fileno(f), LOCK_EX); // [HGM] lock
14002             DisplayMessage(_("Saving position"), "");
14003             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14004             SavePosition(f, 0, NULL);
14005             DisplayMessage(buf, "");
14006             return TRUE;
14007         }
14008     }
14009 }
14010
14011 /* Save the current position to the given open file and close the file */
14012 int
14013 SavePosition (FILE *f, int dummy, char *dummy2)
14014 {
14015     time_t tm;
14016     char *fen;
14017
14018     if (gameMode == EditPosition) EditPositionDone(TRUE);
14019     if (appData.oldSaveStyle) {
14020         tm = time((time_t *) NULL);
14021
14022         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14023         PrintOpponents(f);
14024         fprintf(f, "[--------------\n");
14025         PrintPosition(f, currentMove);
14026         fprintf(f, "--------------]\n");
14027     } else {
14028         fen = PositionToFEN(currentMove, NULL, 1);
14029         fprintf(f, "%s\n", fen);
14030         free(fen);
14031     }
14032     fclose(f);
14033     return TRUE;
14034 }
14035
14036 void
14037 ReloadCmailMsgEvent (int unregister)
14038 {
14039 #if !WIN32
14040     static char *inFilename = NULL;
14041     static char *outFilename;
14042     int i;
14043     struct stat inbuf, outbuf;
14044     int status;
14045
14046     /* Any registered moves are unregistered if unregister is set, */
14047     /* i.e. invoked by the signal handler */
14048     if (unregister) {
14049         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14050             cmailMoveRegistered[i] = FALSE;
14051             if (cmailCommentList[i] != NULL) {
14052                 free(cmailCommentList[i]);
14053                 cmailCommentList[i] = NULL;
14054             }
14055         }
14056         nCmailMovesRegistered = 0;
14057     }
14058
14059     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14060         cmailResult[i] = CMAIL_NOT_RESULT;
14061     }
14062     nCmailResults = 0;
14063
14064     if (inFilename == NULL) {
14065         /* Because the filenames are static they only get malloced once  */
14066         /* and they never get freed                                      */
14067         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14068         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14069
14070         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14071         sprintf(outFilename, "%s.out", appData.cmailGameName);
14072     }
14073
14074     status = stat(outFilename, &outbuf);
14075     if (status < 0) {
14076         cmailMailedMove = FALSE;
14077     } else {
14078         status = stat(inFilename, &inbuf);
14079         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14080     }
14081
14082     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14083        counts the games, notes how each one terminated, etc.
14084
14085        It would be nice to remove this kludge and instead gather all
14086        the information while building the game list.  (And to keep it
14087        in the game list nodes instead of having a bunch of fixed-size
14088        parallel arrays.)  Note this will require getting each game's
14089        termination from the PGN tags, as the game list builder does
14090        not process the game moves.  --mann
14091        */
14092     cmailMsgLoaded = TRUE;
14093     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14094
14095     /* Load first game in the file or popup game menu */
14096     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14097
14098 #endif /* !WIN32 */
14099     return;
14100 }
14101
14102 int
14103 RegisterMove ()
14104 {
14105     FILE *f;
14106     char string[MSG_SIZ];
14107
14108     if (   cmailMailedMove
14109         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14110         return TRUE;            /* Allow free viewing  */
14111     }
14112
14113     /* Unregister move to ensure that we don't leave RegisterMove        */
14114     /* with the move registered when the conditions for registering no   */
14115     /* longer hold                                                       */
14116     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14117         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14118         nCmailMovesRegistered --;
14119
14120         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14121           {
14122               free(cmailCommentList[lastLoadGameNumber - 1]);
14123               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14124           }
14125     }
14126
14127     if (cmailOldMove == -1) {
14128         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14129         return FALSE;
14130     }
14131
14132     if (currentMove > cmailOldMove + 1) {
14133         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14134         return FALSE;
14135     }
14136
14137     if (currentMove < cmailOldMove) {
14138         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14139         return FALSE;
14140     }
14141
14142     if (forwardMostMove > currentMove) {
14143         /* Silently truncate extra moves */
14144         TruncateGame();
14145     }
14146
14147     if (   (currentMove == cmailOldMove + 1)
14148         || (   (currentMove == cmailOldMove)
14149             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14150                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14151         if (gameInfo.result != GameUnfinished) {
14152             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14153         }
14154
14155         if (commentList[currentMove] != NULL) {
14156             cmailCommentList[lastLoadGameNumber - 1]
14157               = StrSave(commentList[currentMove]);
14158         }
14159         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14160
14161         if (appData.debugMode)
14162           fprintf(debugFP, "Saving %s for game %d\n",
14163                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14164
14165         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14166
14167         f = fopen(string, "w");
14168         if (appData.oldSaveStyle) {
14169             SaveGameOldStyle(f); /* also closes the file */
14170
14171             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14172             f = fopen(string, "w");
14173             SavePosition(f, 0, NULL); /* also closes the file */
14174         } else {
14175             fprintf(f, "{--------------\n");
14176             PrintPosition(f, currentMove);
14177             fprintf(f, "--------------}\n\n");
14178
14179             SaveGame(f, 0, NULL); /* also closes the file*/
14180         }
14181
14182         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14183         nCmailMovesRegistered ++;
14184     } else if (nCmailGames == 1) {
14185         DisplayError(_("You have not made a move yet"), 0);
14186         return FALSE;
14187     }
14188
14189     return TRUE;
14190 }
14191
14192 void
14193 MailMoveEvent ()
14194 {
14195 #if !WIN32
14196     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14197     FILE *commandOutput;
14198     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14199     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14200     int nBuffers;
14201     int i;
14202     int archived;
14203     char *arcDir;
14204
14205     if (! cmailMsgLoaded) {
14206         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14207         return;
14208     }
14209
14210     if (nCmailGames == nCmailResults) {
14211         DisplayError(_("No unfinished games"), 0);
14212         return;
14213     }
14214
14215 #if CMAIL_PROHIBIT_REMAIL
14216     if (cmailMailedMove) {
14217       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);
14218         DisplayError(msg, 0);
14219         return;
14220     }
14221 #endif
14222
14223     if (! (cmailMailedMove || RegisterMove())) return;
14224
14225     if (   cmailMailedMove
14226         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14227       snprintf(string, MSG_SIZ, partCommandString,
14228                appData.debugMode ? " -v" : "", appData.cmailGameName);
14229         commandOutput = popen(string, "r");
14230
14231         if (commandOutput == NULL) {
14232             DisplayError(_("Failed to invoke cmail"), 0);
14233         } else {
14234             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14235                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14236             }
14237             if (nBuffers > 1) {
14238                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14239                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14240                 nBytes = MSG_SIZ - 1;
14241             } else {
14242                 (void) memcpy(msg, buffer, nBytes);
14243             }
14244             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14245
14246             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14247                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14248
14249                 archived = TRUE;
14250                 for (i = 0; i < nCmailGames; i ++) {
14251                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14252                         archived = FALSE;
14253                     }
14254                 }
14255                 if (   archived
14256                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14257                         != NULL)) {
14258                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14259                            arcDir,
14260                            appData.cmailGameName,
14261                            gameInfo.date);
14262                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14263                     cmailMsgLoaded = FALSE;
14264                 }
14265             }
14266
14267             DisplayInformation(msg);
14268             pclose(commandOutput);
14269         }
14270     } else {
14271         if ((*cmailMsg) != '\0') {
14272             DisplayInformation(cmailMsg);
14273         }
14274     }
14275
14276     return;
14277 #endif /* !WIN32 */
14278 }
14279
14280 char *
14281 CmailMsg ()
14282 {
14283 #if WIN32
14284     return NULL;
14285 #else
14286     int  prependComma = 0;
14287     char number[5];
14288     char string[MSG_SIZ];       /* Space for game-list */
14289     int  i;
14290
14291     if (!cmailMsgLoaded) return "";
14292
14293     if (cmailMailedMove) {
14294       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14295     } else {
14296         /* Create a list of games left */
14297       snprintf(string, MSG_SIZ, "[");
14298         for (i = 0; i < nCmailGames; i ++) {
14299             if (! (   cmailMoveRegistered[i]
14300                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14301                 if (prependComma) {
14302                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14303                 } else {
14304                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14305                     prependComma = 1;
14306                 }
14307
14308                 strcat(string, number);
14309             }
14310         }
14311         strcat(string, "]");
14312
14313         if (nCmailMovesRegistered + nCmailResults == 0) {
14314             switch (nCmailGames) {
14315               case 1:
14316                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14317                 break;
14318
14319               case 2:
14320                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14321                 break;
14322
14323               default:
14324                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14325                          nCmailGames);
14326                 break;
14327             }
14328         } else {
14329             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14330               case 1:
14331                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14332                          string);
14333                 break;
14334
14335               case 0:
14336                 if (nCmailResults == nCmailGames) {
14337                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14338                 } else {
14339                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14340                 }
14341                 break;
14342
14343               default:
14344                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14345                          string);
14346             }
14347         }
14348     }
14349     return cmailMsg;
14350 #endif /* WIN32 */
14351 }
14352
14353 void
14354 ResetGameEvent ()
14355 {
14356     if (gameMode == Training)
14357       SetTrainingModeOff();
14358
14359     Reset(TRUE, TRUE);
14360     cmailMsgLoaded = FALSE;
14361     if (appData.icsActive) {
14362       SendToICS(ics_prefix);
14363       SendToICS("refresh\n");
14364     }
14365 }
14366
14367 void
14368 ExitEvent (int status)
14369 {
14370     exiting++;
14371     if (exiting > 2) {
14372       /* Give up on clean exit */
14373       exit(status);
14374     }
14375     if (exiting > 1) {
14376       /* Keep trying for clean exit */
14377       return;
14378     }
14379
14380     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14381     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14382
14383     if (telnetISR != NULL) {
14384       RemoveInputSource(telnetISR);
14385     }
14386     if (icsPR != NoProc) {
14387       DestroyChildProcess(icsPR, TRUE);
14388     }
14389
14390     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14391     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14392
14393     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14394     /* make sure this other one finishes before killing it!                  */
14395     if(endingGame) { int count = 0;
14396         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14397         while(endingGame && count++ < 10) DoSleep(1);
14398         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14399     }
14400
14401     /* Kill off chess programs */
14402     if (first.pr != NoProc) {
14403         ExitAnalyzeMode();
14404
14405         DoSleep( appData.delayBeforeQuit );
14406         SendToProgram("quit\n", &first);
14407         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14408     }
14409     if (second.pr != NoProc) {
14410         DoSleep( appData.delayBeforeQuit );
14411         SendToProgram("quit\n", &second);
14412         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14413     }
14414     if (first.isr != NULL) {
14415         RemoveInputSource(first.isr);
14416     }
14417     if (second.isr != NULL) {
14418         RemoveInputSource(second.isr);
14419     }
14420
14421     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14422     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14423
14424     ShutDownFrontEnd();
14425     exit(status);
14426 }
14427
14428 void
14429 PauseEngine (ChessProgramState *cps)
14430 {
14431     SendToProgram("pause\n", cps);
14432     cps->pause = 2;
14433 }
14434
14435 void
14436 UnPauseEngine (ChessProgramState *cps)
14437 {
14438     SendToProgram("resume\n", cps);
14439     cps->pause = 1;
14440 }
14441
14442 void
14443 PauseEvent ()
14444 {
14445     if (appData.debugMode)
14446         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14447     if (pausing) {
14448         pausing = FALSE;
14449         ModeHighlight();
14450         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14451             StartClocks();
14452             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14453                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14454                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14455             }
14456             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14457             HandleMachineMove(stashedInputMove, stalledEngine);
14458             stalledEngine = NULL;
14459             return;
14460         }
14461         if (gameMode == MachinePlaysWhite ||
14462             gameMode == TwoMachinesPlay   ||
14463             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14464             if(first.pause)  UnPauseEngine(&first);
14465             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14466             if(second.pause) UnPauseEngine(&second);
14467             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14468             StartClocks();
14469         } else {
14470             DisplayBothClocks();
14471         }
14472         if (gameMode == PlayFromGameFile) {
14473             if (appData.timeDelay >= 0)
14474                 AutoPlayGameLoop();
14475         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14476             Reset(FALSE, TRUE);
14477             SendToICS(ics_prefix);
14478             SendToICS("refresh\n");
14479         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14480             ForwardInner(forwardMostMove);
14481         }
14482         pauseExamInvalid = FALSE;
14483     } else {
14484         switch (gameMode) {
14485           default:
14486             return;
14487           case IcsExamining:
14488             pauseExamForwardMostMove = forwardMostMove;
14489             pauseExamInvalid = FALSE;
14490             /* fall through */
14491           case IcsObserving:
14492           case IcsPlayingWhite:
14493           case IcsPlayingBlack:
14494             pausing = TRUE;
14495             ModeHighlight();
14496             return;
14497           case PlayFromGameFile:
14498             (void) StopLoadGameTimer();
14499             pausing = TRUE;
14500             ModeHighlight();
14501             break;
14502           case BeginningOfGame:
14503             if (appData.icsActive) return;
14504             /* else fall through */
14505           case MachinePlaysWhite:
14506           case MachinePlaysBlack:
14507           case TwoMachinesPlay:
14508             if (forwardMostMove == 0)
14509               return;           /* don't pause if no one has moved */
14510             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14511                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14512                 if(onMove->pause) {           // thinking engine can be paused
14513                     PauseEngine(onMove);      // do it
14514                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14515                         PauseEngine(onMove->other);
14516                     else
14517                         SendToProgram("easy\n", onMove->other);
14518                     StopClocks();
14519                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14520             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14521                 if(first.pause) {
14522                     PauseEngine(&first);
14523                     StopClocks();
14524                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14525             } else { // human on move, pause pondering by either method
14526                 if(first.pause)
14527                     PauseEngine(&first);
14528                 else if(appData.ponderNextMove)
14529                     SendToProgram("easy\n", &first);
14530                 StopClocks();
14531             }
14532             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14533           case AnalyzeMode:
14534             pausing = TRUE;
14535             ModeHighlight();
14536             break;
14537         }
14538     }
14539 }
14540
14541 void
14542 EditCommentEvent ()
14543 {
14544     char title[MSG_SIZ];
14545
14546     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14547       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14548     } else {
14549       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14550                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14551                parseList[currentMove - 1]);
14552     }
14553
14554     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14555 }
14556
14557
14558 void
14559 EditTagsEvent ()
14560 {
14561     char *tags = PGNTags(&gameInfo);
14562     bookUp = FALSE;
14563     EditTagsPopUp(tags, NULL);
14564     free(tags);
14565 }
14566
14567 void
14568 ToggleSecond ()
14569 {
14570   if(second.analyzing) {
14571     SendToProgram("exit\n", &second);
14572     second.analyzing = FALSE;
14573   } else {
14574     if (second.pr == NoProc) StartChessProgram(&second);
14575     InitChessProgram(&second, FALSE);
14576     FeedMovesToProgram(&second, currentMove);
14577
14578     SendToProgram("analyze\n", &second);
14579     second.analyzing = TRUE;
14580   }
14581 }
14582
14583 /* Toggle ShowThinking */
14584 void
14585 ToggleShowThinking()
14586 {
14587   appData.showThinking = !appData.showThinking;
14588   ShowThinkingEvent();
14589 }
14590
14591 int
14592 AnalyzeModeEvent ()
14593 {
14594     char buf[MSG_SIZ];
14595
14596     if (!first.analysisSupport) {
14597       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14598       DisplayError(buf, 0);
14599       return 0;
14600     }
14601     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14602     if (appData.icsActive) {
14603         if (gameMode != IcsObserving) {
14604           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14605             DisplayError(buf, 0);
14606             /* secure check */
14607             if (appData.icsEngineAnalyze) {
14608                 if (appData.debugMode)
14609                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14610                 ExitAnalyzeMode();
14611                 ModeHighlight();
14612             }
14613             return 0;
14614         }
14615         /* if enable, user wants to disable icsEngineAnalyze */
14616         if (appData.icsEngineAnalyze) {
14617                 ExitAnalyzeMode();
14618                 ModeHighlight();
14619                 return 0;
14620         }
14621         appData.icsEngineAnalyze = TRUE;
14622         if (appData.debugMode)
14623             fprintf(debugFP, "ICS engine analyze starting... \n");
14624     }
14625
14626     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14627     if (appData.noChessProgram || gameMode == AnalyzeMode)
14628       return 0;
14629
14630     if (gameMode != AnalyzeFile) {
14631         if (!appData.icsEngineAnalyze) {
14632                EditGameEvent();
14633                if (gameMode != EditGame) return 0;
14634         }
14635         if (!appData.showThinking) ToggleShowThinking();
14636         ResurrectChessProgram();
14637         SendToProgram("analyze\n", &first);
14638         first.analyzing = TRUE;
14639         /*first.maybeThinking = TRUE;*/
14640         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14641         EngineOutputPopUp();
14642     }
14643     if (!appData.icsEngineAnalyze) {
14644         gameMode = AnalyzeMode;
14645         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14646     }
14647     pausing = FALSE;
14648     ModeHighlight();
14649     SetGameInfo();
14650
14651     StartAnalysisClock();
14652     GetTimeMark(&lastNodeCountTime);
14653     lastNodeCount = 0;
14654     return 1;
14655 }
14656
14657 void
14658 AnalyzeFileEvent ()
14659 {
14660     if (appData.noChessProgram || gameMode == AnalyzeFile)
14661       return;
14662
14663     if (!first.analysisSupport) {
14664       char buf[MSG_SIZ];
14665       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14666       DisplayError(buf, 0);
14667       return;
14668     }
14669
14670     if (gameMode != AnalyzeMode) {
14671         keepInfo = 1; // mere annotating should not alter PGN tags
14672         EditGameEvent();
14673         keepInfo = 0;
14674         if (gameMode != EditGame) return;
14675         if (!appData.showThinking) ToggleShowThinking();
14676         ResurrectChessProgram();
14677         SendToProgram("analyze\n", &first);
14678         first.analyzing = TRUE;
14679         /*first.maybeThinking = TRUE;*/
14680         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14681         EngineOutputPopUp();
14682     }
14683     gameMode = AnalyzeFile;
14684     pausing = FALSE;
14685     ModeHighlight();
14686
14687     StartAnalysisClock();
14688     GetTimeMark(&lastNodeCountTime);
14689     lastNodeCount = 0;
14690     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14691     AnalysisPeriodicEvent(1);
14692 }
14693
14694 void
14695 MachineWhiteEvent ()
14696 {
14697     char buf[MSG_SIZ];
14698     char *bookHit = NULL;
14699
14700     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14701       return;
14702
14703
14704     if (gameMode == PlayFromGameFile ||
14705         gameMode == TwoMachinesPlay  ||
14706         gameMode == Training         ||
14707         gameMode == AnalyzeMode      ||
14708         gameMode == EndOfGame)
14709         EditGameEvent();
14710
14711     if (gameMode == EditPosition)
14712         EditPositionDone(TRUE);
14713
14714     if (!WhiteOnMove(currentMove)) {
14715         DisplayError(_("It is not White's turn"), 0);
14716         return;
14717     }
14718
14719     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14720       ExitAnalyzeMode();
14721
14722     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14723         gameMode == AnalyzeFile)
14724         TruncateGame();
14725
14726     ResurrectChessProgram();    /* in case it isn't running */
14727     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14728         gameMode = MachinePlaysWhite;
14729         ResetClocks();
14730     } else
14731     gameMode = MachinePlaysWhite;
14732     pausing = FALSE;
14733     ModeHighlight();
14734     SetGameInfo();
14735     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14736     DisplayTitle(buf);
14737     if (first.sendName) {
14738       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14739       SendToProgram(buf, &first);
14740     }
14741     if (first.sendTime) {
14742       if (first.useColors) {
14743         SendToProgram("black\n", &first); /*gnu kludge*/
14744       }
14745       SendTimeRemaining(&first, TRUE);
14746     }
14747     if (first.useColors) {
14748       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14749     }
14750     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14751     SetMachineThinkingEnables();
14752     first.maybeThinking = TRUE;
14753     StartClocks();
14754     firstMove = FALSE;
14755
14756     if (appData.autoFlipView && !flipView) {
14757       flipView = !flipView;
14758       DrawPosition(FALSE, NULL);
14759       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14760     }
14761
14762     if(bookHit) { // [HGM] book: simulate book reply
14763         static char bookMove[MSG_SIZ]; // a bit generous?
14764
14765         programStats.nodes = programStats.depth = programStats.time =
14766         programStats.score = programStats.got_only_move = 0;
14767         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14768
14769         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14770         strcat(bookMove, bookHit);
14771         HandleMachineMove(bookMove, &first);
14772     }
14773 }
14774
14775 void
14776 MachineBlackEvent ()
14777 {
14778   char buf[MSG_SIZ];
14779   char *bookHit = NULL;
14780
14781     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14782         return;
14783
14784
14785     if (gameMode == PlayFromGameFile ||
14786         gameMode == TwoMachinesPlay  ||
14787         gameMode == Training         ||
14788         gameMode == AnalyzeMode      ||
14789         gameMode == EndOfGame)
14790         EditGameEvent();
14791
14792     if (gameMode == EditPosition)
14793         EditPositionDone(TRUE);
14794
14795     if (WhiteOnMove(currentMove)) {
14796         DisplayError(_("It is not Black's turn"), 0);
14797         return;
14798     }
14799
14800     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14801       ExitAnalyzeMode();
14802
14803     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14804         gameMode == AnalyzeFile)
14805         TruncateGame();
14806
14807     ResurrectChessProgram();    /* in case it isn't running */
14808     gameMode = MachinePlaysBlack;
14809     pausing = FALSE;
14810     ModeHighlight();
14811     SetGameInfo();
14812     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14813     DisplayTitle(buf);
14814     if (first.sendName) {
14815       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14816       SendToProgram(buf, &first);
14817     }
14818     if (first.sendTime) {
14819       if (first.useColors) {
14820         SendToProgram("white\n", &first); /*gnu kludge*/
14821       }
14822       SendTimeRemaining(&first, FALSE);
14823     }
14824     if (first.useColors) {
14825       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14826     }
14827     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14828     SetMachineThinkingEnables();
14829     first.maybeThinking = TRUE;
14830     StartClocks();
14831
14832     if (appData.autoFlipView && flipView) {
14833       flipView = !flipView;
14834       DrawPosition(FALSE, NULL);
14835       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14836     }
14837     if(bookHit) { // [HGM] book: simulate book reply
14838         static char bookMove[MSG_SIZ]; // a bit generous?
14839
14840         programStats.nodes = programStats.depth = programStats.time =
14841         programStats.score = programStats.got_only_move = 0;
14842         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14843
14844         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14845         strcat(bookMove, bookHit);
14846         HandleMachineMove(bookMove, &first);
14847     }
14848 }
14849
14850
14851 void
14852 DisplayTwoMachinesTitle ()
14853 {
14854     char buf[MSG_SIZ];
14855     if (appData.matchGames > 0) {
14856         if(appData.tourneyFile[0]) {
14857           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14858                    gameInfo.white, _("vs."), gameInfo.black,
14859                    nextGame+1, appData.matchGames+1,
14860                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14861         } else
14862         if (first.twoMachinesColor[0] == 'w') {
14863           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14864                    gameInfo.white, _("vs."),  gameInfo.black,
14865                    first.matchWins, second.matchWins,
14866                    matchGame - 1 - (first.matchWins + second.matchWins));
14867         } else {
14868           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14869                    gameInfo.white, _("vs."), gameInfo.black,
14870                    second.matchWins, first.matchWins,
14871                    matchGame - 1 - (first.matchWins + second.matchWins));
14872         }
14873     } else {
14874       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14875     }
14876     DisplayTitle(buf);
14877 }
14878
14879 void
14880 SettingsMenuIfReady ()
14881 {
14882   if (second.lastPing != second.lastPong) {
14883     DisplayMessage("", _("Waiting for second chess program"));
14884     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14885     return;
14886   }
14887   ThawUI();
14888   DisplayMessage("", "");
14889   SettingsPopUp(&second);
14890 }
14891
14892 int
14893 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14894 {
14895     char buf[MSG_SIZ];
14896     if (cps->pr == NoProc) {
14897         StartChessProgram(cps);
14898         if (cps->protocolVersion == 1) {
14899           retry();
14900           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14901         } else {
14902           /* kludge: allow timeout for initial "feature" command */
14903           if(retry != TwoMachinesEventIfReady) FreezeUI();
14904           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14905           DisplayMessage("", buf);
14906           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14907         }
14908         return 1;
14909     }
14910     return 0;
14911 }
14912
14913 void
14914 TwoMachinesEvent P((void))
14915 {
14916     int i;
14917     char buf[MSG_SIZ];
14918     ChessProgramState *onmove;
14919     char *bookHit = NULL;
14920     static int stalling = 0;
14921     TimeMark now;
14922     long wait;
14923
14924     if (appData.noChessProgram) return;
14925
14926     switch (gameMode) {
14927       case TwoMachinesPlay:
14928         return;
14929       case MachinePlaysWhite:
14930       case MachinePlaysBlack:
14931         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14932             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14933             return;
14934         }
14935         /* fall through */
14936       case BeginningOfGame:
14937       case PlayFromGameFile:
14938       case EndOfGame:
14939         EditGameEvent();
14940         if (gameMode != EditGame) return;
14941         break;
14942       case EditPosition:
14943         EditPositionDone(TRUE);
14944         break;
14945       case AnalyzeMode:
14946       case AnalyzeFile:
14947         ExitAnalyzeMode();
14948         break;
14949       case EditGame:
14950       default:
14951         break;
14952     }
14953
14954 //    forwardMostMove = currentMove;
14955     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14956     startingEngine = TRUE;
14957
14958     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14959
14960     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14961     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14962       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14963       return;
14964     }
14965     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14966
14967     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14968                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14969         startingEngine = matchMode = FALSE;
14970         DisplayError("second engine does not play this", 0);
14971         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14972         EditGameEvent(); // switch back to EditGame mode
14973         return;
14974     }
14975
14976     if(!stalling) {
14977       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14978       SendToProgram("force\n", &second);
14979       stalling = 1;
14980       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14981       return;
14982     }
14983     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14984     if(appData.matchPause>10000 || appData.matchPause<10)
14985                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14986     wait = SubtractTimeMarks(&now, &pauseStart);
14987     if(wait < appData.matchPause) {
14988         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14989         return;
14990     }
14991     // we are now committed to starting the game
14992     stalling = 0;
14993     DisplayMessage("", "");
14994     if (startedFromSetupPosition) {
14995         SendBoard(&second, backwardMostMove);
14996     if (appData.debugMode) {
14997         fprintf(debugFP, "Two Machines\n");
14998     }
14999     }
15000     for (i = backwardMostMove; i < forwardMostMove; i++) {
15001         SendMoveToProgram(i, &second);
15002     }
15003
15004     gameMode = TwoMachinesPlay;
15005     pausing = startingEngine = FALSE;
15006     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15007     SetGameInfo();
15008     DisplayTwoMachinesTitle();
15009     firstMove = TRUE;
15010     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15011         onmove = &first;
15012     } else {
15013         onmove = &second;
15014     }
15015     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15016     SendToProgram(first.computerString, &first);
15017     if (first.sendName) {
15018       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15019       SendToProgram(buf, &first);
15020     }
15021     SendToProgram(second.computerString, &second);
15022     if (second.sendName) {
15023       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15024       SendToProgram(buf, &second);
15025     }
15026
15027     ResetClocks();
15028     if (!first.sendTime || !second.sendTime) {
15029         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15030         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15031     }
15032     if (onmove->sendTime) {
15033       if (onmove->useColors) {
15034         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15035       }
15036       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15037     }
15038     if (onmove->useColors) {
15039       SendToProgram(onmove->twoMachinesColor, onmove);
15040     }
15041     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15042 //    SendToProgram("go\n", onmove);
15043     onmove->maybeThinking = TRUE;
15044     SetMachineThinkingEnables();
15045
15046     StartClocks();
15047
15048     if(bookHit) { // [HGM] book: simulate book reply
15049         static char bookMove[MSG_SIZ]; // a bit generous?
15050
15051         programStats.nodes = programStats.depth = programStats.time =
15052         programStats.score = programStats.got_only_move = 0;
15053         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15054
15055         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15056         strcat(bookMove, bookHit);
15057         savedMessage = bookMove; // args for deferred call
15058         savedState = onmove;
15059         ScheduleDelayedEvent(DeferredBookMove, 1);
15060     }
15061 }
15062
15063 void
15064 TrainingEvent ()
15065 {
15066     if (gameMode == Training) {
15067       SetTrainingModeOff();
15068       gameMode = PlayFromGameFile;
15069       DisplayMessage("", _("Training mode off"));
15070     } else {
15071       gameMode = Training;
15072       animateTraining = appData.animate;
15073
15074       /* make sure we are not already at the end of the game */
15075       if (currentMove < forwardMostMove) {
15076         SetTrainingModeOn();
15077         DisplayMessage("", _("Training mode on"));
15078       } else {
15079         gameMode = PlayFromGameFile;
15080         DisplayError(_("Already at end of game"), 0);
15081       }
15082     }
15083     ModeHighlight();
15084 }
15085
15086 void
15087 IcsClientEvent ()
15088 {
15089     if (!appData.icsActive) return;
15090     switch (gameMode) {
15091       case IcsPlayingWhite:
15092       case IcsPlayingBlack:
15093       case IcsObserving:
15094       case IcsIdle:
15095       case BeginningOfGame:
15096       case IcsExamining:
15097         return;
15098
15099       case EditGame:
15100         break;
15101
15102       case EditPosition:
15103         EditPositionDone(TRUE);
15104         break;
15105
15106       case AnalyzeMode:
15107       case AnalyzeFile:
15108         ExitAnalyzeMode();
15109         break;
15110
15111       default:
15112         EditGameEvent();
15113         break;
15114     }
15115
15116     gameMode = IcsIdle;
15117     ModeHighlight();
15118     return;
15119 }
15120
15121 void
15122 EditGameEvent ()
15123 {
15124     int i;
15125
15126     switch (gameMode) {
15127       case Training:
15128         SetTrainingModeOff();
15129         break;
15130       case MachinePlaysWhite:
15131       case MachinePlaysBlack:
15132       case BeginningOfGame:
15133         SendToProgram("force\n", &first);
15134         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15135             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15136                 char buf[MSG_SIZ];
15137                 abortEngineThink = TRUE;
15138                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15139                 SendToProgram(buf, &first);
15140                 DisplayMessage("Aborting engine think", "");
15141                 FreezeUI();
15142             }
15143         }
15144         SetUserThinkingEnables();
15145         break;
15146       case PlayFromGameFile:
15147         (void) StopLoadGameTimer();
15148         if (gameFileFP != NULL) {
15149             gameFileFP = NULL;
15150         }
15151         break;
15152       case EditPosition:
15153         EditPositionDone(TRUE);
15154         break;
15155       case AnalyzeMode:
15156       case AnalyzeFile:
15157         ExitAnalyzeMode();
15158         SendToProgram("force\n", &first);
15159         break;
15160       case TwoMachinesPlay:
15161         GameEnds(EndOfFile, NULL, GE_PLAYER);
15162         ResurrectChessProgram();
15163         SetUserThinkingEnables();
15164         break;
15165       case EndOfGame:
15166         ResurrectChessProgram();
15167         break;
15168       case IcsPlayingBlack:
15169       case IcsPlayingWhite:
15170         DisplayError(_("Warning: You are still playing a game"), 0);
15171         break;
15172       case IcsObserving:
15173         DisplayError(_("Warning: You are still observing a game"), 0);
15174         break;
15175       case IcsExamining:
15176         DisplayError(_("Warning: You are still examining a game"), 0);
15177         break;
15178       case IcsIdle:
15179         break;
15180       case EditGame:
15181       default:
15182         return;
15183     }
15184
15185     pausing = FALSE;
15186     StopClocks();
15187     first.offeredDraw = second.offeredDraw = 0;
15188
15189     if (gameMode == PlayFromGameFile) {
15190         whiteTimeRemaining = timeRemaining[0][currentMove];
15191         blackTimeRemaining = timeRemaining[1][currentMove];
15192         DisplayTitle("");
15193     }
15194
15195     if (gameMode == MachinePlaysWhite ||
15196         gameMode == MachinePlaysBlack ||
15197         gameMode == TwoMachinesPlay ||
15198         gameMode == EndOfGame) {
15199         i = forwardMostMove;
15200         while (i > currentMove) {
15201             SendToProgram("undo\n", &first);
15202             i--;
15203         }
15204         if(!adjustedClock) {
15205         whiteTimeRemaining = timeRemaining[0][currentMove];
15206         blackTimeRemaining = timeRemaining[1][currentMove];
15207         DisplayBothClocks();
15208         }
15209         if (whiteFlag || blackFlag) {
15210             whiteFlag = blackFlag = 0;
15211         }
15212         DisplayTitle("");
15213     }
15214
15215     gameMode = EditGame;
15216     ModeHighlight();
15217     SetGameInfo();
15218 }
15219
15220
15221 void
15222 EditPositionEvent ()
15223 {
15224     if (gameMode == EditPosition) {
15225         EditGameEvent();
15226         return;
15227     }
15228
15229     EditGameEvent();
15230     if (gameMode != EditGame) return;
15231
15232     gameMode = EditPosition;
15233     ModeHighlight();
15234     SetGameInfo();
15235     if (currentMove > 0)
15236       CopyBoard(boards[0], boards[currentMove]);
15237
15238     blackPlaysFirst = !WhiteOnMove(currentMove);
15239     ResetClocks();
15240     currentMove = forwardMostMove = backwardMostMove = 0;
15241     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15242     DisplayMove(-1);
15243     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15244 }
15245
15246 void
15247 ExitAnalyzeMode ()
15248 {
15249     /* [DM] icsEngineAnalyze - possible call from other functions */
15250     if (appData.icsEngineAnalyze) {
15251         appData.icsEngineAnalyze = FALSE;
15252
15253         DisplayMessage("",_("Close ICS engine analyze..."));
15254     }
15255     if (first.analysisSupport && first.analyzing) {
15256       SendToBoth("exit\n");
15257       first.analyzing = second.analyzing = FALSE;
15258     }
15259     thinkOutput[0] = NULLCHAR;
15260 }
15261
15262 void
15263 EditPositionDone (Boolean fakeRights)
15264 {
15265     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15266
15267     startedFromSetupPosition = TRUE;
15268     InitChessProgram(&first, FALSE);
15269     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15270       boards[0][EP_STATUS] = EP_NONE;
15271       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15272       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15273         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15274         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15275       } else boards[0][CASTLING][2] = NoRights;
15276       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15277         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15278         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15279       } else boards[0][CASTLING][5] = NoRights;
15280       if(gameInfo.variant == VariantSChess) {
15281         int i;
15282         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15283           boards[0][VIRGIN][i] = 0;
15284           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15285           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15286         }
15287       }
15288     }
15289     SendToProgram("force\n", &first);
15290     if (blackPlaysFirst) {
15291         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15292         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15293         currentMove = forwardMostMove = backwardMostMove = 1;
15294         CopyBoard(boards[1], boards[0]);
15295     } else {
15296         currentMove = forwardMostMove = backwardMostMove = 0;
15297     }
15298     SendBoard(&first, forwardMostMove);
15299     if (appData.debugMode) {
15300         fprintf(debugFP, "EditPosDone\n");
15301     }
15302     DisplayTitle("");
15303     DisplayMessage("", "");
15304     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15305     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15306     gameMode = EditGame;
15307     ModeHighlight();
15308     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15309     ClearHighlights(); /* [AS] */
15310 }
15311
15312 /* Pause for `ms' milliseconds */
15313 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15314 void
15315 TimeDelay (long ms)
15316 {
15317     TimeMark m1, m2;
15318
15319     GetTimeMark(&m1);
15320     do {
15321         GetTimeMark(&m2);
15322     } while (SubtractTimeMarks(&m2, &m1) < ms);
15323 }
15324
15325 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15326 void
15327 SendMultiLineToICS (char *buf)
15328 {
15329     char temp[MSG_SIZ+1], *p;
15330     int len;
15331
15332     len = strlen(buf);
15333     if (len > MSG_SIZ)
15334       len = MSG_SIZ;
15335
15336     strncpy(temp, buf, len);
15337     temp[len] = 0;
15338
15339     p = temp;
15340     while (*p) {
15341         if (*p == '\n' || *p == '\r')
15342           *p = ' ';
15343         ++p;
15344     }
15345
15346     strcat(temp, "\n");
15347     SendToICS(temp);
15348     SendToPlayer(temp, strlen(temp));
15349 }
15350
15351 void
15352 SetWhiteToPlayEvent ()
15353 {
15354     if (gameMode == EditPosition) {
15355         blackPlaysFirst = FALSE;
15356         DisplayBothClocks();    /* works because currentMove is 0 */
15357     } else if (gameMode == IcsExamining) {
15358         SendToICS(ics_prefix);
15359         SendToICS("tomove white\n");
15360     }
15361 }
15362
15363 void
15364 SetBlackToPlayEvent ()
15365 {
15366     if (gameMode == EditPosition) {
15367         blackPlaysFirst = TRUE;
15368         currentMove = 1;        /* kludge */
15369         DisplayBothClocks();
15370         currentMove = 0;
15371     } else if (gameMode == IcsExamining) {
15372         SendToICS(ics_prefix);
15373         SendToICS("tomove black\n");
15374     }
15375 }
15376
15377 void
15378 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15379 {
15380     char buf[MSG_SIZ];
15381     ChessSquare piece = boards[0][y][x];
15382     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15383     static int lastVariant;
15384
15385     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15386
15387     switch (selection) {
15388       case ClearBoard:
15389         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15390         MarkTargetSquares(1);
15391         CopyBoard(currentBoard, boards[0]);
15392         CopyBoard(menuBoard, initialPosition);
15393         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15394             SendToICS(ics_prefix);
15395             SendToICS("bsetup clear\n");
15396         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15397             SendToICS(ics_prefix);
15398             SendToICS("clearboard\n");
15399         } else {
15400             int nonEmpty = 0;
15401             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15402                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15403                 for (y = 0; y < BOARD_HEIGHT; y++) {
15404                     if (gameMode == IcsExamining) {
15405                         if (boards[currentMove][y][x] != EmptySquare) {
15406                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15407                                     AAA + x, ONE + y);
15408                             SendToICS(buf);
15409                         }
15410                     } else if(boards[0][y][x] != DarkSquare) {
15411                         if(boards[0][y][x] != p) nonEmpty++;
15412                         boards[0][y][x] = p;
15413                     }
15414                 }
15415             }
15416             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15417                 int r;
15418                 for(r = 0; r < BOARD_HEIGHT; r++) {
15419                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15420                     ChessSquare p = menuBoard[r][x];
15421                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15422                   }
15423                 }
15424                 DisplayMessage("Clicking clock again restores position", "");
15425                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15426                 if(!nonEmpty) { // asked to clear an empty board
15427                     CopyBoard(boards[0], menuBoard);
15428                 } else
15429                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15430                     CopyBoard(boards[0], initialPosition);
15431                 } else
15432                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15433                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15434                     CopyBoard(boards[0], erasedBoard);
15435                 } else
15436                     CopyBoard(erasedBoard, currentBoard);
15437
15438             }
15439         }
15440         if (gameMode == EditPosition) {
15441             DrawPosition(FALSE, boards[0]);
15442         }
15443         break;
15444
15445       case WhitePlay:
15446         SetWhiteToPlayEvent();
15447         break;
15448
15449       case BlackPlay:
15450         SetBlackToPlayEvent();
15451         break;
15452
15453       case EmptySquare:
15454         if (gameMode == IcsExamining) {
15455             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15456             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15457             SendToICS(buf);
15458         } else {
15459             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15460                 if(x == BOARD_LEFT-2) {
15461                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15462                     boards[0][y][1] = 0;
15463                 } else
15464                 if(x == BOARD_RGHT+1) {
15465                     if(y >= gameInfo.holdingsSize) break;
15466                     boards[0][y][BOARD_WIDTH-2] = 0;
15467                 } else break;
15468             }
15469             boards[0][y][x] = EmptySquare;
15470             DrawPosition(FALSE, boards[0]);
15471         }
15472         break;
15473
15474       case PromotePiece:
15475         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15476            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15477             selection = (ChessSquare) (PROMOTED(piece));
15478         } else if(piece == EmptySquare) selection = WhiteSilver;
15479         else selection = (ChessSquare)((int)piece - 1);
15480         goto defaultlabel;
15481
15482       case DemotePiece:
15483         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15484            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15485             selection = (ChessSquare) (DEMOTED(piece));
15486         } else if(piece == EmptySquare) selection = BlackSilver;
15487         else selection = (ChessSquare)((int)piece + 1);
15488         goto defaultlabel;
15489
15490       case WhiteQueen:
15491       case BlackQueen:
15492         if(gameInfo.variant == VariantShatranj ||
15493            gameInfo.variant == VariantXiangqi  ||
15494            gameInfo.variant == VariantCourier  ||
15495            gameInfo.variant == VariantASEAN    ||
15496            gameInfo.variant == VariantMakruk     )
15497             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15498         goto defaultlabel;
15499
15500       case WhiteKing:
15501       case BlackKing:
15502         if(gameInfo.variant == VariantXiangqi)
15503             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15504         if(gameInfo.variant == VariantKnightmate)
15505             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15506       default:
15507         defaultlabel:
15508         if (gameMode == IcsExamining) {
15509             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15510             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15511                      PieceToChar(selection), AAA + x, ONE + y);
15512             SendToICS(buf);
15513         } else {
15514             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15515                 int n;
15516                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15517                     n = PieceToNumber(selection - BlackPawn);
15518                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15519                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15520                     boards[0][BOARD_HEIGHT-1-n][1]++;
15521                 } else
15522                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15523                     n = PieceToNumber(selection);
15524                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15525                     boards[0][n][BOARD_WIDTH-1] = selection;
15526                     boards[0][n][BOARD_WIDTH-2]++;
15527                 }
15528             } else
15529             boards[0][y][x] = selection;
15530             DrawPosition(TRUE, boards[0]);
15531             ClearHighlights();
15532             fromX = fromY = -1;
15533         }
15534         break;
15535     }
15536 }
15537
15538
15539 void
15540 DropMenuEvent (ChessSquare selection, int x, int y)
15541 {
15542     ChessMove moveType;
15543
15544     switch (gameMode) {
15545       case IcsPlayingWhite:
15546       case MachinePlaysBlack:
15547         if (!WhiteOnMove(currentMove)) {
15548             DisplayMoveError(_("It is Black's turn"));
15549             return;
15550         }
15551         moveType = WhiteDrop;
15552         break;
15553       case IcsPlayingBlack:
15554       case MachinePlaysWhite:
15555         if (WhiteOnMove(currentMove)) {
15556             DisplayMoveError(_("It is White's turn"));
15557             return;
15558         }
15559         moveType = BlackDrop;
15560         break;
15561       case EditGame:
15562         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15563         break;
15564       default:
15565         return;
15566     }
15567
15568     if (moveType == BlackDrop && selection < BlackPawn) {
15569       selection = (ChessSquare) ((int) selection
15570                                  + (int) BlackPawn - (int) WhitePawn);
15571     }
15572     if (boards[currentMove][y][x] != EmptySquare) {
15573         DisplayMoveError(_("That square is occupied"));
15574         return;
15575     }
15576
15577     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15578 }
15579
15580 void
15581 AcceptEvent ()
15582 {
15583     /* Accept a pending offer of any kind from opponent */
15584
15585     if (appData.icsActive) {
15586         SendToICS(ics_prefix);
15587         SendToICS("accept\n");
15588     } else if (cmailMsgLoaded) {
15589         if (currentMove == cmailOldMove &&
15590             commentList[cmailOldMove] != NULL &&
15591             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15592                    "Black offers a draw" : "White offers a draw")) {
15593             TruncateGame();
15594             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15595             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15596         } else {
15597             DisplayError(_("There is no pending offer on this move"), 0);
15598             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15599         }
15600     } else {
15601         /* Not used for offers from chess program */
15602     }
15603 }
15604
15605 void
15606 DeclineEvent ()
15607 {
15608     /* Decline a pending offer of any kind from opponent */
15609
15610     if (appData.icsActive) {
15611         SendToICS(ics_prefix);
15612         SendToICS("decline\n");
15613     } else if (cmailMsgLoaded) {
15614         if (currentMove == cmailOldMove &&
15615             commentList[cmailOldMove] != NULL &&
15616             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15617                    "Black offers a draw" : "White offers a draw")) {
15618 #ifdef NOTDEF
15619             AppendComment(cmailOldMove, "Draw declined", TRUE);
15620             DisplayComment(cmailOldMove - 1, "Draw declined");
15621 #endif /*NOTDEF*/
15622         } else {
15623             DisplayError(_("There is no pending offer on this move"), 0);
15624         }
15625     } else {
15626         /* Not used for offers from chess program */
15627     }
15628 }
15629
15630 void
15631 RematchEvent ()
15632 {
15633     /* Issue ICS rematch command */
15634     if (appData.icsActive) {
15635         SendToICS(ics_prefix);
15636         SendToICS("rematch\n");
15637     }
15638 }
15639
15640 void
15641 CallFlagEvent ()
15642 {
15643     /* Call your opponent's flag (claim a win on time) */
15644     if (appData.icsActive) {
15645         SendToICS(ics_prefix);
15646         SendToICS("flag\n");
15647     } else {
15648         switch (gameMode) {
15649           default:
15650             return;
15651           case MachinePlaysWhite:
15652             if (whiteFlag) {
15653                 if (blackFlag)
15654                   GameEnds(GameIsDrawn, "Both players ran out of time",
15655                            GE_PLAYER);
15656                 else
15657                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15658             } else {
15659                 DisplayError(_("Your opponent is not out of time"), 0);
15660             }
15661             break;
15662           case MachinePlaysBlack:
15663             if (blackFlag) {
15664                 if (whiteFlag)
15665                   GameEnds(GameIsDrawn, "Both players ran out of time",
15666                            GE_PLAYER);
15667                 else
15668                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15669             } else {
15670                 DisplayError(_("Your opponent is not out of time"), 0);
15671             }
15672             break;
15673         }
15674     }
15675 }
15676
15677 void
15678 ClockClick (int which)
15679 {       // [HGM] code moved to back-end from winboard.c
15680         if(which) { // black clock
15681           if (gameMode == EditPosition || gameMode == IcsExamining) {
15682             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15683             SetBlackToPlayEvent();
15684           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15685                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15686           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15687           } else if (shiftKey) {
15688             AdjustClock(which, -1);
15689           } else if (gameMode == IcsPlayingWhite ||
15690                      gameMode == MachinePlaysBlack) {
15691             CallFlagEvent();
15692           }
15693         } else { // white clock
15694           if (gameMode == EditPosition || gameMode == IcsExamining) {
15695             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15696             SetWhiteToPlayEvent();
15697           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15698                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15699           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15700           } else if (shiftKey) {
15701             AdjustClock(which, -1);
15702           } else if (gameMode == IcsPlayingBlack ||
15703                    gameMode == MachinePlaysWhite) {
15704             CallFlagEvent();
15705           }
15706         }
15707 }
15708
15709 void
15710 DrawEvent ()
15711 {
15712     /* Offer draw or accept pending draw offer from opponent */
15713
15714     if (appData.icsActive) {
15715         /* Note: tournament rules require draw offers to be
15716            made after you make your move but before you punch
15717            your clock.  Currently ICS doesn't let you do that;
15718            instead, you immediately punch your clock after making
15719            a move, but you can offer a draw at any time. */
15720
15721         SendToICS(ics_prefix);
15722         SendToICS("draw\n");
15723         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15724     } else if (cmailMsgLoaded) {
15725         if (currentMove == cmailOldMove &&
15726             commentList[cmailOldMove] != NULL &&
15727             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15728                    "Black offers a draw" : "White offers a draw")) {
15729             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15730             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15731         } else if (currentMove == cmailOldMove + 1) {
15732             char *offer = WhiteOnMove(cmailOldMove) ?
15733               "White offers a draw" : "Black offers a draw";
15734             AppendComment(currentMove, offer, TRUE);
15735             DisplayComment(currentMove - 1, offer);
15736             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15737         } else {
15738             DisplayError(_("You must make your move before offering a draw"), 0);
15739             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15740         }
15741     } else if (first.offeredDraw) {
15742         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15743     } else {
15744         if (first.sendDrawOffers) {
15745             SendToProgram("draw\n", &first);
15746             userOfferedDraw = TRUE;
15747         }
15748     }
15749 }
15750
15751 void
15752 AdjournEvent ()
15753 {
15754     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15755
15756     if (appData.icsActive) {
15757         SendToICS(ics_prefix);
15758         SendToICS("adjourn\n");
15759     } else {
15760         /* Currently GNU Chess doesn't offer or accept Adjourns */
15761     }
15762 }
15763
15764
15765 void
15766 AbortEvent ()
15767 {
15768     /* Offer Abort or accept pending Abort offer from opponent */
15769
15770     if (appData.icsActive) {
15771         SendToICS(ics_prefix);
15772         SendToICS("abort\n");
15773     } else {
15774         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15775     }
15776 }
15777
15778 void
15779 ResignEvent ()
15780 {
15781     /* Resign.  You can do this even if it's not your turn. */
15782
15783     if (appData.icsActive) {
15784         SendToICS(ics_prefix);
15785         SendToICS("resign\n");
15786     } else {
15787         switch (gameMode) {
15788           case MachinePlaysWhite:
15789             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15790             break;
15791           case MachinePlaysBlack:
15792             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15793             break;
15794           case EditGame:
15795             if (cmailMsgLoaded) {
15796                 TruncateGame();
15797                 if (WhiteOnMove(cmailOldMove)) {
15798                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15799                 } else {
15800                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15801                 }
15802                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15803             }
15804             break;
15805           default:
15806             break;
15807         }
15808     }
15809 }
15810
15811
15812 void
15813 StopObservingEvent ()
15814 {
15815     /* Stop observing current games */
15816     SendToICS(ics_prefix);
15817     SendToICS("unobserve\n");
15818 }
15819
15820 void
15821 StopExaminingEvent ()
15822 {
15823     /* Stop observing current game */
15824     SendToICS(ics_prefix);
15825     SendToICS("unexamine\n");
15826 }
15827
15828 void
15829 ForwardInner (int target)
15830 {
15831     int limit; int oldSeekGraphUp = seekGraphUp;
15832
15833     if (appData.debugMode)
15834         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15835                 target, currentMove, forwardMostMove);
15836
15837     if (gameMode == EditPosition)
15838       return;
15839
15840     seekGraphUp = FALSE;
15841     MarkTargetSquares(1);
15842     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15843
15844     if (gameMode == PlayFromGameFile && !pausing)
15845       PauseEvent();
15846
15847     if (gameMode == IcsExamining && pausing)
15848       limit = pauseExamForwardMostMove;
15849     else
15850       limit = forwardMostMove;
15851
15852     if (target > limit) target = limit;
15853
15854     if (target > 0 && moveList[target - 1][0]) {
15855         int fromX, fromY, toX, toY;
15856         toX = moveList[target - 1][2] - AAA;
15857         toY = moveList[target - 1][3] - ONE;
15858         if (moveList[target - 1][1] == '@') {
15859             if (appData.highlightLastMove) {
15860                 SetHighlights(-1, -1, toX, toY);
15861             }
15862         } else {
15863             int viaX = moveList[target - 1][5] - AAA;
15864             int viaY = moveList[target - 1][6] - ONE;
15865             fromX = moveList[target - 1][0] - AAA;
15866             fromY = moveList[target - 1][1] - ONE;
15867             if (target == currentMove + 1) {
15868                 if(moveList[target - 1][4] == ';') { // multi-leg
15869                     ChessSquare piece = boards[currentMove][viaY][viaX];
15870                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15871                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15872                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15873                     boards[currentMove][viaY][viaX] = piece;
15874                 } else
15875                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15876             }
15877             if (appData.highlightLastMove) {
15878                 SetHighlights(fromX, fromY, toX, toY);
15879             }
15880         }
15881     }
15882     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15883         gameMode == Training || gameMode == PlayFromGameFile ||
15884         gameMode == AnalyzeFile) {
15885         while (currentMove < target) {
15886             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15887             SendMoveToProgram(currentMove++, &first);
15888         }
15889     } else {
15890         currentMove = target;
15891     }
15892
15893     if (gameMode == EditGame || gameMode == EndOfGame) {
15894         whiteTimeRemaining = timeRemaining[0][currentMove];
15895         blackTimeRemaining = timeRemaining[1][currentMove];
15896     }
15897     DisplayBothClocks();
15898     DisplayMove(currentMove - 1);
15899     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15900     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15901     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15902         DisplayComment(currentMove - 1, commentList[currentMove]);
15903     }
15904     ClearMap(); // [HGM] exclude: invalidate map
15905 }
15906
15907
15908 void
15909 ForwardEvent ()
15910 {
15911     if (gameMode == IcsExamining && !pausing) {
15912         SendToICS(ics_prefix);
15913         SendToICS("forward\n");
15914     } else {
15915         ForwardInner(currentMove + 1);
15916     }
15917 }
15918
15919 void
15920 ToEndEvent ()
15921 {
15922     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15923         /* to optimze, we temporarily turn off analysis mode while we feed
15924          * the remaining moves to the engine. Otherwise we get analysis output
15925          * after each move.
15926          */
15927         if (first.analysisSupport) {
15928           SendToProgram("exit\nforce\n", &first);
15929           first.analyzing = FALSE;
15930         }
15931     }
15932
15933     if (gameMode == IcsExamining && !pausing) {
15934         SendToICS(ics_prefix);
15935         SendToICS("forward 999999\n");
15936     } else {
15937         ForwardInner(forwardMostMove);
15938     }
15939
15940     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15941         /* we have fed all the moves, so reactivate analysis mode */
15942         SendToProgram("analyze\n", &first);
15943         first.analyzing = TRUE;
15944         /*first.maybeThinking = TRUE;*/
15945         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15946     }
15947 }
15948
15949 void
15950 BackwardInner (int target)
15951 {
15952     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15953
15954     if (appData.debugMode)
15955         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15956                 target, currentMove, forwardMostMove);
15957
15958     if (gameMode == EditPosition) return;
15959     seekGraphUp = FALSE;
15960     MarkTargetSquares(1);
15961     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15962     if (currentMove <= backwardMostMove) {
15963         ClearHighlights();
15964         DrawPosition(full_redraw, boards[currentMove]);
15965         return;
15966     }
15967     if (gameMode == PlayFromGameFile && !pausing)
15968       PauseEvent();
15969
15970     if (moveList[target][0]) {
15971         int fromX, fromY, toX, toY;
15972         toX = moveList[target][2] - AAA;
15973         toY = moveList[target][3] - ONE;
15974         if (moveList[target][1] == '@') {
15975             if (appData.highlightLastMove) {
15976                 SetHighlights(-1, -1, toX, toY);
15977             }
15978         } else {
15979             fromX = moveList[target][0] - AAA;
15980             fromY = moveList[target][1] - ONE;
15981             if (target == currentMove - 1) {
15982                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15983             }
15984             if (appData.highlightLastMove) {
15985                 SetHighlights(fromX, fromY, toX, toY);
15986             }
15987         }
15988     }
15989     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15990         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15991         while (currentMove > target) {
15992             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15993                 // null move cannot be undone. Reload program with move history before it.
15994                 int i;
15995                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15996                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15997                 }
15998                 SendBoard(&first, i);
15999               if(second.analyzing) SendBoard(&second, i);
16000                 for(currentMove=i; currentMove<target; currentMove++) {
16001                     SendMoveToProgram(currentMove, &first);
16002                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16003                 }
16004                 break;
16005             }
16006             SendToBoth("undo\n");
16007             currentMove--;
16008         }
16009     } else {
16010         currentMove = target;
16011     }
16012
16013     if (gameMode == EditGame || gameMode == EndOfGame) {
16014         whiteTimeRemaining = timeRemaining[0][currentMove];
16015         blackTimeRemaining = timeRemaining[1][currentMove];
16016     }
16017     DisplayBothClocks();
16018     DisplayMove(currentMove - 1);
16019     DrawPosition(full_redraw, boards[currentMove]);
16020     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16021     // [HGM] PV info: routine tests if comment empty
16022     DisplayComment(currentMove - 1, commentList[currentMove]);
16023     ClearMap(); // [HGM] exclude: invalidate map
16024 }
16025
16026 void
16027 BackwardEvent ()
16028 {
16029     if (gameMode == IcsExamining && !pausing) {
16030         SendToICS(ics_prefix);
16031         SendToICS("backward\n");
16032     } else {
16033         BackwardInner(currentMove - 1);
16034     }
16035 }
16036
16037 void
16038 ToStartEvent ()
16039 {
16040     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16041         /* to optimize, we temporarily turn off analysis mode while we undo
16042          * all the moves. Otherwise we get analysis output after each undo.
16043          */
16044         if (first.analysisSupport) {
16045           SendToProgram("exit\nforce\n", &first);
16046           first.analyzing = FALSE;
16047         }
16048     }
16049
16050     if (gameMode == IcsExamining && !pausing) {
16051         SendToICS(ics_prefix);
16052         SendToICS("backward 999999\n");
16053     } else {
16054         BackwardInner(backwardMostMove);
16055     }
16056
16057     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16058         /* we have fed all the moves, so reactivate analysis mode */
16059         SendToProgram("analyze\n", &first);
16060         first.analyzing = TRUE;
16061         /*first.maybeThinking = TRUE;*/
16062         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16063     }
16064 }
16065
16066 void
16067 ToNrEvent (int to)
16068 {
16069   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16070   if (to >= forwardMostMove) to = forwardMostMove;
16071   if (to <= backwardMostMove) to = backwardMostMove;
16072   if (to < currentMove) {
16073     BackwardInner(to);
16074   } else {
16075     ForwardInner(to);
16076   }
16077 }
16078
16079 void
16080 RevertEvent (Boolean annotate)
16081 {
16082     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16083         return;
16084     }
16085     if (gameMode != IcsExamining) {
16086         DisplayError(_("You are not examining a game"), 0);
16087         return;
16088     }
16089     if (pausing) {
16090         DisplayError(_("You can't revert while pausing"), 0);
16091         return;
16092     }
16093     SendToICS(ics_prefix);
16094     SendToICS("revert\n");
16095 }
16096
16097 void
16098 RetractMoveEvent ()
16099 {
16100     switch (gameMode) {
16101       case MachinePlaysWhite:
16102       case MachinePlaysBlack:
16103         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16104             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16105             return;
16106         }
16107         if (forwardMostMove < 2) return;
16108         currentMove = forwardMostMove = forwardMostMove - 2;
16109         whiteTimeRemaining = timeRemaining[0][currentMove];
16110         blackTimeRemaining = timeRemaining[1][currentMove];
16111         DisplayBothClocks();
16112         DisplayMove(currentMove - 1);
16113         ClearHighlights();/*!! could figure this out*/
16114         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16115         SendToProgram("remove\n", &first);
16116         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16117         break;
16118
16119       case BeginningOfGame:
16120       default:
16121         break;
16122
16123       case IcsPlayingWhite:
16124       case IcsPlayingBlack:
16125         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16126             SendToICS(ics_prefix);
16127             SendToICS("takeback 2\n");
16128         } else {
16129             SendToICS(ics_prefix);
16130             SendToICS("takeback 1\n");
16131         }
16132         break;
16133     }
16134 }
16135
16136 void
16137 MoveNowEvent ()
16138 {
16139     ChessProgramState *cps;
16140
16141     switch (gameMode) {
16142       case MachinePlaysWhite:
16143         if (!WhiteOnMove(forwardMostMove)) {
16144             DisplayError(_("It is your turn"), 0);
16145             return;
16146         }
16147         cps = &first;
16148         break;
16149       case MachinePlaysBlack:
16150         if (WhiteOnMove(forwardMostMove)) {
16151             DisplayError(_("It is your turn"), 0);
16152             return;
16153         }
16154         cps = &first;
16155         break;
16156       case TwoMachinesPlay:
16157         if (WhiteOnMove(forwardMostMove) ==
16158             (first.twoMachinesColor[0] == 'w')) {
16159             cps = &first;
16160         } else {
16161             cps = &second;
16162         }
16163         break;
16164       case BeginningOfGame:
16165       default:
16166         return;
16167     }
16168     SendToProgram("?\n", cps);
16169 }
16170
16171 void
16172 TruncateGameEvent ()
16173 {
16174     EditGameEvent();
16175     if (gameMode != EditGame) return;
16176     TruncateGame();
16177 }
16178
16179 void
16180 TruncateGame ()
16181 {
16182     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16183     if (forwardMostMove > currentMove) {
16184         if (gameInfo.resultDetails != NULL) {
16185             free(gameInfo.resultDetails);
16186             gameInfo.resultDetails = NULL;
16187             gameInfo.result = GameUnfinished;
16188         }
16189         forwardMostMove = currentMove;
16190         HistorySet(parseList, backwardMostMove, forwardMostMove,
16191                    currentMove-1);
16192     }
16193 }
16194
16195 void
16196 HintEvent ()
16197 {
16198     if (appData.noChessProgram) return;
16199     switch (gameMode) {
16200       case MachinePlaysWhite:
16201         if (WhiteOnMove(forwardMostMove)) {
16202             DisplayError(_("Wait until your turn."), 0);
16203             return;
16204         }
16205         break;
16206       case BeginningOfGame:
16207       case MachinePlaysBlack:
16208         if (!WhiteOnMove(forwardMostMove)) {
16209             DisplayError(_("Wait until your turn."), 0);
16210             return;
16211         }
16212         break;
16213       default:
16214         DisplayError(_("No hint available"), 0);
16215         return;
16216     }
16217     SendToProgram("hint\n", &first);
16218     hintRequested = TRUE;
16219 }
16220
16221 int
16222 SaveSelected (FILE *g, int dummy, char *dummy2)
16223 {
16224     ListGame * lg = (ListGame *) gameList.head;
16225     int nItem, cnt=0;
16226     FILE *f;
16227
16228     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16229         DisplayError(_("Game list not loaded or empty"), 0);
16230         return 0;
16231     }
16232
16233     creatingBook = TRUE; // suppresses stuff during load game
16234
16235     /* Get list size */
16236     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16237         if(lg->position >= 0) { // selected?
16238             LoadGame(f, nItem, "", TRUE);
16239             SaveGamePGN2(g); // leaves g open
16240             cnt++; DoEvents();
16241         }
16242         lg = (ListGame *) lg->node.succ;
16243     }
16244
16245     fclose(g);
16246     creatingBook = FALSE;
16247
16248     return cnt;
16249 }
16250
16251 void
16252 CreateBookEvent ()
16253 {
16254     ListGame * lg = (ListGame *) gameList.head;
16255     FILE *f, *g;
16256     int nItem;
16257     static int secondTime = FALSE;
16258
16259     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16260         DisplayError(_("Game list not loaded or empty"), 0);
16261         return;
16262     }
16263
16264     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16265         fclose(g);
16266         secondTime++;
16267         DisplayNote(_("Book file exists! Try again for overwrite."));
16268         return;
16269     }
16270
16271     creatingBook = TRUE;
16272     secondTime = FALSE;
16273
16274     /* Get list size */
16275     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16276         if(lg->position >= 0) {
16277             LoadGame(f, nItem, "", TRUE);
16278             AddGameToBook(TRUE);
16279             DoEvents();
16280         }
16281         lg = (ListGame *) lg->node.succ;
16282     }
16283
16284     creatingBook = FALSE;
16285     FlushBook();
16286 }
16287
16288 void
16289 BookEvent ()
16290 {
16291     if (appData.noChessProgram) return;
16292     switch (gameMode) {
16293       case MachinePlaysWhite:
16294         if (WhiteOnMove(forwardMostMove)) {
16295             DisplayError(_("Wait until your turn."), 0);
16296             return;
16297         }
16298         break;
16299       case BeginningOfGame:
16300       case MachinePlaysBlack:
16301         if (!WhiteOnMove(forwardMostMove)) {
16302             DisplayError(_("Wait until your turn."), 0);
16303             return;
16304         }
16305         break;
16306       case EditPosition:
16307         EditPositionDone(TRUE);
16308         break;
16309       case TwoMachinesPlay:
16310         return;
16311       default:
16312         break;
16313     }
16314     SendToProgram("bk\n", &first);
16315     bookOutput[0] = NULLCHAR;
16316     bookRequested = TRUE;
16317 }
16318
16319 void
16320 AboutGameEvent ()
16321 {
16322     char *tags = PGNTags(&gameInfo);
16323     TagsPopUp(tags, CmailMsg());
16324     free(tags);
16325 }
16326
16327 /* end button procedures */
16328
16329 void
16330 PrintPosition (FILE *fp, int move)
16331 {
16332     int i, j;
16333
16334     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16335         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16336             char c = PieceToChar(boards[move][i][j]);
16337             fputc(c == '?' ? '.' : c, fp);
16338             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16339         }
16340     }
16341     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16342       fprintf(fp, "white to play\n");
16343     else
16344       fprintf(fp, "black to play\n");
16345 }
16346
16347 void
16348 PrintOpponents (FILE *fp)
16349 {
16350     if (gameInfo.white != NULL) {
16351         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16352     } else {
16353         fprintf(fp, "\n");
16354     }
16355 }
16356
16357 /* Find last component of program's own name, using some heuristics */
16358 void
16359 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16360 {
16361     char *p, *q, c;
16362     int local = (strcmp(host, "localhost") == 0);
16363     while (!local && (p = strchr(prog, ';')) != NULL) {
16364         p++;
16365         while (*p == ' ') p++;
16366         prog = p;
16367     }
16368     if (*prog == '"' || *prog == '\'') {
16369         q = strchr(prog + 1, *prog);
16370     } else {
16371         q = strchr(prog, ' ');
16372     }
16373     if (q == NULL) q = prog + strlen(prog);
16374     p = q;
16375     while (p >= prog && *p != '/' && *p != '\\') p--;
16376     p++;
16377     if(p == prog && *p == '"') p++;
16378     c = *q; *q = 0;
16379     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16380     memcpy(buf, p, q - p);
16381     buf[q - p] = NULLCHAR;
16382     if (!local) {
16383         strcat(buf, "@");
16384         strcat(buf, host);
16385     }
16386 }
16387
16388 char *
16389 TimeControlTagValue ()
16390 {
16391     char buf[MSG_SIZ];
16392     if (!appData.clockMode) {
16393       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16394     } else if (movesPerSession > 0) {
16395       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16396     } else if (timeIncrement == 0) {
16397       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16398     } else {
16399       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16400     }
16401     return StrSave(buf);
16402 }
16403
16404 void
16405 SetGameInfo ()
16406 {
16407     /* This routine is used only for certain modes */
16408     VariantClass v = gameInfo.variant;
16409     ChessMove r = GameUnfinished;
16410     char *p = NULL;
16411
16412     if(keepInfo) return;
16413
16414     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16415         r = gameInfo.result;
16416         p = gameInfo.resultDetails;
16417         gameInfo.resultDetails = NULL;
16418     }
16419     ClearGameInfo(&gameInfo);
16420     gameInfo.variant = v;
16421
16422     switch (gameMode) {
16423       case MachinePlaysWhite:
16424         gameInfo.event = StrSave( appData.pgnEventHeader );
16425         gameInfo.site = StrSave(HostName());
16426         gameInfo.date = PGNDate();
16427         gameInfo.round = StrSave("-");
16428         gameInfo.white = StrSave(first.tidy);
16429         gameInfo.black = StrSave(UserName());
16430         gameInfo.timeControl = TimeControlTagValue();
16431         break;
16432
16433       case MachinePlaysBlack:
16434         gameInfo.event = StrSave( appData.pgnEventHeader );
16435         gameInfo.site = StrSave(HostName());
16436         gameInfo.date = PGNDate();
16437         gameInfo.round = StrSave("-");
16438         gameInfo.white = StrSave(UserName());
16439         gameInfo.black = StrSave(first.tidy);
16440         gameInfo.timeControl = TimeControlTagValue();
16441         break;
16442
16443       case TwoMachinesPlay:
16444         gameInfo.event = StrSave( appData.pgnEventHeader );
16445         gameInfo.site = StrSave(HostName());
16446         gameInfo.date = PGNDate();
16447         if (roundNr > 0) {
16448             char buf[MSG_SIZ];
16449             snprintf(buf, MSG_SIZ, "%d", roundNr);
16450             gameInfo.round = StrSave(buf);
16451         } else {
16452             gameInfo.round = StrSave("-");
16453         }
16454         if (first.twoMachinesColor[0] == 'w') {
16455             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16456             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16457         } else {
16458             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16459             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16460         }
16461         gameInfo.timeControl = TimeControlTagValue();
16462         break;
16463
16464       case EditGame:
16465         gameInfo.event = StrSave("Edited game");
16466         gameInfo.site = StrSave(HostName());
16467         gameInfo.date = PGNDate();
16468         gameInfo.round = StrSave("-");
16469         gameInfo.white = StrSave("-");
16470         gameInfo.black = StrSave("-");
16471         gameInfo.result = r;
16472         gameInfo.resultDetails = p;
16473         break;
16474
16475       case EditPosition:
16476         gameInfo.event = StrSave("Edited position");
16477         gameInfo.site = StrSave(HostName());
16478         gameInfo.date = PGNDate();
16479         gameInfo.round = StrSave("-");
16480         gameInfo.white = StrSave("-");
16481         gameInfo.black = StrSave("-");
16482         break;
16483
16484       case IcsPlayingWhite:
16485       case IcsPlayingBlack:
16486       case IcsObserving:
16487       case IcsExamining:
16488         break;
16489
16490       case PlayFromGameFile:
16491         gameInfo.event = StrSave("Game from non-PGN file");
16492         gameInfo.site = StrSave(HostName());
16493         gameInfo.date = PGNDate();
16494         gameInfo.round = StrSave("-");
16495         gameInfo.white = StrSave("?");
16496         gameInfo.black = StrSave("?");
16497         break;
16498
16499       default:
16500         break;
16501     }
16502 }
16503
16504 void
16505 ReplaceComment (int index, char *text)
16506 {
16507     int len;
16508     char *p;
16509     float score;
16510
16511     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16512        pvInfoList[index-1].depth == len &&
16513        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16514        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16515     while (*text == '\n') text++;
16516     len = strlen(text);
16517     while (len > 0 && text[len - 1] == '\n') len--;
16518
16519     if (commentList[index] != NULL)
16520       free(commentList[index]);
16521
16522     if (len == 0) {
16523         commentList[index] = NULL;
16524         return;
16525     }
16526   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16527       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16528       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16529     commentList[index] = (char *) malloc(len + 2);
16530     strncpy(commentList[index], text, len);
16531     commentList[index][len] = '\n';
16532     commentList[index][len + 1] = NULLCHAR;
16533   } else {
16534     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16535     char *p;
16536     commentList[index] = (char *) malloc(len + 7);
16537     safeStrCpy(commentList[index], "{\n", 3);
16538     safeStrCpy(commentList[index]+2, text, len+1);
16539     commentList[index][len+2] = NULLCHAR;
16540     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16541     strcat(commentList[index], "\n}\n");
16542   }
16543 }
16544
16545 void
16546 CrushCRs (char *text)
16547 {
16548   char *p = text;
16549   char *q = text;
16550   char ch;
16551
16552   do {
16553     ch = *p++;
16554     if (ch == '\r') continue;
16555     *q++ = ch;
16556   } while (ch != '\0');
16557 }
16558
16559 void
16560 AppendComment (int index, char *text, Boolean addBraces)
16561 /* addBraces  tells if we should add {} */
16562 {
16563     int oldlen, len;
16564     char *old;
16565
16566 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16567     if(addBraces == 3) addBraces = 0; else // force appending literally
16568     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16569
16570     CrushCRs(text);
16571     while (*text == '\n') text++;
16572     len = strlen(text);
16573     while (len > 0 && text[len - 1] == '\n') len--;
16574     text[len] = NULLCHAR;
16575
16576     if (len == 0) return;
16577
16578     if (commentList[index] != NULL) {
16579       Boolean addClosingBrace = addBraces;
16580         old = commentList[index];
16581         oldlen = strlen(old);
16582         while(commentList[index][oldlen-1] ==  '\n')
16583           commentList[index][--oldlen] = NULLCHAR;
16584         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16585         safeStrCpy(commentList[index], old, oldlen + len + 6);
16586         free(old);
16587         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16588         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16589           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16590           while (*text == '\n') { text++; len--; }
16591           commentList[index][--oldlen] = NULLCHAR;
16592       }
16593         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16594         else          strcat(commentList[index], "\n");
16595         strcat(commentList[index], text);
16596         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16597         else          strcat(commentList[index], "\n");
16598     } else {
16599         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16600         if(addBraces)
16601           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16602         else commentList[index][0] = NULLCHAR;
16603         strcat(commentList[index], text);
16604         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16605         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16606     }
16607 }
16608
16609 static char *
16610 FindStr (char * text, char * sub_text)
16611 {
16612     char * result = strstr( text, sub_text );
16613
16614     if( result != NULL ) {
16615         result += strlen( sub_text );
16616     }
16617
16618     return result;
16619 }
16620
16621 /* [AS] Try to extract PV info from PGN comment */
16622 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16623 char *
16624 GetInfoFromComment (int index, char * text)
16625 {
16626     char * sep = text, *p;
16627
16628     if( text != NULL && index > 0 ) {
16629         int score = 0;
16630         int depth = 0;
16631         int time = -1, sec = 0, deci;
16632         char * s_eval = FindStr( text, "[%eval " );
16633         char * s_emt = FindStr( text, "[%emt " );
16634 #if 0
16635         if( s_eval != NULL || s_emt != NULL ) {
16636 #else
16637         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16638 #endif
16639             /* New style */
16640             char delim;
16641
16642             if( s_eval != NULL ) {
16643                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16644                     return text;
16645                 }
16646
16647                 if( delim != ']' ) {
16648                     return text;
16649                 }
16650             }
16651
16652             if( s_emt != NULL ) {
16653             }
16654                 return text;
16655         }
16656         else {
16657             /* We expect something like: [+|-]nnn.nn/dd */
16658             int score_lo = 0;
16659
16660             if(*text != '{') return text; // [HGM] braces: must be normal comment
16661
16662             sep = strchr( text, '/' );
16663             if( sep == NULL || sep < (text+4) ) {
16664                 return text;
16665             }
16666
16667             p = text;
16668             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16669             if(p[1] == '(') { // comment starts with PV
16670                p = strchr(p, ')'); // locate end of PV
16671                if(p == NULL || sep < p+5) return text;
16672                // at this point we have something like "{(.*) +0.23/6 ..."
16673                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16674                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16675                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16676             }
16677             time = -1; sec = -1; deci = -1;
16678             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16679                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16680                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16681                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16682                 return text;
16683             }
16684
16685             if( score_lo < 0 || score_lo >= 100 ) {
16686                 return text;
16687             }
16688
16689             if(sec >= 0) time = 600*time + 10*sec; else
16690             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16691
16692             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16693
16694             /* [HGM] PV time: now locate end of PV info */
16695             while( *++sep >= '0' && *sep <= '9'); // strip depth
16696             if(time >= 0)
16697             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16698             if(sec >= 0)
16699             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16700             if(deci >= 0)
16701             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16702             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16703         }
16704
16705         if( depth <= 0 ) {
16706             return text;
16707         }
16708
16709         if( time < 0 ) {
16710             time = -1;
16711         }
16712
16713         pvInfoList[index-1].depth = depth;
16714         pvInfoList[index-1].score = score;
16715         pvInfoList[index-1].time  = 10*time; // centi-sec
16716         if(*sep == '}') *sep = 0; else *--sep = '{';
16717         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16718     }
16719     return sep;
16720 }
16721
16722 void
16723 SendToProgram (char *message, ChessProgramState *cps)
16724 {
16725     int count, outCount, error;
16726     char buf[MSG_SIZ];
16727
16728     if (cps->pr == NoProc) return;
16729     Attention(cps);
16730
16731     if (appData.debugMode) {
16732         TimeMark now;
16733         GetTimeMark(&now);
16734         fprintf(debugFP, "%ld >%-6s: %s",
16735                 SubtractTimeMarks(&now, &programStartTime),
16736                 cps->which, message);
16737         if(serverFP)
16738             fprintf(serverFP, "%ld >%-6s: %s",
16739                 SubtractTimeMarks(&now, &programStartTime),
16740                 cps->which, message), fflush(serverFP);
16741     }
16742
16743     count = strlen(message);
16744     outCount = OutputToProcess(cps->pr, message, count, &error);
16745     if (outCount < count && !exiting
16746                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16747       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16748       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16749         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16750             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16751                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16752                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16753                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16754             } else {
16755                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16756                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16757                 gameInfo.result = res;
16758             }
16759             gameInfo.resultDetails = StrSave(buf);
16760         }
16761         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16762         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16763     }
16764 }
16765
16766 void
16767 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16768 {
16769     char *end_str;
16770     char buf[MSG_SIZ];
16771     ChessProgramState *cps = (ChessProgramState *)closure;
16772
16773     if (isr != cps->isr) return; /* Killed intentionally */
16774     if (count <= 0) {
16775         if (count == 0) {
16776             RemoveInputSource(cps->isr);
16777             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16778                     _(cps->which), cps->program);
16779             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16780             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16781                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16782                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16783                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16784                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16785                 } else {
16786                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16787                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16788                     gameInfo.result = res;
16789                 }
16790                 gameInfo.resultDetails = StrSave(buf);
16791             }
16792             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16793             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16794         } else {
16795             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16796                     _(cps->which), cps->program);
16797             RemoveInputSource(cps->isr);
16798
16799             /* [AS] Program is misbehaving badly... kill it */
16800             if( count == -2 ) {
16801                 DestroyChildProcess( cps->pr, 9 );
16802                 cps->pr = NoProc;
16803             }
16804
16805             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16806         }
16807         return;
16808     }
16809
16810     if ((end_str = strchr(message, '\r')) != NULL)
16811       *end_str = NULLCHAR;
16812     if ((end_str = strchr(message, '\n')) != NULL)
16813       *end_str = NULLCHAR;
16814
16815     if (appData.debugMode) {
16816         TimeMark now; int print = 1;
16817         char *quote = ""; char c; int i;
16818
16819         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16820                 char start = message[0];
16821                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16822                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16823                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16824                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16825                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16826                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16827                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16828                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16829                    sscanf(message, "hint: %c", &c)!=1 &&
16830                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16831                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16832                     print = (appData.engineComments >= 2);
16833                 }
16834                 message[0] = start; // restore original message
16835         }
16836         if(print) {
16837                 GetTimeMark(&now);
16838                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16839                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16840                         quote,
16841                         message);
16842                 if(serverFP)
16843                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16844                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16845                         quote,
16846                         message), fflush(serverFP);
16847         }
16848     }
16849
16850     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16851     if (appData.icsEngineAnalyze) {
16852         if (strstr(message, "whisper") != NULL ||
16853              strstr(message, "kibitz") != NULL ||
16854             strstr(message, "tellics") != NULL) return;
16855     }
16856
16857     HandleMachineMove(message, cps);
16858 }
16859
16860
16861 void
16862 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16863 {
16864     char buf[MSG_SIZ];
16865     int seconds;
16866
16867     if( timeControl_2 > 0 ) {
16868         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16869             tc = timeControl_2;
16870         }
16871     }
16872     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16873     inc /= cps->timeOdds;
16874     st  /= cps->timeOdds;
16875
16876     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16877
16878     if (st > 0) {
16879       /* Set exact time per move, normally using st command */
16880       if (cps->stKludge) {
16881         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16882         seconds = st % 60;
16883         if (seconds == 0) {
16884           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16885         } else {
16886           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16887         }
16888       } else {
16889         snprintf(buf, MSG_SIZ, "st %d\n", st);
16890       }
16891     } else {
16892       /* Set conventional or incremental time control, using level command */
16893       if (seconds == 0) {
16894         /* Note old gnuchess bug -- minutes:seconds used to not work.
16895            Fixed in later versions, but still avoid :seconds
16896            when seconds is 0. */
16897         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16898       } else {
16899         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16900                  seconds, inc/1000.);
16901       }
16902     }
16903     SendToProgram(buf, cps);
16904
16905     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16906     /* Orthogonally, limit search to given depth */
16907     if (sd > 0) {
16908       if (cps->sdKludge) {
16909         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16910       } else {
16911         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16912       }
16913       SendToProgram(buf, cps);
16914     }
16915
16916     if(cps->nps >= 0) { /* [HGM] nps */
16917         if(cps->supportsNPS == FALSE)
16918           cps->nps = -1; // don't use if engine explicitly says not supported!
16919         else {
16920           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16921           SendToProgram(buf, cps);
16922         }
16923     }
16924 }
16925
16926 ChessProgramState *
16927 WhitePlayer ()
16928 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16929 {
16930     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16931        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16932         return &second;
16933     return &first;
16934 }
16935
16936 void
16937 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16938 {
16939     char message[MSG_SIZ];
16940     long time, otime;
16941
16942     /* Note: this routine must be called when the clocks are stopped
16943        or when they have *just* been set or switched; otherwise
16944        it will be off by the time since the current tick started.
16945     */
16946     if (machineWhite) {
16947         time = whiteTimeRemaining / 10;
16948         otime = blackTimeRemaining / 10;
16949     } else {
16950         time = blackTimeRemaining / 10;
16951         otime = whiteTimeRemaining / 10;
16952     }
16953     /* [HGM] translate opponent's time by time-odds factor */
16954     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16955
16956     if (time <= 0) time = 1;
16957     if (otime <= 0) otime = 1;
16958
16959     snprintf(message, MSG_SIZ, "time %ld\n", time);
16960     SendToProgram(message, cps);
16961
16962     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16963     SendToProgram(message, cps);
16964 }
16965
16966 char *
16967 EngineDefinedVariant (ChessProgramState *cps, int n)
16968 {   // return name of n-th unknown variant that engine supports
16969     static char buf[MSG_SIZ];
16970     char *p, *s = cps->variants;
16971     if(!s) return NULL;
16972     do { // parse string from variants feature
16973       VariantClass v;
16974         p = strchr(s, ',');
16975         if(p) *p = NULLCHAR;
16976       v = StringToVariant(s);
16977       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16978         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16979             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16980                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16981                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16982                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16983             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16984         }
16985         if(p) *p++ = ',';
16986         if(n < 0) return buf;
16987     } while(s = p);
16988     return NULL;
16989 }
16990
16991 int
16992 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16993 {
16994   char buf[MSG_SIZ];
16995   int len = strlen(name);
16996   int val;
16997
16998   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16999     (*p) += len + 1;
17000     sscanf(*p, "%d", &val);
17001     *loc = (val != 0);
17002     while (**p && **p != ' ')
17003       (*p)++;
17004     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17005     SendToProgram(buf, cps);
17006     return TRUE;
17007   }
17008   return FALSE;
17009 }
17010
17011 int
17012 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17013 {
17014   char buf[MSG_SIZ];
17015   int len = strlen(name);
17016   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17017     (*p) += len + 1;
17018     sscanf(*p, "%d", loc);
17019     while (**p && **p != ' ') (*p)++;
17020     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17021     SendToProgram(buf, cps);
17022     return TRUE;
17023   }
17024   return FALSE;
17025 }
17026
17027 int
17028 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17029 {
17030   char buf[MSG_SIZ];
17031   int len = strlen(name);
17032   if (strncmp((*p), name, len) == 0
17033       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17034     (*p) += len + 2;
17035     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17036     sscanf(*p, "%[^\"]", *loc);
17037     while (**p && **p != '\"') (*p)++;
17038     if (**p == '\"') (*p)++;
17039     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17040     SendToProgram(buf, cps);
17041     return TRUE;
17042   }
17043   return FALSE;
17044 }
17045
17046 int
17047 ParseOption (Option *opt, ChessProgramState *cps)
17048 // [HGM] options: process the string that defines an engine option, and determine
17049 // name, type, default value, and allowed value range
17050 {
17051         char *p, *q, buf[MSG_SIZ];
17052         int n, min = (-1)<<31, max = 1<<31, def;
17053
17054         opt->target = &opt->value;   // OK for spin/slider and checkbox
17055         if(p = strstr(opt->name, " -spin ")) {
17056             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17057             if(max < min) max = min; // enforce consistency
17058             if(def < min) def = min;
17059             if(def > max) def = max;
17060             opt->value = def;
17061             opt->min = min;
17062             opt->max = max;
17063             opt->type = Spin;
17064         } else if((p = strstr(opt->name, " -slider "))) {
17065             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17066             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17067             if(max < min) max = min; // enforce consistency
17068             if(def < min) def = min;
17069             if(def > max) def = max;
17070             opt->value = def;
17071             opt->min = min;
17072             opt->max = max;
17073             opt->type = Spin; // Slider;
17074         } else if((p = strstr(opt->name, " -string "))) {
17075             opt->textValue = p+9;
17076             opt->type = TextBox;
17077             opt->target = &opt->textValue;
17078         } else if((p = strstr(opt->name, " -file "))) {
17079             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17080             opt->target = opt->textValue = p+7;
17081             opt->type = FileName; // FileName;
17082             opt->target = &opt->textValue;
17083         } else if((p = strstr(opt->name, " -path "))) {
17084             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17085             opt->target = opt->textValue = p+7;
17086             opt->type = PathName; // PathName;
17087             opt->target = &opt->textValue;
17088         } else if(p = strstr(opt->name, " -check ")) {
17089             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17090             opt->value = (def != 0);
17091             opt->type = CheckBox;
17092         } else if(p = strstr(opt->name, " -combo ")) {
17093             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17094             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17095             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17096             opt->value = n = 0;
17097             while(q = StrStr(q, " /// ")) {
17098                 n++; *q = 0;    // count choices, and null-terminate each of them
17099                 q += 5;
17100                 if(*q == '*') { // remember default, which is marked with * prefix
17101                     q++;
17102                     opt->value = n;
17103                 }
17104                 cps->comboList[cps->comboCnt++] = q;
17105             }
17106             cps->comboList[cps->comboCnt++] = NULL;
17107             opt->max = n + 1;
17108             opt->type = ComboBox;
17109         } else if(p = strstr(opt->name, " -button")) {
17110             opt->type = Button;
17111         } else if(p = strstr(opt->name, " -save")) {
17112             opt->type = SaveButton;
17113         } else return FALSE;
17114         *p = 0; // terminate option name
17115         // now look if the command-line options define a setting for this engine option.
17116         if(cps->optionSettings && cps->optionSettings[0])
17117             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17118         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17119           snprintf(buf, MSG_SIZ, "option %s", p);
17120                 if(p = strstr(buf, ",")) *p = 0;
17121                 if(q = strchr(buf, '=')) switch(opt->type) {
17122                     case ComboBox:
17123                         for(n=0; n<opt->max; n++)
17124                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17125                         break;
17126                     case TextBox:
17127                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17128                         break;
17129                     case Spin:
17130                     case CheckBox:
17131                         opt->value = atoi(q+1);
17132                     default:
17133                         break;
17134                 }
17135                 strcat(buf, "\n");
17136                 SendToProgram(buf, cps);
17137         }
17138         return TRUE;
17139 }
17140
17141 void
17142 FeatureDone (ChessProgramState *cps, int val)
17143 {
17144   DelayedEventCallback cb = GetDelayedEvent();
17145   if ((cb == InitBackEnd3 && cps == &first) ||
17146       (cb == SettingsMenuIfReady && cps == &second) ||
17147       (cb == LoadEngine) ||
17148       (cb == TwoMachinesEventIfReady)) {
17149     CancelDelayedEvent();
17150     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17151   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17152   cps->initDone = val;
17153   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17154 }
17155
17156 /* Parse feature command from engine */
17157 void
17158 ParseFeatures (char *args, ChessProgramState *cps)
17159 {
17160   char *p = args;
17161   char *q = NULL;
17162   int val;
17163   char buf[MSG_SIZ];
17164
17165   for (;;) {
17166     while (*p == ' ') p++;
17167     if (*p == NULLCHAR) return;
17168
17169     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17170     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17171     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17172     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17173     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17174     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17175     if (BoolFeature(&p, "reuse", &val, cps)) {
17176       /* Engine can disable reuse, but can't enable it if user said no */
17177       if (!val) cps->reuse = FALSE;
17178       continue;
17179     }
17180     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17181     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17182       if (gameMode == TwoMachinesPlay) {
17183         DisplayTwoMachinesTitle();
17184       } else {
17185         DisplayTitle("");
17186       }
17187       continue;
17188     }
17189     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17190     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17191     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17192     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17193     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17194     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17195     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17196     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17197     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17198     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17199     if (IntFeature(&p, "done", &val, cps)) {
17200       FeatureDone(cps, val);
17201       continue;
17202     }
17203     /* Added by Tord: */
17204     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17205     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17206     /* End of additions by Tord */
17207
17208     /* [HGM] added features: */
17209     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17210     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17211     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17212     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17213     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17214     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17215     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17216     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17217         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17218         FREE(cps->option[cps->nrOptions].name);
17219         cps->option[cps->nrOptions].name = q; q = NULL;
17220         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17221           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17222             SendToProgram(buf, cps);
17223             continue;
17224         }
17225         if(cps->nrOptions >= MAX_OPTIONS) {
17226             cps->nrOptions--;
17227             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17228             DisplayError(buf, 0);
17229         }
17230         continue;
17231     }
17232     /* End of additions by HGM */
17233
17234     /* unknown feature: complain and skip */
17235     q = p;
17236     while (*q && *q != '=') q++;
17237     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17238     SendToProgram(buf, cps);
17239     p = q;
17240     if (*p == '=') {
17241       p++;
17242       if (*p == '\"') {
17243         p++;
17244         while (*p && *p != '\"') p++;
17245         if (*p == '\"') p++;
17246       } else {
17247         while (*p && *p != ' ') p++;
17248       }
17249     }
17250   }
17251
17252 }
17253
17254 void
17255 PeriodicUpdatesEvent (int newState)
17256 {
17257     if (newState == appData.periodicUpdates)
17258       return;
17259
17260     appData.periodicUpdates=newState;
17261
17262     /* Display type changes, so update it now */
17263 //    DisplayAnalysis();
17264
17265     /* Get the ball rolling again... */
17266     if (newState) {
17267         AnalysisPeriodicEvent(1);
17268         StartAnalysisClock();
17269     }
17270 }
17271
17272 void
17273 PonderNextMoveEvent (int newState)
17274 {
17275     if (newState == appData.ponderNextMove) return;
17276     if (gameMode == EditPosition) EditPositionDone(TRUE);
17277     if (newState) {
17278         SendToProgram("hard\n", &first);
17279         if (gameMode == TwoMachinesPlay) {
17280             SendToProgram("hard\n", &second);
17281         }
17282     } else {
17283         SendToProgram("easy\n", &first);
17284         thinkOutput[0] = NULLCHAR;
17285         if (gameMode == TwoMachinesPlay) {
17286             SendToProgram("easy\n", &second);
17287         }
17288     }
17289     appData.ponderNextMove = newState;
17290 }
17291
17292 void
17293 NewSettingEvent (int option, int *feature, char *command, int value)
17294 {
17295     char buf[MSG_SIZ];
17296
17297     if (gameMode == EditPosition) EditPositionDone(TRUE);
17298     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17299     if(feature == NULL || *feature) SendToProgram(buf, &first);
17300     if (gameMode == TwoMachinesPlay) {
17301         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17302     }
17303 }
17304
17305 void
17306 ShowThinkingEvent ()
17307 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17308 {
17309     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17310     int newState = appData.showThinking
17311         // [HGM] thinking: other features now need thinking output as well
17312         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17313
17314     if (oldState == newState) return;
17315     oldState = newState;
17316     if (gameMode == EditPosition) EditPositionDone(TRUE);
17317     if (oldState) {
17318         SendToProgram("post\n", &first);
17319         if (gameMode == TwoMachinesPlay) {
17320             SendToProgram("post\n", &second);
17321         }
17322     } else {
17323         SendToProgram("nopost\n", &first);
17324         thinkOutput[0] = NULLCHAR;
17325         if (gameMode == TwoMachinesPlay) {
17326             SendToProgram("nopost\n", &second);
17327         }
17328     }
17329 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17330 }
17331
17332 void
17333 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17334 {
17335   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17336   if (pr == NoProc) return;
17337   AskQuestion(title, question, replyPrefix, pr);
17338 }
17339
17340 void
17341 TypeInEvent (char firstChar)
17342 {
17343     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17344         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17345         gameMode == AnalyzeMode || gameMode == EditGame ||
17346         gameMode == EditPosition || gameMode == IcsExamining ||
17347         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17348         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17349                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17350                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17351         gameMode == Training) PopUpMoveDialog(firstChar);
17352 }
17353
17354 void
17355 TypeInDoneEvent (char *move)
17356 {
17357         Board board;
17358         int n, fromX, fromY, toX, toY;
17359         char promoChar;
17360         ChessMove moveType;
17361
17362         // [HGM] FENedit
17363         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17364                 EditPositionPasteFEN(move);
17365                 return;
17366         }
17367         // [HGM] movenum: allow move number to be typed in any mode
17368         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17369           ToNrEvent(2*n-1);
17370           return;
17371         }
17372         // undocumented kludge: allow command-line option to be typed in!
17373         // (potentially fatal, and does not implement the effect of the option.)
17374         // should only be used for options that are values on which future decisions will be made,
17375         // and definitely not on options that would be used during initialization.
17376         if(strstr(move, "!!! -") == move) {
17377             ParseArgsFromString(move+4);
17378             return;
17379         }
17380
17381       if (gameMode != EditGame && currentMove != forwardMostMove &&
17382         gameMode != Training) {
17383         DisplayMoveError(_("Displayed move is not current"));
17384       } else {
17385         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17386           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17387         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17388         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17389           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17390           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17391         } else {
17392           DisplayMoveError(_("Could not parse move"));
17393         }
17394       }
17395 }
17396
17397 void
17398 DisplayMove (int moveNumber)
17399 {
17400     char message[MSG_SIZ];
17401     char res[MSG_SIZ];
17402     char cpThinkOutput[MSG_SIZ];
17403
17404     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17405
17406     if (moveNumber == forwardMostMove - 1 ||
17407         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17408
17409         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17410
17411         if (strchr(cpThinkOutput, '\n')) {
17412             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17413         }
17414     } else {
17415         *cpThinkOutput = NULLCHAR;
17416     }
17417
17418     /* [AS] Hide thinking from human user */
17419     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17420         *cpThinkOutput = NULLCHAR;
17421         if( thinkOutput[0] != NULLCHAR ) {
17422             int i;
17423
17424             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17425                 cpThinkOutput[i] = '.';
17426             }
17427             cpThinkOutput[i] = NULLCHAR;
17428             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17429         }
17430     }
17431
17432     if (moveNumber == forwardMostMove - 1 &&
17433         gameInfo.resultDetails != NULL) {
17434         if (gameInfo.resultDetails[0] == NULLCHAR) {
17435           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17436         } else {
17437           snprintf(res, MSG_SIZ, " {%s} %s",
17438                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17439         }
17440     } else {
17441         res[0] = NULLCHAR;
17442     }
17443
17444     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17445         DisplayMessage(res, cpThinkOutput);
17446     } else {
17447       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17448                 WhiteOnMove(moveNumber) ? " " : ".. ",
17449                 parseList[moveNumber], res);
17450         DisplayMessage(message, cpThinkOutput);
17451     }
17452 }
17453
17454 void
17455 DisplayComment (int moveNumber, char *text)
17456 {
17457     char title[MSG_SIZ];
17458
17459     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17460       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17461     } else {
17462       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17463               WhiteOnMove(moveNumber) ? " " : ".. ",
17464               parseList[moveNumber]);
17465     }
17466     if (text != NULL && (appData.autoDisplayComment || commentUp))
17467         CommentPopUp(title, text);
17468 }
17469
17470 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17471  * might be busy thinking or pondering.  It can be omitted if your
17472  * gnuchess is configured to stop thinking immediately on any user
17473  * input.  However, that gnuchess feature depends on the FIONREAD
17474  * ioctl, which does not work properly on some flavors of Unix.
17475  */
17476 void
17477 Attention (ChessProgramState *cps)
17478 {
17479 #if ATTENTION
17480     if (!cps->useSigint) return;
17481     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17482     switch (gameMode) {
17483       case MachinePlaysWhite:
17484       case MachinePlaysBlack:
17485       case TwoMachinesPlay:
17486       case IcsPlayingWhite:
17487       case IcsPlayingBlack:
17488       case AnalyzeMode:
17489       case AnalyzeFile:
17490         /* Skip if we know it isn't thinking */
17491         if (!cps->maybeThinking) return;
17492         if (appData.debugMode)
17493           fprintf(debugFP, "Interrupting %s\n", cps->which);
17494         InterruptChildProcess(cps->pr);
17495         cps->maybeThinking = FALSE;
17496         break;
17497       default:
17498         break;
17499     }
17500 #endif /*ATTENTION*/
17501 }
17502
17503 int
17504 CheckFlags ()
17505 {
17506     if (whiteTimeRemaining <= 0) {
17507         if (!whiteFlag) {
17508             whiteFlag = TRUE;
17509             if (appData.icsActive) {
17510                 if (appData.autoCallFlag &&
17511                     gameMode == IcsPlayingBlack && !blackFlag) {
17512                   SendToICS(ics_prefix);
17513                   SendToICS("flag\n");
17514                 }
17515             } else {
17516                 if (blackFlag) {
17517                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17518                 } else {
17519                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17520                     if (appData.autoCallFlag) {
17521                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17522                         return TRUE;
17523                     }
17524                 }
17525             }
17526         }
17527     }
17528     if (blackTimeRemaining <= 0) {
17529         if (!blackFlag) {
17530             blackFlag = TRUE;
17531             if (appData.icsActive) {
17532                 if (appData.autoCallFlag &&
17533                     gameMode == IcsPlayingWhite && !whiteFlag) {
17534                   SendToICS(ics_prefix);
17535                   SendToICS("flag\n");
17536                 }
17537             } else {
17538                 if (whiteFlag) {
17539                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17540                 } else {
17541                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17542                     if (appData.autoCallFlag) {
17543                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17544                         return TRUE;
17545                     }
17546                 }
17547             }
17548         }
17549     }
17550     return FALSE;
17551 }
17552
17553 void
17554 CheckTimeControl ()
17555 {
17556     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17557         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17558
17559     /*
17560      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17561      */
17562     if ( !WhiteOnMove(forwardMostMove) ) {
17563         /* White made time control */
17564         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17565         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17566         /* [HGM] time odds: correct new time quota for time odds! */
17567                                             / WhitePlayer()->timeOdds;
17568         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17569     } else {
17570         lastBlack -= blackTimeRemaining;
17571         /* Black made time control */
17572         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17573                                             / WhitePlayer()->other->timeOdds;
17574         lastWhite = whiteTimeRemaining;
17575     }
17576 }
17577
17578 void
17579 DisplayBothClocks ()
17580 {
17581     int wom = gameMode == EditPosition ?
17582       !blackPlaysFirst : WhiteOnMove(currentMove);
17583     DisplayWhiteClock(whiteTimeRemaining, wom);
17584     DisplayBlackClock(blackTimeRemaining, !wom);
17585 }
17586
17587
17588 /* Timekeeping seems to be a portability nightmare.  I think everyone
17589    has ftime(), but I'm really not sure, so I'm including some ifdefs
17590    to use other calls if you don't.  Clocks will be less accurate if
17591    you have neither ftime nor gettimeofday.
17592 */
17593
17594 /* VS 2008 requires the #include outside of the function */
17595 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17596 #include <sys/timeb.h>
17597 #endif
17598
17599 /* Get the current time as a TimeMark */
17600 void
17601 GetTimeMark (TimeMark *tm)
17602 {
17603 #if HAVE_GETTIMEOFDAY
17604
17605     struct timeval timeVal;
17606     struct timezone timeZone;
17607
17608     gettimeofday(&timeVal, &timeZone);
17609     tm->sec = (long) timeVal.tv_sec;
17610     tm->ms = (int) (timeVal.tv_usec / 1000L);
17611
17612 #else /*!HAVE_GETTIMEOFDAY*/
17613 #if HAVE_FTIME
17614
17615 // include <sys/timeb.h> / moved to just above start of function
17616     struct timeb timeB;
17617
17618     ftime(&timeB);
17619     tm->sec = (long) timeB.time;
17620     tm->ms = (int) timeB.millitm;
17621
17622 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17623     tm->sec = (long) time(NULL);
17624     tm->ms = 0;
17625 #endif
17626 #endif
17627 }
17628
17629 /* Return the difference in milliseconds between two
17630    time marks.  We assume the difference will fit in a long!
17631 */
17632 long
17633 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17634 {
17635     return 1000L*(tm2->sec - tm1->sec) +
17636            (long) (tm2->ms - tm1->ms);
17637 }
17638
17639
17640 /*
17641  * Code to manage the game clocks.
17642  *
17643  * In tournament play, black starts the clock and then white makes a move.
17644  * We give the human user a slight advantage if he is playing white---the
17645  * clocks don't run until he makes his first move, so it takes zero time.
17646  * Also, we don't account for network lag, so we could get out of sync
17647  * with GNU Chess's clock -- but then, referees are always right.
17648  */
17649
17650 static TimeMark tickStartTM;
17651 static long intendedTickLength;
17652
17653 long
17654 NextTickLength (long timeRemaining)
17655 {
17656     long nominalTickLength, nextTickLength;
17657
17658     if (timeRemaining > 0L && timeRemaining <= 10000L)
17659       nominalTickLength = 100L;
17660     else
17661       nominalTickLength = 1000L;
17662     nextTickLength = timeRemaining % nominalTickLength;
17663     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17664
17665     return nextTickLength;
17666 }
17667
17668 /* Adjust clock one minute up or down */
17669 void
17670 AdjustClock (Boolean which, int dir)
17671 {
17672     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17673     if(which) blackTimeRemaining += 60000*dir;
17674     else      whiteTimeRemaining += 60000*dir;
17675     DisplayBothClocks();
17676     adjustedClock = TRUE;
17677 }
17678
17679 /* Stop clocks and reset to a fresh time control */
17680 void
17681 ResetClocks ()
17682 {
17683     (void) StopClockTimer();
17684     if (appData.icsActive) {
17685         whiteTimeRemaining = blackTimeRemaining = 0;
17686     } else if (searchTime) {
17687         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17688         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17689     } else { /* [HGM] correct new time quote for time odds */
17690         whiteTC = blackTC = fullTimeControlString;
17691         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17692         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17693     }
17694     if (whiteFlag || blackFlag) {
17695         DisplayTitle("");
17696         whiteFlag = blackFlag = FALSE;
17697     }
17698     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17699     DisplayBothClocks();
17700     adjustedClock = FALSE;
17701 }
17702
17703 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17704
17705 /* Decrement running clock by amount of time that has passed */
17706 void
17707 DecrementClocks ()
17708 {
17709     long timeRemaining;
17710     long lastTickLength, fudge;
17711     TimeMark now;
17712
17713     if (!appData.clockMode) return;
17714     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17715
17716     GetTimeMark(&now);
17717
17718     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17719
17720     /* Fudge if we woke up a little too soon */
17721     fudge = intendedTickLength - lastTickLength;
17722     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17723
17724     if (WhiteOnMove(forwardMostMove)) {
17725         if(whiteNPS >= 0) lastTickLength = 0;
17726         timeRemaining = whiteTimeRemaining -= lastTickLength;
17727         if(timeRemaining < 0 && !appData.icsActive) {
17728             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17729             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17730                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17731                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17732             }
17733         }
17734         DisplayWhiteClock(whiteTimeRemaining - fudge,
17735                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17736     } else {
17737         if(blackNPS >= 0) lastTickLength = 0;
17738         timeRemaining = blackTimeRemaining -= lastTickLength;
17739         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17740             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17741             if(suddenDeath) {
17742                 blackStartMove = forwardMostMove;
17743                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17744             }
17745         }
17746         DisplayBlackClock(blackTimeRemaining - fudge,
17747                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17748     }
17749     if (CheckFlags()) return;
17750
17751     if(twoBoards) { // count down secondary board's clocks as well
17752         activePartnerTime -= lastTickLength;
17753         partnerUp = 1;
17754         if(activePartner == 'W')
17755             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17756         else
17757             DisplayBlackClock(activePartnerTime, TRUE);
17758         partnerUp = 0;
17759     }
17760
17761     tickStartTM = now;
17762     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17763     StartClockTimer(intendedTickLength);
17764
17765     /* if the time remaining has fallen below the alarm threshold, sound the
17766      * alarm. if the alarm has sounded and (due to a takeback or time control
17767      * with increment) the time remaining has increased to a level above the
17768      * threshold, reset the alarm so it can sound again.
17769      */
17770
17771     if (appData.icsActive && appData.icsAlarm) {
17772
17773         /* make sure we are dealing with the user's clock */
17774         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17775                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17776            )) return;
17777
17778         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17779             alarmSounded = FALSE;
17780         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17781             PlayAlarmSound();
17782             alarmSounded = TRUE;
17783         }
17784     }
17785 }
17786
17787
17788 /* A player has just moved, so stop the previously running
17789    clock and (if in clock mode) start the other one.
17790    We redisplay both clocks in case we're in ICS mode, because
17791    ICS gives us an update to both clocks after every move.
17792    Note that this routine is called *after* forwardMostMove
17793    is updated, so the last fractional tick must be subtracted
17794    from the color that is *not* on move now.
17795 */
17796 void
17797 SwitchClocks (int newMoveNr)
17798 {
17799     long lastTickLength;
17800     TimeMark now;
17801     int flagged = FALSE;
17802
17803     GetTimeMark(&now);
17804
17805     if (StopClockTimer() && appData.clockMode) {
17806         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17807         if (!WhiteOnMove(forwardMostMove)) {
17808             if(blackNPS >= 0) lastTickLength = 0;
17809             blackTimeRemaining -= lastTickLength;
17810            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17811 //         if(pvInfoList[forwardMostMove].time == -1)
17812                  pvInfoList[forwardMostMove].time =               // use GUI time
17813                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17814         } else {
17815            if(whiteNPS >= 0) lastTickLength = 0;
17816            whiteTimeRemaining -= lastTickLength;
17817            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17818 //         if(pvInfoList[forwardMostMove].time == -1)
17819                  pvInfoList[forwardMostMove].time =
17820                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17821         }
17822         flagged = CheckFlags();
17823     }
17824     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17825     CheckTimeControl();
17826
17827     if (flagged || !appData.clockMode) return;
17828
17829     switch (gameMode) {
17830       case MachinePlaysBlack:
17831       case MachinePlaysWhite:
17832       case BeginningOfGame:
17833         if (pausing) return;
17834         break;
17835
17836       case EditGame:
17837       case PlayFromGameFile:
17838       case IcsExamining:
17839         return;
17840
17841       default:
17842         break;
17843     }
17844
17845     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17846         if(WhiteOnMove(forwardMostMove))
17847              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17848         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17849     }
17850
17851     tickStartTM = now;
17852     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17853       whiteTimeRemaining : blackTimeRemaining);
17854     StartClockTimer(intendedTickLength);
17855 }
17856
17857
17858 /* Stop both clocks */
17859 void
17860 StopClocks ()
17861 {
17862     long lastTickLength;
17863     TimeMark now;
17864
17865     if (!StopClockTimer()) return;
17866     if (!appData.clockMode) return;
17867
17868     GetTimeMark(&now);
17869
17870     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17871     if (WhiteOnMove(forwardMostMove)) {
17872         if(whiteNPS >= 0) lastTickLength = 0;
17873         whiteTimeRemaining -= lastTickLength;
17874         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17875     } else {
17876         if(blackNPS >= 0) lastTickLength = 0;
17877         blackTimeRemaining -= lastTickLength;
17878         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17879     }
17880     CheckFlags();
17881 }
17882
17883 /* Start clock of player on move.  Time may have been reset, so
17884    if clock is already running, stop and restart it. */
17885 void
17886 StartClocks ()
17887 {
17888     (void) StopClockTimer(); /* in case it was running already */
17889     DisplayBothClocks();
17890     if (CheckFlags()) return;
17891
17892     if (!appData.clockMode) return;
17893     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17894
17895     GetTimeMark(&tickStartTM);
17896     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17897       whiteTimeRemaining : blackTimeRemaining);
17898
17899    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17900     whiteNPS = blackNPS = -1;
17901     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17902        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17903         whiteNPS = first.nps;
17904     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17905        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17906         blackNPS = first.nps;
17907     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17908         whiteNPS = second.nps;
17909     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17910         blackNPS = second.nps;
17911     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17912
17913     StartClockTimer(intendedTickLength);
17914 }
17915
17916 char *
17917 TimeString (long ms)
17918 {
17919     long second, minute, hour, day;
17920     char *sign = "";
17921     static char buf[32];
17922
17923     if (ms > 0 && ms <= 9900) {
17924       /* convert milliseconds to tenths, rounding up */
17925       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17926
17927       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17928       return buf;
17929     }
17930
17931     /* convert milliseconds to seconds, rounding up */
17932     /* use floating point to avoid strangeness of integer division
17933        with negative dividends on many machines */
17934     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17935
17936     if (second < 0) {
17937         sign = "-";
17938         second = -second;
17939     }
17940
17941     day = second / (60 * 60 * 24);
17942     second = second % (60 * 60 * 24);
17943     hour = second / (60 * 60);
17944     second = second % (60 * 60);
17945     minute = second / 60;
17946     second = second % 60;
17947
17948     if (day > 0)
17949       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17950               sign, day, hour, minute, second);
17951     else if (hour > 0)
17952       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17953     else
17954       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17955
17956     return buf;
17957 }
17958
17959
17960 /*
17961  * This is necessary because some C libraries aren't ANSI C compliant yet.
17962  */
17963 char *
17964 StrStr (char *string, char *match)
17965 {
17966     int i, length;
17967
17968     length = strlen(match);
17969
17970     for (i = strlen(string) - length; i >= 0; i--, string++)
17971       if (!strncmp(match, string, length))
17972         return string;
17973
17974     return NULL;
17975 }
17976
17977 char *
17978 StrCaseStr (char *string, char *match)
17979 {
17980     int i, j, length;
17981
17982     length = strlen(match);
17983
17984     for (i = strlen(string) - length; i >= 0; i--, string++) {
17985         for (j = 0; j < length; j++) {
17986             if (ToLower(match[j]) != ToLower(string[j]))
17987               break;
17988         }
17989         if (j == length) return string;
17990     }
17991
17992     return NULL;
17993 }
17994
17995 #ifndef _amigados
17996 int
17997 StrCaseCmp (char *s1, char *s2)
17998 {
17999     char c1, c2;
18000
18001     for (;;) {
18002         c1 = ToLower(*s1++);
18003         c2 = ToLower(*s2++);
18004         if (c1 > c2) return 1;
18005         if (c1 < c2) return -1;
18006         if (c1 == NULLCHAR) return 0;
18007     }
18008 }
18009
18010
18011 int
18012 ToLower (int c)
18013 {
18014     return isupper(c) ? tolower(c) : c;
18015 }
18016
18017
18018 int
18019 ToUpper (int c)
18020 {
18021     return islower(c) ? toupper(c) : c;
18022 }
18023 #endif /* !_amigados    */
18024
18025 char *
18026 StrSave (char *s)
18027 {
18028   char *ret;
18029
18030   if ((ret = (char *) malloc(strlen(s) + 1)))
18031     {
18032       safeStrCpy(ret, s, strlen(s)+1);
18033     }
18034   return ret;
18035 }
18036
18037 char *
18038 StrSavePtr (char *s, char **savePtr)
18039 {
18040     if (*savePtr) {
18041         free(*savePtr);
18042     }
18043     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18044       safeStrCpy(*savePtr, s, strlen(s)+1);
18045     }
18046     return(*savePtr);
18047 }
18048
18049 char *
18050 PGNDate ()
18051 {
18052     time_t clock;
18053     struct tm *tm;
18054     char buf[MSG_SIZ];
18055
18056     clock = time((time_t *)NULL);
18057     tm = localtime(&clock);
18058     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18059             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18060     return StrSave(buf);
18061 }
18062
18063
18064 char *
18065 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18066 {
18067     int i, j, fromX, fromY, toX, toY;
18068     int whiteToPlay, haveRights = nrCastlingRights;
18069     char buf[MSG_SIZ];
18070     char *p, *q;
18071     int emptycount;
18072     ChessSquare piece;
18073
18074     whiteToPlay = (gameMode == EditPosition) ?
18075       !blackPlaysFirst : (move % 2 == 0);
18076     p = buf;
18077
18078     /* Piece placement data */
18079     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18080         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18081         emptycount = 0;
18082         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18083             if (boards[move][i][j] == EmptySquare) {
18084                 emptycount++;
18085             } else { ChessSquare piece = boards[move][i][j];
18086                 if (emptycount > 0) {
18087                     if(emptycount<10) /* [HGM] can be >= 10 */
18088                         *p++ = '0' + emptycount;
18089                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18090                     emptycount = 0;
18091                 }
18092                 if(PieceToChar(piece) == '+') {
18093                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18094                     *p++ = '+';
18095                     piece = (ChessSquare)(CHUDEMOTED(piece));
18096                 }
18097                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18098                 if(*p = PieceSuffix(piece)) p++;
18099                 if(p[-1] == '~') {
18100                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18101                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18102                     *p++ = '~';
18103                 }
18104             }
18105         }
18106         if (emptycount > 0) {
18107             if(emptycount<10) /* [HGM] can be >= 10 */
18108                 *p++ = '0' + emptycount;
18109             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18110             emptycount = 0;
18111         }
18112         *p++ = '/';
18113     }
18114     *(p - 1) = ' ';
18115
18116     /* [HGM] print Crazyhouse or Shogi holdings */
18117     if( gameInfo.holdingsWidth ) {
18118         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18119         q = p;
18120         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18121             piece = boards[move][i][BOARD_WIDTH-1];
18122             if( piece != EmptySquare )
18123               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18124                   *p++ = PieceToChar(piece);
18125         }
18126         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18127             piece = boards[move][BOARD_HEIGHT-i-1][0];
18128             if( piece != EmptySquare )
18129               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18130                   *p++ = PieceToChar(piece);
18131         }
18132
18133         if( q == p ) *p++ = '-';
18134         *p++ = ']';
18135         *p++ = ' ';
18136     }
18137
18138     /* Active color */
18139     *p++ = whiteToPlay ? 'w' : 'b';
18140     *p++ = ' ';
18141
18142   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18143     haveRights = 0; q = p;
18144     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18145       piece = boards[move][0][i];
18146       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18147         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18148       }
18149     }
18150     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18151       piece = boards[move][BOARD_HEIGHT-1][i];
18152       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18153         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18154       }
18155     }
18156     if(p == q) *p++ = '-';
18157     *p++ = ' ';
18158   }
18159
18160   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18161     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18162   } else {
18163   if(haveRights) {
18164      int handW=0, handB=0;
18165      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18166         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18167         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18168      }
18169      q = p;
18170      if(appData.fischerCastling) {
18171         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18172            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18173                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18174         } else {
18175        /* [HGM] write directly from rights */
18176            if(boards[move][CASTLING][2] != NoRights &&
18177               boards[move][CASTLING][0] != NoRights   )
18178                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18179            if(boards[move][CASTLING][2] != NoRights &&
18180               boards[move][CASTLING][1] != NoRights   )
18181                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18182         }
18183         if(handB) {
18184            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18185                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18186         } else {
18187            if(boards[move][CASTLING][5] != NoRights &&
18188               boards[move][CASTLING][3] != NoRights   )
18189                 *p++ = boards[move][CASTLING][3] + AAA;
18190            if(boards[move][CASTLING][5] != NoRights &&
18191               boards[move][CASTLING][4] != NoRights   )
18192                 *p++ = boards[move][CASTLING][4] + AAA;
18193         }
18194      } else {
18195
18196         /* [HGM] write true castling rights */
18197         if( nrCastlingRights == 6 ) {
18198             int q, k=0;
18199             if(boards[move][CASTLING][0] != NoRights &&
18200                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18201             q = (boards[move][CASTLING][1] != NoRights &&
18202                  boards[move][CASTLING][2] != NoRights  );
18203             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18204                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18205                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18206                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18207             }
18208             if(q) *p++ = 'Q';
18209             k = 0;
18210             if(boards[move][CASTLING][3] != NoRights &&
18211                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18212             q = (boards[move][CASTLING][4] != NoRights &&
18213                  boards[move][CASTLING][5] != NoRights  );
18214             if(handB) {
18215                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18216                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18217                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18218             }
18219             if(q) *p++ = 'q';
18220         }
18221      }
18222      if (q == p) *p++ = '-'; /* No castling rights */
18223      *p++ = ' ';
18224   }
18225
18226   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18227      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18228      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18229     /* En passant target square */
18230     if (move > backwardMostMove) {
18231         fromX = moveList[move - 1][0] - AAA;
18232         fromY = moveList[move - 1][1] - ONE;
18233         toX = moveList[move - 1][2] - AAA;
18234         toY = moveList[move - 1][3] - ONE;
18235         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18236             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18237             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18238             fromX == toX) {
18239             /* 2-square pawn move just happened */
18240             *p++ = toX + AAA;
18241             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18242         } else {
18243             *p++ = '-';
18244         }
18245     } else if(move == backwardMostMove) {
18246         // [HGM] perhaps we should always do it like this, and forget the above?
18247         if((signed char)boards[move][EP_STATUS] >= 0) {
18248             *p++ = boards[move][EP_STATUS] + AAA;
18249             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18250         } else {
18251             *p++ = '-';
18252         }
18253     } else {
18254         *p++ = '-';
18255     }
18256     *p++ = ' ';
18257   }
18258   }
18259
18260     if(moveCounts)
18261     {   int i = 0, j=move;
18262
18263         /* [HGM] find reversible plies */
18264         if (appData.debugMode) { int k;
18265             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18266             for(k=backwardMostMove; k<=forwardMostMove; k++)
18267                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18268
18269         }
18270
18271         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18272         if( j == backwardMostMove ) i += initialRulePlies;
18273         sprintf(p, "%d ", i);
18274         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18275
18276         /* Fullmove number */
18277         sprintf(p, "%d", (move / 2) + 1);
18278     } else *--p = NULLCHAR;
18279
18280     return StrSave(buf);
18281 }
18282
18283 Boolean
18284 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18285 {
18286     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18287     char *p, c;
18288     int emptycount, virgin[BOARD_FILES];
18289     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18290
18291     p = fen;
18292
18293     /* Piece placement data */
18294     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18295         j = 0;
18296         for (;;) {
18297             if (*p == '/' || *p == ' ' || *p == '[' ) {
18298                 if(j > w) w = j;
18299                 emptycount = gameInfo.boardWidth - j;
18300                 while (emptycount--)
18301                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18302                 if (*p == '/') p++;
18303                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18304                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18305                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18306                     }
18307                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18308                 }
18309                 break;
18310 #if(BOARD_FILES >= 10)*0
18311             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18312                 p++; emptycount=10;
18313                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18314                 while (emptycount--)
18315                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18316 #endif
18317             } else if (*p == '*') {
18318                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18319             } else if (isdigit(*p)) {
18320                 emptycount = *p++ - '0';
18321                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18322                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18323                 while (emptycount--)
18324                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18325             } else if (*p == '<') {
18326                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18327                 else if (i != 0 || !shuffle) return FALSE;
18328                 p++;
18329             } else if (shuffle && *p == '>') {
18330                 p++; // for now ignore closing shuffle range, and assume rank-end
18331             } else if (*p == '?') {
18332                 if (j >= gameInfo.boardWidth) return FALSE;
18333                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18334                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18335             } else if (*p == '+' || isalpha(*p)) {
18336                 char *q, *s = SUFFIXES;
18337                 if (j >= gameInfo.boardWidth) return FALSE;
18338                 if(*p=='+') {
18339                     char c = *++p;
18340                     if(q = strchr(s, p[1])) p++;
18341                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18342                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18343                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18344                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18345                 } else {
18346                     char c = *p++;
18347                     if(q = strchr(s, *p)) p++;
18348                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18349                 }
18350
18351                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18352                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18353                     piece = (ChessSquare) (PROMOTED(piece));
18354                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18355                     p++;
18356                 }
18357                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18358                 if(piece == king) wKingRank = i;
18359                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18360             } else {
18361                 return FALSE;
18362             }
18363         }
18364     }
18365     while (*p == '/' || *p == ' ') p++;
18366
18367     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18368
18369     /* [HGM] by default clear Crazyhouse holdings, if present */
18370     if(gameInfo.holdingsWidth) {
18371        for(i=0; i<BOARD_HEIGHT; i++) {
18372            board[i][0]             = EmptySquare; /* black holdings */
18373            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18374            board[i][1]             = (ChessSquare) 0; /* black counts */
18375            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18376        }
18377     }
18378
18379     /* [HGM] look for Crazyhouse holdings here */
18380     while(*p==' ') p++;
18381     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18382         int swap=0, wcnt=0, bcnt=0;
18383         if(*p == '[') p++;
18384         if(*p == '<') swap++, p++;
18385         if(*p == '-' ) p++; /* empty holdings */ else {
18386             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18387             /* if we would allow FEN reading to set board size, we would   */
18388             /* have to add holdings and shift the board read so far here   */
18389             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18390                 p++;
18391                 if((int) piece >= (int) BlackPawn ) {
18392                     i = (int)piece - (int)BlackPawn;
18393                     i = PieceToNumber((ChessSquare)i);
18394                     if( i >= gameInfo.holdingsSize ) return FALSE;
18395                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18396                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18397                     bcnt++;
18398                 } else {
18399                     i = (int)piece - (int)WhitePawn;
18400                     i = PieceToNumber((ChessSquare)i);
18401                     if( i >= gameInfo.holdingsSize ) return FALSE;
18402                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18403                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18404                     wcnt++;
18405                 }
18406             }
18407             if(subst) { // substitute back-rank question marks by holdings pieces
18408                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18409                     int k, m, n = bcnt + 1;
18410                     if(board[0][j] == ClearBoard) {
18411                         if(!wcnt) return FALSE;
18412                         n = rand() % wcnt;
18413                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18414                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18415                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18416                             break;
18417                         }
18418                     }
18419                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18420                         if(!bcnt) return FALSE;
18421                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18422                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18423                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18424                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18425                             break;
18426                         }
18427                     }
18428                 }
18429                 subst = 0;
18430             }
18431         }
18432         if(*p == ']') p++;
18433     }
18434
18435     if(subst) return FALSE; // substitution requested, but no holdings
18436
18437     while(*p == ' ') p++;
18438
18439     /* Active color */
18440     c = *p++;
18441     if(appData.colorNickNames) {
18442       if( c == appData.colorNickNames[0] ) c = 'w'; else
18443       if( c == appData.colorNickNames[1] ) c = 'b';
18444     }
18445     switch (c) {
18446       case 'w':
18447         *blackPlaysFirst = FALSE;
18448         break;
18449       case 'b':
18450         *blackPlaysFirst = TRUE;
18451         break;
18452       default:
18453         return FALSE;
18454     }
18455
18456     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18457     /* return the extra info in global variiables             */
18458
18459     while(*p==' ') p++;
18460
18461     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18462         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18463         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18464     }
18465
18466     /* set defaults in case FEN is incomplete */
18467     board[EP_STATUS] = EP_UNKNOWN;
18468     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18469     for(i=0; i<nrCastlingRights; i++ ) {
18470         board[CASTLING][i] =
18471             appData.fischerCastling ? NoRights : initialRights[i];
18472     }   /* assume possible unless obviously impossible */
18473     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18474     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18475     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18476                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18477     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18478     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18479     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18480                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18481     FENrulePlies = 0;
18482
18483     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18484       char *q = p;
18485       int w=0, b=0;
18486       while(isalpha(*p)) {
18487         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18488         if(islower(*p)) b |= 1 << (*p++ - 'a');
18489       }
18490       if(*p == '-') p++;
18491       if(p != q) {
18492         board[TOUCHED_W] = ~w;
18493         board[TOUCHED_B] = ~b;
18494         while(*p == ' ') p++;
18495       }
18496     } else
18497
18498     if(nrCastlingRights) {
18499       int fischer = 0;
18500       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18501       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18502           /* castling indicator present, so default becomes no castlings */
18503           for(i=0; i<nrCastlingRights; i++ ) {
18504                  board[CASTLING][i] = NoRights;
18505           }
18506       }
18507       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18508              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18509              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18510              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18511         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18512
18513         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18514             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18515             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18516         }
18517         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18518             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18519         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18520                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18521         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18522                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18523         switch(c) {
18524           case'K':
18525               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18526               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18527               board[CASTLING][2] = whiteKingFile;
18528               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18529               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18530               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18531               break;
18532           case'Q':
18533               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18534               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18535               board[CASTLING][2] = whiteKingFile;
18536               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18537               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18538               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18539               break;
18540           case'k':
18541               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18542               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18543               board[CASTLING][5] = blackKingFile;
18544               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18545               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18546               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18547               break;
18548           case'q':
18549               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18550               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18551               board[CASTLING][5] = blackKingFile;
18552               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18553               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18554               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18555           case '-':
18556               break;
18557           default: /* FRC castlings */
18558               if(c >= 'a') { /* black rights */
18559                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18560                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18561                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18562                   if(i == BOARD_RGHT) break;
18563                   board[CASTLING][5] = i;
18564                   c -= AAA;
18565                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18566                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18567                   if(c > i)
18568                       board[CASTLING][3] = c;
18569                   else
18570                       board[CASTLING][4] = c;
18571               } else { /* white rights */
18572                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18573                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18574                     if(board[0][i] == WhiteKing) break;
18575                   if(i == BOARD_RGHT) break;
18576                   board[CASTLING][2] = i;
18577                   c -= AAA - 'a' + 'A';
18578                   if(board[0][c] >= WhiteKing) break;
18579                   if(c > i)
18580                       board[CASTLING][0] = c;
18581                   else
18582                       board[CASTLING][1] = c;
18583               }
18584         }
18585       }
18586       for(i=0; i<nrCastlingRights; i++)
18587         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18588       if(gameInfo.variant == VariantSChess)
18589         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18590       if(fischer && shuffle) appData.fischerCastling = TRUE;
18591     if (appData.debugMode) {
18592         fprintf(debugFP, "FEN castling rights:");
18593         for(i=0; i<nrCastlingRights; i++)
18594         fprintf(debugFP, " %d", board[CASTLING][i]);
18595         fprintf(debugFP, "\n");
18596     }
18597
18598       while(*p==' ') p++;
18599     }
18600
18601     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18602
18603     /* read e.p. field in games that know e.p. capture */
18604     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18605        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18606        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18607       if(*p=='-') {
18608         p++; board[EP_STATUS] = EP_NONE;
18609       } else {
18610          char c = *p++ - AAA;
18611
18612          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18613          if(*p >= '0' && *p <='9') p++;
18614          board[EP_STATUS] = c;
18615       }
18616     }
18617
18618
18619     if(sscanf(p, "%d", &i) == 1) {
18620         FENrulePlies = i; /* 50-move ply counter */
18621         /* (The move number is still ignored)    */
18622     }
18623
18624     return TRUE;
18625 }
18626
18627 void
18628 EditPositionPasteFEN (char *fen)
18629 {
18630   if (fen != NULL) {
18631     Board initial_position;
18632
18633     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18634       DisplayError(_("Bad FEN position in clipboard"), 0);
18635       return ;
18636     } else {
18637       int savedBlackPlaysFirst = blackPlaysFirst;
18638       EditPositionEvent();
18639       blackPlaysFirst = savedBlackPlaysFirst;
18640       CopyBoard(boards[0], initial_position);
18641       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18642       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18643       DisplayBothClocks();
18644       DrawPosition(FALSE, boards[currentMove]);
18645     }
18646   }
18647 }
18648
18649 static char cseq[12] = "\\   ";
18650
18651 Boolean
18652 set_cont_sequence (char *new_seq)
18653 {
18654     int len;
18655     Boolean ret;
18656
18657     // handle bad attempts to set the sequence
18658         if (!new_seq)
18659                 return 0; // acceptable error - no debug
18660
18661     len = strlen(new_seq);
18662     ret = (len > 0) && (len < sizeof(cseq));
18663     if (ret)
18664       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18665     else if (appData.debugMode)
18666       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18667     return ret;
18668 }
18669
18670 /*
18671     reformat a source message so words don't cross the width boundary.  internal
18672     newlines are not removed.  returns the wrapped size (no null character unless
18673     included in source message).  If dest is NULL, only calculate the size required
18674     for the dest buffer.  lp argument indicats line position upon entry, and it's
18675     passed back upon exit.
18676 */
18677 int
18678 wrap (char *dest, char *src, int count, int width, int *lp)
18679 {
18680     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18681
18682     cseq_len = strlen(cseq);
18683     old_line = line = *lp;
18684     ansi = len = clen = 0;
18685
18686     for (i=0; i < count; i++)
18687     {
18688         if (src[i] == '\033')
18689             ansi = 1;
18690
18691         // if we hit the width, back up
18692         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18693         {
18694             // store i & len in case the word is too long
18695             old_i = i, old_len = len;
18696
18697             // find the end of the last word
18698             while (i && src[i] != ' ' && src[i] != '\n')
18699             {
18700                 i--;
18701                 len--;
18702             }
18703
18704             // word too long?  restore i & len before splitting it
18705             if ((old_i-i+clen) >= width)
18706             {
18707                 i = old_i;
18708                 len = old_len;
18709             }
18710
18711             // extra space?
18712             if (i && src[i-1] == ' ')
18713                 len--;
18714
18715             if (src[i] != ' ' && src[i] != '\n')
18716             {
18717                 i--;
18718                 if (len)
18719                     len--;
18720             }
18721
18722             // now append the newline and continuation sequence
18723             if (dest)
18724                 dest[len] = '\n';
18725             len++;
18726             if (dest)
18727                 strncpy(dest+len, cseq, cseq_len);
18728             len += cseq_len;
18729             line = cseq_len;
18730             clen = cseq_len;
18731             continue;
18732         }
18733
18734         if (dest)
18735             dest[len] = src[i];
18736         len++;
18737         if (!ansi)
18738             line++;
18739         if (src[i] == '\n')
18740             line = 0;
18741         if (src[i] == 'm')
18742             ansi = 0;
18743     }
18744     if (dest && appData.debugMode)
18745     {
18746         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18747             count, width, line, len, *lp);
18748         show_bytes(debugFP, src, count);
18749         fprintf(debugFP, "\ndest: ");
18750         show_bytes(debugFP, dest, len);
18751         fprintf(debugFP, "\n");
18752     }
18753     *lp = dest ? line : old_line;
18754
18755     return len;
18756 }
18757
18758 // [HGM] vari: routines for shelving variations
18759 Boolean modeRestore = FALSE;
18760
18761 void
18762 PushInner (int firstMove, int lastMove)
18763 {
18764         int i, j, nrMoves = lastMove - firstMove;
18765
18766         // push current tail of game on stack
18767         savedResult[storedGames] = gameInfo.result;
18768         savedDetails[storedGames] = gameInfo.resultDetails;
18769         gameInfo.resultDetails = NULL;
18770         savedFirst[storedGames] = firstMove;
18771         savedLast [storedGames] = lastMove;
18772         savedFramePtr[storedGames] = framePtr;
18773         framePtr -= nrMoves; // reserve space for the boards
18774         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18775             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18776             for(j=0; j<MOVE_LEN; j++)
18777                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18778             for(j=0; j<2*MOVE_LEN; j++)
18779                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18780             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18781             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18782             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18783             pvInfoList[firstMove+i-1].depth = 0;
18784             commentList[framePtr+i] = commentList[firstMove+i];
18785             commentList[firstMove+i] = NULL;
18786         }
18787
18788         storedGames++;
18789         forwardMostMove = firstMove; // truncate game so we can start variation
18790 }
18791
18792 void
18793 PushTail (int firstMove, int lastMove)
18794 {
18795         if(appData.icsActive) { // only in local mode
18796                 forwardMostMove = currentMove; // mimic old ICS behavior
18797                 return;
18798         }
18799         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18800
18801         PushInner(firstMove, lastMove);
18802         if(storedGames == 1) GreyRevert(FALSE);
18803         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18804 }
18805
18806 void
18807 PopInner (Boolean annotate)
18808 {
18809         int i, j, nrMoves;
18810         char buf[8000], moveBuf[20];
18811
18812         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18813         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18814         nrMoves = savedLast[storedGames] - currentMove;
18815         if(annotate) {
18816                 int cnt = 10;
18817                 if(!WhiteOnMove(currentMove))
18818                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18819                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18820                 for(i=currentMove; i<forwardMostMove; i++) {
18821                         if(WhiteOnMove(i))
18822                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18823                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18824                         strcat(buf, moveBuf);
18825                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18826                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18827                 }
18828                 strcat(buf, ")");
18829         }
18830         for(i=1; i<=nrMoves; i++) { // copy last variation back
18831             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18832             for(j=0; j<MOVE_LEN; j++)
18833                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18834             for(j=0; j<2*MOVE_LEN; j++)
18835                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18836             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18837             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18838             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18839             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18840             commentList[currentMove+i] = commentList[framePtr+i];
18841             commentList[framePtr+i] = NULL;
18842         }
18843         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18844         framePtr = savedFramePtr[storedGames];
18845         gameInfo.result = savedResult[storedGames];
18846         if(gameInfo.resultDetails != NULL) {
18847             free(gameInfo.resultDetails);
18848       }
18849         gameInfo.resultDetails = savedDetails[storedGames];
18850         forwardMostMove = currentMove + nrMoves;
18851 }
18852
18853 Boolean
18854 PopTail (Boolean annotate)
18855 {
18856         if(appData.icsActive) return FALSE; // only in local mode
18857         if(!storedGames) return FALSE; // sanity
18858         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18859
18860         PopInner(annotate);
18861         if(currentMove < forwardMostMove) ForwardEvent(); else
18862         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18863
18864         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18865         return TRUE;
18866 }
18867
18868 void
18869 CleanupTail ()
18870 {       // remove all shelved variations
18871         int i;
18872         for(i=0; i<storedGames; i++) {
18873             if(savedDetails[i])
18874                 free(savedDetails[i]);
18875             savedDetails[i] = NULL;
18876         }
18877         for(i=framePtr; i<MAX_MOVES; i++) {
18878                 if(commentList[i]) free(commentList[i]);
18879                 commentList[i] = NULL;
18880         }
18881         framePtr = MAX_MOVES-1;
18882         storedGames = 0;
18883 }
18884
18885 void
18886 LoadVariation (int index, char *text)
18887 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18888         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18889         int level = 0, move;
18890
18891         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18892         // first find outermost bracketing variation
18893         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18894             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18895                 if(*p == '{') wait = '}'; else
18896                 if(*p == '[') wait = ']'; else
18897                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18898                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18899             }
18900             if(*p == wait) wait = NULLCHAR; // closing ]} found
18901             p++;
18902         }
18903         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18904         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18905         end[1] = NULLCHAR; // clip off comment beyond variation
18906         ToNrEvent(currentMove-1);
18907         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18908         // kludge: use ParsePV() to append variation to game
18909         move = currentMove;
18910         ParsePV(start, TRUE, TRUE);
18911         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18912         ClearPremoveHighlights();
18913         CommentPopDown();
18914         ToNrEvent(currentMove+1);
18915 }
18916
18917 void
18918 LoadTheme ()
18919 {
18920     char *p, *q, buf[MSG_SIZ];
18921     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18922         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18923         ParseArgsFromString(buf);
18924         ActivateTheme(TRUE); // also redo colors
18925         return;
18926     }
18927     p = nickName;
18928     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18929     {
18930         int len;
18931         q = appData.themeNames;
18932         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18933       if(appData.useBitmaps) {
18934         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18935                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18936                 appData.liteBackTextureMode,
18937                 appData.darkBackTextureMode );
18938       } else {
18939         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18940                 Col2Text(2),   // lightSquareColor
18941                 Col2Text(3) ); // darkSquareColor
18942       }
18943       if(appData.useBorder) {
18944         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18945                 appData.border);
18946       } else {
18947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18948       }
18949       if(appData.useFont) {
18950         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18951                 appData.renderPiecesWithFont,
18952                 appData.fontToPieceTable,
18953                 Col2Text(9),    // appData.fontBackColorWhite
18954                 Col2Text(10) ); // appData.fontForeColorBlack
18955       } else {
18956         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18957                 appData.pieceDirectory);
18958         if(!appData.pieceDirectory[0])
18959           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18960                 Col2Text(0),   // whitePieceColor
18961                 Col2Text(1) ); // blackPieceColor
18962       }
18963       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18964                 Col2Text(4),   // highlightSquareColor
18965                 Col2Text(5) ); // premoveHighlightColor
18966         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18967         if(insert != q) insert[-1] = NULLCHAR;
18968         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18969         if(q)   free(q);
18970     }
18971     ActivateTheme(FALSE);
18972 }