Start implementing rights control in Edit Position mode
[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], avoidMove[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         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1000         return;
1001     }
1002     p = engineName;
1003     while(q = strchr(p, SLASH)) p = q+1;
1004     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1005     if(engineDir[0] != NULLCHAR) {
1006         ASSIGN(appData.directory[i], engineDir); p = engineName;
1007     } else if(p != engineName) { // derive directory from engine path, when not given
1008         p[-1] = 0;
1009         ASSIGN(appData.directory[i], engineName);
1010         p[-1] = SLASH;
1011         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1012     } else { ASSIGN(appData.directory[i], "."); }
1013     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1014     if(params[0]) {
1015         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1016         snprintf(command, MSG_SIZ, "%s %s", p, params);
1017         p = command;
1018     }
1019     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1020     ASSIGN(appData.chessProgram[i], p);
1021     appData.isUCI[i] = isUCI;
1022     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1023     appData.hasOwnBookUCI[i] = hasBook;
1024     if(!nickName[0]) useNick = FALSE;
1025     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1026     if(addToList) {
1027         int len;
1028         char quote;
1029         q = firstChessProgramNames;
1030         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1031         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1032         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1033                         quote, p, quote, appData.directory[i],
1034                         useNick ? " -fn \"" : "",
1035                         useNick ? nickName : "",
1036                         useNick ? "\"" : "",
1037                         v1 ? " -firstProtocolVersion 1" : "",
1038                         hasBook ? "" : " -fNoOwnBookUCI",
1039                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1040                         storeVariant ? " -variant " : "",
1041                         storeVariant ? VariantName(gameInfo.variant) : "");
1042         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1043         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1044         if(insert != q) insert[-1] = NULLCHAR;
1045         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1046         if(q)   free(q);
1047         FloatToFront(&appData.recentEngineList, buf);
1048     }
1049     ReplaceEngine(cps, i);
1050 }
1051
1052 void
1053 InitTimeControls ()
1054 {
1055     int matched, min, sec;
1056     /*
1057      * Parse timeControl resource
1058      */
1059     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1060                           appData.movesPerSession)) {
1061         char buf[MSG_SIZ];
1062         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1063         DisplayFatalError(buf, 0, 2);
1064     }
1065
1066     /*
1067      * Parse searchTime resource
1068      */
1069     if (*appData.searchTime != NULLCHAR) {
1070         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1071         if (matched == 1) {
1072             searchTime = min * 60;
1073         } else if (matched == 2) {
1074             searchTime = min * 60 + sec;
1075         } else {
1076             char buf[MSG_SIZ];
1077             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1078             DisplayFatalError(buf, 0, 2);
1079         }
1080     }
1081 }
1082
1083 void
1084 InitBackEnd1 ()
1085 {
1086
1087     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1088     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1089
1090     GetTimeMark(&programStartTime);
1091     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1092     appData.seedBase = random() + (random()<<15);
1093     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1094
1095     ClearProgramStats();
1096     programStats.ok_to_send = 1;
1097     programStats.seen_stat = 0;
1098
1099     /*
1100      * Initialize game list
1101      */
1102     ListNew(&gameList);
1103
1104
1105     /*
1106      * Internet chess server status
1107      */
1108     if (appData.icsActive) {
1109         appData.matchMode = FALSE;
1110         appData.matchGames = 0;
1111 #if ZIPPY
1112         appData.noChessProgram = !appData.zippyPlay;
1113 #else
1114         appData.zippyPlay = FALSE;
1115         appData.zippyTalk = FALSE;
1116         appData.noChessProgram = TRUE;
1117 #endif
1118         if (*appData.icsHelper != NULLCHAR) {
1119             appData.useTelnet = TRUE;
1120             appData.telnetProgram = appData.icsHelper;
1121         }
1122     } else {
1123         appData.zippyTalk = appData.zippyPlay = FALSE;
1124     }
1125
1126     /* [AS] Initialize pv info list [HGM] and game state */
1127     {
1128         int i, j;
1129
1130         for( i=0; i<=framePtr; i++ ) {
1131             pvInfoList[i].depth = -1;
1132             boards[i][EP_STATUS] = EP_NONE;
1133             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1134         }
1135     }
1136
1137     InitTimeControls();
1138
1139     /* [AS] Adjudication threshold */
1140     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1141
1142     InitEngine(&first, 0);
1143     InitEngine(&second, 1);
1144     CommonEngineInit();
1145
1146     pairing.which = "pairing"; // pairing engine
1147     pairing.pr = NoProc;
1148     pairing.isr = NULL;
1149     pairing.program = appData.pairingEngine;
1150     pairing.host = "localhost";
1151     pairing.dir = ".";
1152
1153     if (appData.icsActive) {
1154         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1155     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1156         appData.clockMode = FALSE;
1157         first.sendTime = second.sendTime = 0;
1158     }
1159
1160 #if ZIPPY
1161     /* Override some settings from environment variables, for backward
1162        compatibility.  Unfortunately it's not feasible to have the env
1163        vars just set defaults, at least in xboard.  Ugh.
1164     */
1165     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1166       ZippyInit();
1167     }
1168 #endif
1169
1170     if (!appData.icsActive) {
1171       char buf[MSG_SIZ];
1172       int len;
1173
1174       /* Check for variants that are supported only in ICS mode,
1175          or not at all.  Some that are accepted here nevertheless
1176          have bugs; see comments below.
1177       */
1178       VariantClass variant = StringToVariant(appData.variant);
1179       switch (variant) {
1180       case VariantBughouse:     /* need four players and two boards */
1181       case VariantKriegspiel:   /* need to hide pieces and move details */
1182         /* case VariantFischeRandom: (Fabien: moved below) */
1183         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1184         if( (len >= MSG_SIZ) && appData.debugMode )
1185           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1186
1187         DisplayFatalError(buf, 0, 2);
1188         return;
1189
1190       case VariantUnknown:
1191       case VariantLoadable:
1192       case Variant29:
1193       case Variant30:
1194       case Variant31:
1195       case Variant32:
1196       case Variant33:
1197       case Variant34:
1198       case Variant35:
1199       case Variant36:
1200       default:
1201         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1202         if( (len >= MSG_SIZ) && appData.debugMode )
1203           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1204
1205         DisplayFatalError(buf, 0, 2);
1206         return;
1207
1208       case VariantNormal:     /* definitely works! */
1209         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1210           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1211           return;
1212         }
1213       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1214       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1215       case VariantGothic:     /* [HGM] should work */
1216       case VariantCapablanca: /* [HGM] should work */
1217       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1218       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1219       case VariantChu:        /* [HGM] experimental */
1220       case VariantKnightmate: /* [HGM] should work */
1221       case VariantCylinder:   /* [HGM] untested */
1222       case VariantFalcon:     /* [HGM] untested */
1223       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1224                                  offboard interposition not understood */
1225       case VariantWildCastle: /* pieces not automatically shuffled */
1226       case VariantNoCastle:   /* pieces not automatically shuffled */
1227       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1228       case VariantLosers:     /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantSuicide:    /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantGiveaway:   /* should work except for win condition,
1233                                  and doesn't know captures are mandatory */
1234       case VariantTwoKings:   /* should work */
1235       case VariantAtomic:     /* should work except for win condition */
1236       case Variant3Check:     /* should work except for win condition */
1237       case VariantShatranj:   /* should work except for all win conditions */
1238       case VariantMakruk:     /* should work except for draw countdown */
1239       case VariantASEAN :     /* should work except for draw countdown */
1240       case VariantBerolina:   /* might work if TestLegality is off */
1241       case VariantCapaRandom: /* should work */
1242       case VariantJanus:      /* should work */
1243       case VariantSuper:      /* experimental */
1244       case VariantGreat:      /* experimental, requires legality testing to be off */
1245       case VariantSChess:     /* S-Chess, should work */
1246       case VariantGrand:      /* should work */
1247       case VariantSpartan:    /* should work */
1248       case VariantLion:       /* should work */
1249       case VariantChuChess:   /* should work */
1250         break;
1251       }
1252     }
1253
1254 }
1255
1256 int
1257 NextIntegerFromString (char ** str, long * value)
1258 {
1259     int result = -1;
1260     char * s = *str;
1261
1262     while( *s == ' ' || *s == '\t' ) {
1263         s++;
1264     }
1265
1266     *value = 0;
1267
1268     if( *s >= '0' && *s <= '9' ) {
1269         while( *s >= '0' && *s <= '9' ) {
1270             *value = *value * 10 + (*s - '0');
1271             s++;
1272         }
1273
1274         result = 0;
1275     }
1276
1277     *str = s;
1278
1279     return result;
1280 }
1281
1282 int
1283 NextTimeControlFromString (char ** str, long * value)
1284 {
1285     long temp;
1286     int result = NextIntegerFromString( str, &temp );
1287
1288     if( result == 0 ) {
1289         *value = temp * 60; /* Minutes */
1290         if( **str == ':' ) {
1291             (*str)++;
1292             result = NextIntegerFromString( str, &temp );
1293             *value += temp; /* Seconds */
1294         }
1295     }
1296
1297     return result;
1298 }
1299
1300 int
1301 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1302 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1303     int result = -1, type = 0; long temp, temp2;
1304
1305     if(**str != ':') return -1; // old params remain in force!
1306     (*str)++;
1307     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1308     if( NextIntegerFromString( str, &temp ) ) return -1;
1309     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1310
1311     if(**str != '/') {
1312         /* time only: incremental or sudden-death time control */
1313         if(**str == '+') { /* increment follows; read it */
1314             (*str)++;
1315             if(**str == '!') type = *(*str)++; // Bronstein TC
1316             if(result = NextIntegerFromString( str, &temp2)) return -1;
1317             *inc = temp2 * 1000;
1318             if(**str == '.') { // read fraction of increment
1319                 char *start = ++(*str);
1320                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1321                 temp2 *= 1000;
1322                 while(start++ < *str) temp2 /= 10;
1323                 *inc += temp2;
1324             }
1325         } else *inc = 0;
1326         *moves = 0; *tc = temp * 1000; *incType = type;
1327         return 0;
1328     }
1329
1330     (*str)++; /* classical time control */
1331     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1332
1333     if(result == 0) {
1334         *moves = temp;
1335         *tc    = temp2 * 1000;
1336         *inc   = 0;
1337         *incType = type;
1338     }
1339     return result;
1340 }
1341
1342 int
1343 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1344 {   /* [HGM] get time to add from the multi-session time-control string */
1345     int incType, moves=1; /* kludge to force reading of first session */
1346     long time, increment;
1347     char *s = tcString;
1348
1349     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1350     do {
1351         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1352         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1353         if(movenr == -1) return time;    /* last move before new session     */
1354         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1355         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1356         if(!moves) return increment;     /* current session is incremental   */
1357         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1358     } while(movenr >= -1);               /* try again for next session       */
1359
1360     return 0; // no new time quota on this move
1361 }
1362
1363 int
1364 ParseTimeControl (char *tc, float ti, int mps)
1365 {
1366   long tc1;
1367   long tc2;
1368   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1369   int min, sec=0;
1370
1371   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1372   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1373       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1374   if(ti > 0) {
1375
1376     if(mps)
1377       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1378     else
1379       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1380   } else {
1381     if(mps)
1382       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1383     else
1384       snprintf(buf, MSG_SIZ, ":%s", mytc);
1385   }
1386   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1387
1388   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1389     return FALSE;
1390   }
1391
1392   if( *tc == '/' ) {
1393     /* Parse second time control */
1394     tc++;
1395
1396     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1397       return FALSE;
1398     }
1399
1400     if( tc2 == 0 ) {
1401       return FALSE;
1402     }
1403
1404     timeControl_2 = tc2 * 1000;
1405   }
1406   else {
1407     timeControl_2 = 0;
1408   }
1409
1410   if( tc1 == 0 ) {
1411     return FALSE;
1412   }
1413
1414   timeControl = tc1 * 1000;
1415
1416   if (ti >= 0) {
1417     timeIncrement = ti * 1000;  /* convert to ms */
1418     movesPerSession = 0;
1419   } else {
1420     timeIncrement = 0;
1421     movesPerSession = mps;
1422   }
1423   return TRUE;
1424 }
1425
1426 void
1427 InitBackEnd2 ()
1428 {
1429     if (appData.debugMode) {
1430 #    ifdef __GIT_VERSION
1431       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1432 #    else
1433       fprintf(debugFP, "Version: %s\n", programVersion);
1434 #    endif
1435     }
1436     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1437
1438     set_cont_sequence(appData.wrapContSeq);
1439     if (appData.matchGames > 0) {
1440         appData.matchMode = TRUE;
1441     } else if (appData.matchMode) {
1442         appData.matchGames = 1;
1443     }
1444     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1445         appData.matchGames = appData.sameColorGames;
1446     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1447         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1448         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1449     }
1450     Reset(TRUE, FALSE);
1451     if (appData.noChessProgram || first.protocolVersion == 1) {
1452       InitBackEnd3();
1453     } else {
1454       /* kludge: allow timeout for initial "feature" commands */
1455       FreezeUI();
1456       DisplayMessage("", _("Starting chess program"));
1457       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1458     }
1459 }
1460
1461 int
1462 CalculateIndex (int index, int gameNr)
1463 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1464     int res;
1465     if(index > 0) return index; // fixed nmber
1466     if(index == 0) return 1;
1467     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1468     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1469     return res;
1470 }
1471
1472 int
1473 LoadGameOrPosition (int gameNr)
1474 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1475     if (*appData.loadGameFile != NULLCHAR) {
1476         if (!LoadGameFromFile(appData.loadGameFile,
1477                 CalculateIndex(appData.loadGameIndex, gameNr),
1478                               appData.loadGameFile, FALSE)) {
1479             DisplayFatalError(_("Bad game file"), 0, 1);
1480             return 0;
1481         }
1482     } else if (*appData.loadPositionFile != NULLCHAR) {
1483         if (!LoadPositionFromFile(appData.loadPositionFile,
1484                 CalculateIndex(appData.loadPositionIndex, gameNr),
1485                                   appData.loadPositionFile)) {
1486             DisplayFatalError(_("Bad position file"), 0, 1);
1487             return 0;
1488         }
1489     }
1490     return 1;
1491 }
1492
1493 void
1494 ReserveGame (int gameNr, char resChar)
1495 {
1496     FILE *tf = fopen(appData.tourneyFile, "r+");
1497     char *p, *q, c, buf[MSG_SIZ];
1498     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1499     safeStrCpy(buf, lastMsg, MSG_SIZ);
1500     DisplayMessage(_("Pick new game"), "");
1501     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1502     ParseArgsFromFile(tf);
1503     p = q = appData.results;
1504     if(appData.debugMode) {
1505       char *r = appData.participants;
1506       fprintf(debugFP, "results = '%s'\n", p);
1507       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1508       fprintf(debugFP, "\n");
1509     }
1510     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1511     nextGame = q - p;
1512     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1513     safeStrCpy(q, p, strlen(p) + 2);
1514     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1515     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1516     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1517         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1518         q[nextGame] = '*';
1519     }
1520     fseek(tf, -(strlen(p)+4), SEEK_END);
1521     c = fgetc(tf);
1522     if(c != '"') // depending on DOS or Unix line endings we can be one off
1523          fseek(tf, -(strlen(p)+2), SEEK_END);
1524     else fseek(tf, -(strlen(p)+3), SEEK_END);
1525     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1526     DisplayMessage(buf, "");
1527     free(p); appData.results = q;
1528     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1529        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1530       int round = appData.defaultMatchGames * appData.tourneyType;
1531       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1532          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1533         UnloadEngine(&first);  // next game belongs to other pairing;
1534         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1535     }
1536     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1537 }
1538
1539 void
1540 MatchEvent (int mode)
1541 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1542         int dummy;
1543         if(matchMode) { // already in match mode: switch it off
1544             abortMatch = TRUE;
1545             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1546             return;
1547         }
1548 //      if(gameMode != BeginningOfGame) {
1549 //          DisplayError(_("You can only start a match from the initial position."), 0);
1550 //          return;
1551 //      }
1552         abortMatch = FALSE;
1553         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1554         /* Set up machine vs. machine match */
1555         nextGame = 0;
1556         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1557         if(appData.tourneyFile[0]) {
1558             ReserveGame(-1, 0);
1559             if(nextGame > appData.matchGames) {
1560                 char buf[MSG_SIZ];
1561                 if(strchr(appData.results, '*') == NULL) {
1562                     FILE *f;
1563                     appData.tourneyCycles++;
1564                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1565                         fclose(f);
1566                         NextTourneyGame(-1, &dummy);
1567                         ReserveGame(-1, 0);
1568                         if(nextGame <= appData.matchGames) {
1569                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1570                             matchMode = mode;
1571                             ScheduleDelayedEvent(NextMatchGame, 10000);
1572                             return;
1573                         }
1574                     }
1575                 }
1576                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1577                 DisplayError(buf, 0);
1578                 appData.tourneyFile[0] = 0;
1579                 return;
1580             }
1581         } else
1582         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1583             DisplayFatalError(_("Can't have a match with no chess programs"),
1584                               0, 2);
1585             return;
1586         }
1587         matchMode = mode;
1588         matchGame = roundNr = 1;
1589         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1590         NextMatchGame();
1591 }
1592
1593 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1594
1595 void
1596 InitBackEnd3 P((void))
1597 {
1598     GameMode initialMode;
1599     char buf[MSG_SIZ];
1600     int err, len;
1601
1602     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1603        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1604         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1605        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1606        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1607         char c, *q = first.variants, *p = strchr(q, ',');
1608         if(p) *p = NULLCHAR;
1609         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1610             int w, h, s;
1611             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1612                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1613             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1614             Reset(TRUE, FALSE);         // and re-initialize
1615         }
1616         if(p) *p = ',';
1617     }
1618
1619     InitChessProgram(&first, startedFromSetupPosition);
1620
1621     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1622         free(programVersion);
1623         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1624         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1625         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1626     }
1627
1628     if (appData.icsActive) {
1629 #ifdef WIN32
1630         /* [DM] Make a console window if needed [HGM] merged ifs */
1631         ConsoleCreate();
1632 #endif
1633         err = establish();
1634         if (err != 0)
1635           {
1636             if (*appData.icsCommPort != NULLCHAR)
1637               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1638                              appData.icsCommPort);
1639             else
1640               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1641                         appData.icsHost, appData.icsPort);
1642
1643             if( (len >= MSG_SIZ) && appData.debugMode )
1644               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1645
1646             DisplayFatalError(buf, err, 1);
1647             return;
1648         }
1649         SetICSMode();
1650         telnetISR =
1651           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1652         fromUserISR =
1653           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1654         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1655             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1656     } else if (appData.noChessProgram) {
1657         SetNCPMode();
1658     } else {
1659         SetGNUMode();
1660     }
1661
1662     if (*appData.cmailGameName != NULLCHAR) {
1663         SetCmailMode();
1664         OpenLoopback(&cmailPR);
1665         cmailISR =
1666           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1667     }
1668
1669     ThawUI();
1670     DisplayMessage("", "");
1671     if (StrCaseCmp(appData.initialMode, "") == 0) {
1672       initialMode = BeginningOfGame;
1673       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1674         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1675         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1676         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1677         ModeHighlight();
1678       }
1679     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1680       initialMode = TwoMachinesPlay;
1681     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1682       initialMode = AnalyzeFile;
1683     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1684       initialMode = AnalyzeMode;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1686       initialMode = MachinePlaysWhite;
1687     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1688       initialMode = MachinePlaysBlack;
1689     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1690       initialMode = EditGame;
1691     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1692       initialMode = EditPosition;
1693     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1694       initialMode = Training;
1695     } else {
1696       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1697       if( (len >= MSG_SIZ) && appData.debugMode )
1698         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1699
1700       DisplayFatalError(buf, 0, 2);
1701       return;
1702     }
1703
1704     if (appData.matchMode) {
1705         if(appData.tourneyFile[0]) { // start tourney from command line
1706             FILE *f;
1707             if(f = fopen(appData.tourneyFile, "r")) {
1708                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1709                 fclose(f);
1710                 appData.clockMode = TRUE;
1711                 SetGNUMode();
1712             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1713         }
1714         MatchEvent(TRUE);
1715     } else if (*appData.cmailGameName != NULLCHAR) {
1716         /* Set up cmail mode */
1717         ReloadCmailMsgEvent(TRUE);
1718     } else {
1719         /* Set up other modes */
1720         if (initialMode == AnalyzeFile) {
1721           if (*appData.loadGameFile == NULLCHAR) {
1722             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1723             return;
1724           }
1725         }
1726         if (*appData.loadGameFile != NULLCHAR) {
1727             (void) LoadGameFromFile(appData.loadGameFile,
1728                                     appData.loadGameIndex,
1729                                     appData.loadGameFile, TRUE);
1730         } else if (*appData.loadPositionFile != NULLCHAR) {
1731             (void) LoadPositionFromFile(appData.loadPositionFile,
1732                                         appData.loadPositionIndex,
1733                                         appData.loadPositionFile);
1734             /* [HGM] try to make self-starting even after FEN load */
1735             /* to allow automatic setup of fairy variants with wtm */
1736             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1737                 gameMode = BeginningOfGame;
1738                 setboardSpoiledMachineBlack = 1;
1739             }
1740             /* [HGM] loadPos: make that every new game uses the setup */
1741             /* from file as long as we do not switch variant          */
1742             if(!blackPlaysFirst) {
1743                 startedFromPositionFile = TRUE;
1744                 CopyBoard(filePosition, boards[0]);
1745                 CopyBoard(initialPosition, boards[0]);
1746             }
1747         } else if(*appData.fen != NULLCHAR) {
1748             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1749                 startedFromPositionFile = TRUE;
1750                 Reset(TRUE, TRUE);
1751             }
1752         }
1753         if (initialMode == AnalyzeMode) {
1754           if (appData.noChessProgram) {
1755             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1756             return;
1757           }
1758           if (appData.icsActive) {
1759             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1760             return;
1761           }
1762           AnalyzeModeEvent();
1763         } else if (initialMode == AnalyzeFile) {
1764           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1765           ShowThinkingEvent();
1766           AnalyzeFileEvent();
1767           AnalysisPeriodicEvent(1);
1768         } else if (initialMode == MachinePlaysWhite) {
1769           if (appData.noChessProgram) {
1770             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1771                               0, 2);
1772             return;
1773           }
1774           if (appData.icsActive) {
1775             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1776                               0, 2);
1777             return;
1778           }
1779           MachineWhiteEvent();
1780         } else if (initialMode == MachinePlaysBlack) {
1781           if (appData.noChessProgram) {
1782             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1783                               0, 2);
1784             return;
1785           }
1786           if (appData.icsActive) {
1787             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1788                               0, 2);
1789             return;
1790           }
1791           MachineBlackEvent();
1792         } else if (initialMode == TwoMachinesPlay) {
1793           if (appData.noChessProgram) {
1794             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1795                               0, 2);
1796             return;
1797           }
1798           if (appData.icsActive) {
1799             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1800                               0, 2);
1801             return;
1802           }
1803           TwoMachinesEvent();
1804         } else if (initialMode == EditGame) {
1805           EditGameEvent();
1806         } else if (initialMode == EditPosition) {
1807           EditPositionEvent();
1808         } else if (initialMode == Training) {
1809           if (*appData.loadGameFile == NULLCHAR) {
1810             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1811             return;
1812           }
1813           TrainingEvent();
1814         }
1815     }
1816 }
1817
1818 void
1819 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1820 {
1821     DisplayBook(current+1);
1822
1823     MoveHistorySet( movelist, first, last, current, pvInfoList );
1824
1825     EvalGraphSet( first, last, current, pvInfoList );
1826
1827     MakeEngineOutputTitle();
1828 }
1829
1830 /*
1831  * Establish will establish a contact to a remote host.port.
1832  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1833  *  used to talk to the host.
1834  * Returns 0 if okay, error code if not.
1835  */
1836 int
1837 establish ()
1838 {
1839     char buf[MSG_SIZ];
1840
1841     if (*appData.icsCommPort != NULLCHAR) {
1842         /* Talk to the host through a serial comm port */
1843         return OpenCommPort(appData.icsCommPort, &icsPR);
1844
1845     } else if (*appData.gateway != NULLCHAR) {
1846         if (*appData.remoteShell == NULLCHAR) {
1847             /* Use the rcmd protocol to run telnet program on a gateway host */
1848             snprintf(buf, sizeof(buf), "%s %s %s",
1849                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1850             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1851
1852         } else {
1853             /* Use the rsh program to run telnet program on a gateway host */
1854             if (*appData.remoteUser == NULLCHAR) {
1855                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1856                         appData.gateway, appData.telnetProgram,
1857                         appData.icsHost, appData.icsPort);
1858             } else {
1859                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1860                         appData.remoteShell, appData.gateway,
1861                         appData.remoteUser, appData.telnetProgram,
1862                         appData.icsHost, appData.icsPort);
1863             }
1864             return StartChildProcess(buf, "", &icsPR);
1865
1866         }
1867     } else if (appData.useTelnet) {
1868         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1869
1870     } else {
1871         /* TCP socket interface differs somewhat between
1872            Unix and NT; handle details in the front end.
1873            */
1874         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1875     }
1876 }
1877
1878 void
1879 EscapeExpand (char *p, char *q)
1880 {       // [HGM] initstring: routine to shape up string arguments
1881         while(*p++ = *q++) if(p[-1] == '\\')
1882             switch(*q++) {
1883                 case 'n': p[-1] = '\n'; break;
1884                 case 'r': p[-1] = '\r'; break;
1885                 case 't': p[-1] = '\t'; break;
1886                 case '\\': p[-1] = '\\'; break;
1887                 case 0: *p = 0; return;
1888                 default: p[-1] = q[-1]; break;
1889             }
1890 }
1891
1892 void
1893 show_bytes (FILE *fp, char *buf, int count)
1894 {
1895     while (count--) {
1896         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1897             fprintf(fp, "\\%03o", *buf & 0xff);
1898         } else {
1899             putc(*buf, fp);
1900         }
1901         buf++;
1902     }
1903     fflush(fp);
1904 }
1905
1906 /* Returns an errno value */
1907 int
1908 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1909 {
1910     char buf[8192], *p, *q, *buflim;
1911     int left, newcount, outcount;
1912
1913     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1914         *appData.gateway != NULLCHAR) {
1915         if (appData.debugMode) {
1916             fprintf(debugFP, ">ICS: ");
1917             show_bytes(debugFP, message, count);
1918             fprintf(debugFP, "\n");
1919         }
1920         return OutputToProcess(pr, message, count, outError);
1921     }
1922
1923     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1924     p = message;
1925     q = buf;
1926     left = count;
1927     newcount = 0;
1928     while (left) {
1929         if (q >= buflim) {
1930             if (appData.debugMode) {
1931                 fprintf(debugFP, ">ICS: ");
1932                 show_bytes(debugFP, buf, newcount);
1933                 fprintf(debugFP, "\n");
1934             }
1935             outcount = OutputToProcess(pr, buf, newcount, outError);
1936             if (outcount < newcount) return -1; /* to be sure */
1937             q = buf;
1938             newcount = 0;
1939         }
1940         if (*p == '\n') {
1941             *q++ = '\r';
1942             newcount++;
1943         } else if (((unsigned char) *p) == TN_IAC) {
1944             *q++ = (char) TN_IAC;
1945             newcount ++;
1946         }
1947         *q++ = *p++;
1948         newcount++;
1949         left--;
1950     }
1951     if (appData.debugMode) {
1952         fprintf(debugFP, ">ICS: ");
1953         show_bytes(debugFP, buf, newcount);
1954         fprintf(debugFP, "\n");
1955     }
1956     outcount = OutputToProcess(pr, buf, newcount, outError);
1957     if (outcount < newcount) return -1; /* to be sure */
1958     return count;
1959 }
1960
1961 void
1962 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1963 {
1964     int outError, outCount;
1965     static int gotEof = 0;
1966     static FILE *ini;
1967
1968     /* Pass data read from player on to ICS */
1969     if (count > 0) {
1970         gotEof = 0;
1971         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1972         if (outCount < count) {
1973             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1974         }
1975         if(have_sent_ICS_logon == 2) {
1976           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1977             fprintf(ini, "%s", message);
1978             have_sent_ICS_logon = 3;
1979           } else
1980             have_sent_ICS_logon = 1;
1981         } else if(have_sent_ICS_logon == 3) {
1982             fprintf(ini, "%s", message);
1983             fclose(ini);
1984           have_sent_ICS_logon = 1;
1985         }
1986     } else if (count < 0) {
1987         RemoveInputSource(isr);
1988         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1989     } else if (gotEof++ > 0) {
1990         RemoveInputSource(isr);
1991         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1992     }
1993 }
1994
1995 void
1996 KeepAlive ()
1997 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1998     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1999     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2000     SendToICS("date\n");
2001     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2002 }
2003
2004 /* added routine for printf style output to ics */
2005 void
2006 ics_printf (char *format, ...)
2007 {
2008     char buffer[MSG_SIZ];
2009     va_list args;
2010
2011     va_start(args, format);
2012     vsnprintf(buffer, sizeof(buffer), format, args);
2013     buffer[sizeof(buffer)-1] = '\0';
2014     SendToICS(buffer);
2015     va_end(args);
2016 }
2017
2018 void
2019 SendToICS (char *s)
2020 {
2021     int count, outCount, outError;
2022
2023     if (icsPR == NoProc) return;
2024
2025     count = strlen(s);
2026     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2027     if (outCount < count) {
2028         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2029     }
2030 }
2031
2032 /* This is used for sending logon scripts to the ICS. Sending
2033    without a delay causes problems when using timestamp on ICC
2034    (at least on my machine). */
2035 void
2036 SendToICSDelayed (char *s, long msdelay)
2037 {
2038     int count, outCount, outError;
2039
2040     if (icsPR == NoProc) return;
2041
2042     count = strlen(s);
2043     if (appData.debugMode) {
2044         fprintf(debugFP, ">ICS: ");
2045         show_bytes(debugFP, s, count);
2046         fprintf(debugFP, "\n");
2047     }
2048     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2049                                       msdelay);
2050     if (outCount < count) {
2051         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2052     }
2053 }
2054
2055
2056 /* Remove all highlighting escape sequences in s
2057    Also deletes any suffix starting with '('
2058    */
2059 char *
2060 StripHighlightAndTitle (char *s)
2061 {
2062     static char retbuf[MSG_SIZ];
2063     char *p = retbuf;
2064
2065     while (*s != NULLCHAR) {
2066         while (*s == '\033') {
2067             while (*s != NULLCHAR && !isalpha(*s)) s++;
2068             if (*s != NULLCHAR) s++;
2069         }
2070         while (*s != NULLCHAR && *s != '\033') {
2071             if (*s == '(' || *s == '[') {
2072                 *p = NULLCHAR;
2073                 return retbuf;
2074             }
2075             *p++ = *s++;
2076         }
2077     }
2078     *p = NULLCHAR;
2079     return retbuf;
2080 }
2081
2082 /* Remove all highlighting escape sequences in s */
2083 char *
2084 StripHighlight (char *s)
2085 {
2086     static char retbuf[MSG_SIZ];
2087     char *p = retbuf;
2088
2089     while (*s != NULLCHAR) {
2090         while (*s == '\033') {
2091             while (*s != NULLCHAR && !isalpha(*s)) s++;
2092             if (*s != NULLCHAR) s++;
2093         }
2094         while (*s != NULLCHAR && *s != '\033') {
2095             *p++ = *s++;
2096         }
2097     }
2098     *p = NULLCHAR;
2099     return retbuf;
2100 }
2101
2102 char engineVariant[MSG_SIZ];
2103 char *variantNames[] = VARIANT_NAMES;
2104 char *
2105 VariantName (VariantClass v)
2106 {
2107     if(v == VariantUnknown || *engineVariant) return engineVariant;
2108     return variantNames[v];
2109 }
2110
2111
2112 /* Identify a variant from the strings the chess servers use or the
2113    PGN Variant tag names we use. */
2114 VariantClass
2115 StringToVariant (char *e)
2116 {
2117     char *p;
2118     int wnum = -1;
2119     VariantClass v = VariantNormal;
2120     int i, found = FALSE;
2121     char buf[MSG_SIZ], c;
2122     int len;
2123
2124     if (!e) return v;
2125
2126     /* [HGM] skip over optional board-size prefixes */
2127     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2128         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2129         while( *e++ != '_');
2130     }
2131
2132     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2133         v = VariantNormal;
2134         found = TRUE;
2135     } else
2136     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2137       if (p = StrCaseStr(e, variantNames[i])) {
2138         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2139         v = (VariantClass) i;
2140         found = TRUE;
2141         break;
2142       }
2143     }
2144
2145     if (!found) {
2146       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2147           || StrCaseStr(e, "wild/fr")
2148           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2149         v = VariantFischeRandom;
2150       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2151                  (i = 1, p = StrCaseStr(e, "w"))) {
2152         p += i;
2153         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2154         if (isdigit(*p)) {
2155           wnum = atoi(p);
2156         } else {
2157           wnum = -1;
2158         }
2159         switch (wnum) {
2160         case 0: /* FICS only, actually */
2161         case 1:
2162           /* Castling legal even if K starts on d-file */
2163           v = VariantWildCastle;
2164           break;
2165         case 2:
2166         case 3:
2167         case 4:
2168           /* Castling illegal even if K & R happen to start in
2169              normal positions. */
2170           v = VariantNoCastle;
2171           break;
2172         case 5:
2173         case 7:
2174         case 8:
2175         case 10:
2176         case 11:
2177         case 12:
2178         case 13:
2179         case 14:
2180         case 15:
2181         case 18:
2182         case 19:
2183           /* Castling legal iff K & R start in normal positions */
2184           v = VariantNormal;
2185           break;
2186         case 6:
2187         case 20:
2188         case 21:
2189           /* Special wilds for position setup; unclear what to do here */
2190           v = VariantLoadable;
2191           break;
2192         case 9:
2193           /* Bizarre ICC game */
2194           v = VariantTwoKings;
2195           break;
2196         case 16:
2197           v = VariantKriegspiel;
2198           break;
2199         case 17:
2200           v = VariantLosers;
2201           break;
2202         case 22:
2203           v = VariantFischeRandom;
2204           break;
2205         case 23:
2206           v = VariantCrazyhouse;
2207           break;
2208         case 24:
2209           v = VariantBughouse;
2210           break;
2211         case 25:
2212           v = Variant3Check;
2213           break;
2214         case 26:
2215           /* Not quite the same as FICS suicide! */
2216           v = VariantGiveaway;
2217           break;
2218         case 27:
2219           v = VariantAtomic;
2220           break;
2221         case 28:
2222           v = VariantShatranj;
2223           break;
2224
2225         /* Temporary names for future ICC types.  The name *will* change in
2226            the next xboard/WinBoard release after ICC defines it. */
2227         case 29:
2228           v = Variant29;
2229           break;
2230         case 30:
2231           v = Variant30;
2232           break;
2233         case 31:
2234           v = Variant31;
2235           break;
2236         case 32:
2237           v = Variant32;
2238           break;
2239         case 33:
2240           v = Variant33;
2241           break;
2242         case 34:
2243           v = Variant34;
2244           break;
2245         case 35:
2246           v = Variant35;
2247           break;
2248         case 36:
2249           v = Variant36;
2250           break;
2251         case 37:
2252           v = VariantShogi;
2253           break;
2254         case 38:
2255           v = VariantXiangqi;
2256           break;
2257         case 39:
2258           v = VariantCourier;
2259           break;
2260         case 40:
2261           v = VariantGothic;
2262           break;
2263         case 41:
2264           v = VariantCapablanca;
2265           break;
2266         case 42:
2267           v = VariantKnightmate;
2268           break;
2269         case 43:
2270           v = VariantFairy;
2271           break;
2272         case 44:
2273           v = VariantCylinder;
2274           break;
2275         case 45:
2276           v = VariantFalcon;
2277           break;
2278         case 46:
2279           v = VariantCapaRandom;
2280           break;
2281         case 47:
2282           v = VariantBerolina;
2283           break;
2284         case 48:
2285           v = VariantJanus;
2286           break;
2287         case 49:
2288           v = VariantSuper;
2289           break;
2290         case 50:
2291           v = VariantGreat;
2292           break;
2293         case -1:
2294           /* Found "wild" or "w" in the string but no number;
2295              must assume it's normal chess. */
2296           v = VariantNormal;
2297           break;
2298         default:
2299           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2300           if( (len >= MSG_SIZ) && appData.debugMode )
2301             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2302
2303           DisplayError(buf, 0);
2304           v = VariantUnknown;
2305           break;
2306         }
2307       }
2308     }
2309     if (appData.debugMode) {
2310       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2311               e, wnum, VariantName(v));
2312     }
2313     return v;
2314 }
2315
2316 static int leftover_start = 0, leftover_len = 0;
2317 char star_match[STAR_MATCH_N][MSG_SIZ];
2318
2319 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2320    advance *index beyond it, and set leftover_start to the new value of
2321    *index; else return FALSE.  If pattern contains the character '*', it
2322    matches any sequence of characters not containing '\r', '\n', or the
2323    character following the '*' (if any), and the matched sequence(s) are
2324    copied into star_match.
2325    */
2326 int
2327 looking_at ( char *buf, int *index, char *pattern)
2328 {
2329     char *bufp = &buf[*index], *patternp = pattern;
2330     int star_count = 0;
2331     char *matchp = star_match[0];
2332
2333     for (;;) {
2334         if (*patternp == NULLCHAR) {
2335             *index = leftover_start = bufp - buf;
2336             *matchp = NULLCHAR;
2337             return TRUE;
2338         }
2339         if (*bufp == NULLCHAR) return FALSE;
2340         if (*patternp == '*') {
2341             if (*bufp == *(patternp + 1)) {
2342                 *matchp = NULLCHAR;
2343                 matchp = star_match[++star_count];
2344                 patternp += 2;
2345                 bufp++;
2346                 continue;
2347             } else if (*bufp == '\n' || *bufp == '\r') {
2348                 patternp++;
2349                 if (*patternp == NULLCHAR)
2350                   continue;
2351                 else
2352                   return FALSE;
2353             } else {
2354                 *matchp++ = *bufp++;
2355                 continue;
2356             }
2357         }
2358         if (*patternp != *bufp) return FALSE;
2359         patternp++;
2360         bufp++;
2361     }
2362 }
2363
2364 void
2365 SendToPlayer (char *data, int length)
2366 {
2367     int error, outCount;
2368     outCount = OutputToProcess(NoProc, data, length, &error);
2369     if (outCount < length) {
2370         DisplayFatalError(_("Error writing to display"), error, 1);
2371     }
2372 }
2373
2374 void
2375 PackHolding (char packed[], char *holding)
2376 {
2377     char *p = holding;
2378     char *q = packed;
2379     int runlength = 0;
2380     int curr = 9999;
2381     do {
2382         if (*p == curr) {
2383             runlength++;
2384         } else {
2385             switch (runlength) {
2386               case 0:
2387                 break;
2388               case 1:
2389                 *q++ = curr;
2390                 break;
2391               case 2:
2392                 *q++ = curr;
2393                 *q++ = curr;
2394                 break;
2395               default:
2396                 sprintf(q, "%d", runlength);
2397                 while (*q) q++;
2398                 *q++ = curr;
2399                 break;
2400             }
2401             runlength = 1;
2402             curr = *p;
2403         }
2404     } while (*p++);
2405     *q = NULLCHAR;
2406 }
2407
2408 /* Telnet protocol requests from the front end */
2409 void
2410 TelnetRequest (unsigned char ddww, unsigned char option)
2411 {
2412     unsigned char msg[3];
2413     int outCount, outError;
2414
2415     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2416
2417     if (appData.debugMode) {
2418         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2419         switch (ddww) {
2420           case TN_DO:
2421             ddwwStr = "DO";
2422             break;
2423           case TN_DONT:
2424             ddwwStr = "DONT";
2425             break;
2426           case TN_WILL:
2427             ddwwStr = "WILL";
2428             break;
2429           case TN_WONT:
2430             ddwwStr = "WONT";
2431             break;
2432           default:
2433             ddwwStr = buf1;
2434             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2435             break;
2436         }
2437         switch (option) {
2438           case TN_ECHO:
2439             optionStr = "ECHO";
2440             break;
2441           default:
2442             optionStr = buf2;
2443             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2444             break;
2445         }
2446         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2447     }
2448     msg[0] = TN_IAC;
2449     msg[1] = ddww;
2450     msg[2] = option;
2451     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2452     if (outCount < 3) {
2453         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2454     }
2455 }
2456
2457 void
2458 DoEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DO, TN_ECHO);
2462 }
2463
2464 void
2465 DontEcho ()
2466 {
2467     if (!appData.icsActive) return;
2468     TelnetRequest(TN_DONT, TN_ECHO);
2469 }
2470
2471 void
2472 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2473 {
2474     /* put the holdings sent to us by the server on the board holdings area */
2475     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2476     char p;
2477     ChessSquare piece;
2478
2479     if(gameInfo.holdingsWidth < 2)  return;
2480     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2481         return; // prevent overwriting by pre-board holdings
2482
2483     if( (int)lowestPiece >= BlackPawn ) {
2484         holdingsColumn = 0;
2485         countsColumn = 1;
2486         holdingsStartRow = BOARD_HEIGHT-1;
2487         direction = -1;
2488     } else {
2489         holdingsColumn = BOARD_WIDTH-1;
2490         countsColumn = BOARD_WIDTH-2;
2491         holdingsStartRow = 0;
2492         direction = 1;
2493     }
2494
2495     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2496         board[i][holdingsColumn] = EmptySquare;
2497         board[i][countsColumn]   = (ChessSquare) 0;
2498     }
2499     while( (p=*holdings++) != NULLCHAR ) {
2500         piece = CharToPiece( ToUpper(p) );
2501         if(piece == EmptySquare) continue;
2502         /*j = (int) piece - (int) WhitePawn;*/
2503         j = PieceToNumber(piece);
2504         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2505         if(j < 0) continue;               /* should not happen */
2506         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2507         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2508         board[holdingsStartRow+j*direction][countsColumn]++;
2509     }
2510 }
2511
2512
2513 void
2514 VariantSwitch (Board board, VariantClass newVariant)
2515 {
2516    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2517    static Board oldBoard;
2518
2519    startedFromPositionFile = FALSE;
2520    if(gameInfo.variant == newVariant) return;
2521
2522    /* [HGM] This routine is called each time an assignment is made to
2523     * gameInfo.variant during a game, to make sure the board sizes
2524     * are set to match the new variant. If that means adding or deleting
2525     * holdings, we shift the playing board accordingly
2526     * This kludge is needed because in ICS observe mode, we get boards
2527     * of an ongoing game without knowing the variant, and learn about the
2528     * latter only later. This can be because of the move list we requested,
2529     * in which case the game history is refilled from the beginning anyway,
2530     * but also when receiving holdings of a crazyhouse game. In the latter
2531     * case we want to add those holdings to the already received position.
2532     */
2533
2534
2535    if (appData.debugMode) {
2536      fprintf(debugFP, "Switch board from %s to %s\n",
2537              VariantName(gameInfo.variant), VariantName(newVariant));
2538      setbuf(debugFP, NULL);
2539    }
2540    shuffleOpenings = 0;       /* [HGM] shuffle */
2541    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2542    switch(newVariant)
2543      {
2544      case VariantShogi:
2545        newWidth = 9;  newHeight = 9;
2546        gameInfo.holdingsSize = 7;
2547      case VariantBughouse:
2548      case VariantCrazyhouse:
2549        newHoldingsWidth = 2; break;
2550      case VariantGreat:
2551        newWidth = 10;
2552      case VariantSuper:
2553        newHoldingsWidth = 2;
2554        gameInfo.holdingsSize = 8;
2555        break;
2556      case VariantGothic:
2557      case VariantCapablanca:
2558      case VariantCapaRandom:
2559        newWidth = 10;
2560      default:
2561        newHoldingsWidth = gameInfo.holdingsSize = 0;
2562      };
2563
2564    if(newWidth  != gameInfo.boardWidth  ||
2565       newHeight != gameInfo.boardHeight ||
2566       newHoldingsWidth != gameInfo.holdingsWidth ) {
2567
2568      /* shift position to new playing area, if needed */
2569      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2570        for(i=0; i<BOARD_HEIGHT; i++)
2571          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2572            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2573              board[i][j];
2574        for(i=0; i<newHeight; i++) {
2575          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2576          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2577        }
2578      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2579        for(i=0; i<BOARD_HEIGHT; i++)
2580          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2581            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2582              board[i][j];
2583      }
2584      board[HOLDINGS_SET] = 0;
2585      gameInfo.boardWidth  = newWidth;
2586      gameInfo.boardHeight = newHeight;
2587      gameInfo.holdingsWidth = newHoldingsWidth;
2588      gameInfo.variant = newVariant;
2589      InitDrawingSizes(-2, 0);
2590    } else gameInfo.variant = newVariant;
2591    CopyBoard(oldBoard, board);   // remember correctly formatted board
2592      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2593    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2594 }
2595
2596 static int loggedOn = FALSE;
2597
2598 /*-- Game start info cache: --*/
2599 int gs_gamenum;
2600 char gs_kind[MSG_SIZ];
2601 static char player1Name[128] = "";
2602 static char player2Name[128] = "";
2603 static char cont_seq[] = "\n\\   ";
2604 static int player1Rating = -1;
2605 static int player2Rating = -1;
2606 /*----------------------------*/
2607
2608 ColorClass curColor = ColorNormal;
2609 int suppressKibitz = 0;
2610
2611 // [HGM] seekgraph
2612 Boolean soughtPending = FALSE;
2613 Boolean seekGraphUp;
2614 #define MAX_SEEK_ADS 200
2615 #define SQUARE 0x80
2616 char *seekAdList[MAX_SEEK_ADS];
2617 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2618 float tcList[MAX_SEEK_ADS];
2619 char colorList[MAX_SEEK_ADS];
2620 int nrOfSeekAds = 0;
2621 int minRating = 1010, maxRating = 2800;
2622 int hMargin = 10, vMargin = 20, h, w;
2623 extern int squareSize, lineGap;
2624
2625 void
2626 PlotSeekAd (int i)
2627 {
2628         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2629         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2630         if(r < minRating+100 && r >=0 ) r = minRating+100;
2631         if(r > maxRating) r = maxRating;
2632         if(tc < 1.f) tc = 1.f;
2633         if(tc > 95.f) tc = 95.f;
2634         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2635         y = ((double)r - minRating)/(maxRating - minRating)
2636             * (h-vMargin-squareSize/8-1) + vMargin;
2637         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2638         if(strstr(seekAdList[i], " u ")) color = 1;
2639         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2640            !strstr(seekAdList[i], "bullet") &&
2641            !strstr(seekAdList[i], "blitz") &&
2642            !strstr(seekAdList[i], "standard") ) color = 2;
2643         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2644         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2645 }
2646
2647 void
2648 PlotSingleSeekAd (int i)
2649 {
2650         PlotSeekAd(i);
2651 }
2652
2653 void
2654 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2655 {
2656         char buf[MSG_SIZ], *ext = "";
2657         VariantClass v = StringToVariant(type);
2658         if(strstr(type, "wild")) {
2659             ext = type + 4; // append wild number
2660             if(v == VariantFischeRandom) type = "chess960"; else
2661             if(v == VariantLoadable) type = "setup"; else
2662             type = VariantName(v);
2663         }
2664         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2665         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2666             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2667             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2668             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2669             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2670             seekNrList[nrOfSeekAds] = nr;
2671             zList[nrOfSeekAds] = 0;
2672             seekAdList[nrOfSeekAds++] = StrSave(buf);
2673             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2674         }
2675 }
2676
2677 void
2678 EraseSeekDot (int i)
2679 {
2680     int x = xList[i], y = yList[i], d=squareSize/4, k;
2681     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2682     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2683     // now replot every dot that overlapped
2684     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2685         int xx = xList[k], yy = yList[k];
2686         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2687             DrawSeekDot(xx, yy, colorList[k]);
2688     }
2689 }
2690
2691 void
2692 RemoveSeekAd (int nr)
2693 {
2694         int i;
2695         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2696             EraseSeekDot(i);
2697             if(seekAdList[i]) free(seekAdList[i]);
2698             seekAdList[i] = seekAdList[--nrOfSeekAds];
2699             seekNrList[i] = seekNrList[nrOfSeekAds];
2700             ratingList[i] = ratingList[nrOfSeekAds];
2701             colorList[i]  = colorList[nrOfSeekAds];
2702             tcList[i] = tcList[nrOfSeekAds];
2703             xList[i]  = xList[nrOfSeekAds];
2704             yList[i]  = yList[nrOfSeekAds];
2705             zList[i]  = zList[nrOfSeekAds];
2706             seekAdList[nrOfSeekAds] = NULL;
2707             break;
2708         }
2709 }
2710
2711 Boolean
2712 MatchSoughtLine (char *line)
2713 {
2714     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2715     int nr, base, inc, u=0; char dummy;
2716
2717     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2718        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2719        (u=1) &&
2720        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2721         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2722         // match: compact and save the line
2723         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2724         return TRUE;
2725     }
2726     return FALSE;
2727 }
2728
2729 int
2730 DrawSeekGraph ()
2731 {
2732     int i;
2733     if(!seekGraphUp) return FALSE;
2734     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2735     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2736
2737     DrawSeekBackground(0, 0, w, h);
2738     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2739     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2740     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2741         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2742         yy = h-1-yy;
2743         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2744         if(i%500 == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2748         }
2749     }
2750     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2751     for(i=1; i<100; i+=(i<10?1:5)) {
2752         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2753         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2754         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2755             char buf[MSG_SIZ];
2756             snprintf(buf, MSG_SIZ, "%d", i);
2757             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2758         }
2759     }
2760     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2761     return TRUE;
2762 }
2763
2764 int
2765 SeekGraphClick (ClickType click, int x, int y, int moving)
2766 {
2767     static int lastDown = 0, displayed = 0, lastSecond;
2768     if(y < 0) return FALSE;
2769     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2770         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2771         if(!seekGraphUp) return FALSE;
2772         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2773         DrawPosition(TRUE, NULL);
2774         return TRUE;
2775     }
2776     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2777         if(click == Release || moving) return FALSE;
2778         nrOfSeekAds = 0;
2779         soughtPending = TRUE;
2780         SendToICS(ics_prefix);
2781         SendToICS("sought\n"); // should this be "sought all"?
2782     } else { // issue challenge based on clicked ad
2783         int dist = 10000; int i, closest = 0, second = 0;
2784         for(i=0; i<nrOfSeekAds; i++) {
2785             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2786             if(d < dist) { dist = d; closest = i; }
2787             second += (d - zList[i] < 120); // count in-range ads
2788             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2789         }
2790         if(dist < 120) {
2791             char buf[MSG_SIZ];
2792             second = (second > 1);
2793             if(displayed != closest || second != lastSecond) {
2794                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2795                 lastSecond = second; displayed = closest;
2796             }
2797             if(click == Press) {
2798                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2799                 lastDown = closest;
2800                 return TRUE;
2801             } // on press 'hit', only show info
2802             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2803             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2804             SendToICS(ics_prefix);
2805             SendToICS(buf);
2806             return TRUE; // let incoming board of started game pop down the graph
2807         } else if(click == Release) { // release 'miss' is ignored
2808             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2809             if(moving == 2) { // right up-click
2810                 nrOfSeekAds = 0; // refresh graph
2811                 soughtPending = TRUE;
2812                 SendToICS(ics_prefix);
2813                 SendToICS("sought\n"); // should this be "sought all"?
2814             }
2815             return TRUE;
2816         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2817         // press miss or release hit 'pop down' seek graph
2818         seekGraphUp = FALSE;
2819         DrawPosition(TRUE, NULL);
2820     }
2821     return TRUE;
2822 }
2823
2824 void
2825 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2826 {
2827 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2828 #define STARTED_NONE 0
2829 #define STARTED_MOVES 1
2830 #define STARTED_BOARD 2
2831 #define STARTED_OBSERVE 3
2832 #define STARTED_HOLDINGS 4
2833 #define STARTED_CHATTER 5
2834 #define STARTED_COMMENT 6
2835 #define STARTED_MOVES_NOHIDE 7
2836
2837     static int started = STARTED_NONE;
2838     static char parse[20000];
2839     static int parse_pos = 0;
2840     static char buf[BUF_SIZE + 1];
2841     static int firstTime = TRUE, intfSet = FALSE;
2842     static ColorClass prevColor = ColorNormal;
2843     static int savingComment = FALSE;
2844     static int cmatch = 0; // continuation sequence match
2845     char *bp;
2846     char str[MSG_SIZ];
2847     int i, oldi;
2848     int buf_len;
2849     int next_out;
2850     int tkind;
2851     int backup;    /* [DM] For zippy color lines */
2852     char *p;
2853     char talker[MSG_SIZ]; // [HGM] chat
2854     int channel, collective=0;
2855
2856     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2857
2858     if (appData.debugMode) {
2859       if (!error) {
2860         fprintf(debugFP, "<ICS: ");
2861         show_bytes(debugFP, data, count);
2862         fprintf(debugFP, "\n");
2863       }
2864     }
2865
2866     if (appData.debugMode) { int f = forwardMostMove;
2867         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2868                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2869                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2870     }
2871     if (count > 0) {
2872         /* If last read ended with a partial line that we couldn't parse,
2873            prepend it to the new read and try again. */
2874         if (leftover_len > 0) {
2875             for (i=0; i<leftover_len; i++)
2876               buf[i] = buf[leftover_start + i];
2877         }
2878
2879     /* copy new characters into the buffer */
2880     bp = buf + leftover_len;
2881     buf_len=leftover_len;
2882     for (i=0; i<count; i++)
2883     {
2884         // ignore these
2885         if (data[i] == '\r')
2886             continue;
2887
2888         // join lines split by ICS?
2889         if (!appData.noJoin)
2890         {
2891             /*
2892                 Joining just consists of finding matches against the
2893                 continuation sequence, and discarding that sequence
2894                 if found instead of copying it.  So, until a match
2895                 fails, there's nothing to do since it might be the
2896                 complete sequence, and thus, something we don't want
2897                 copied.
2898             */
2899             if (data[i] == cont_seq[cmatch])
2900             {
2901                 cmatch++;
2902                 if (cmatch == strlen(cont_seq))
2903                 {
2904                     cmatch = 0; // complete match.  just reset the counter
2905
2906                     /*
2907                         it's possible for the ICS to not include the space
2908                         at the end of the last word, making our [correct]
2909                         join operation fuse two separate words.  the server
2910                         does this when the space occurs at the width setting.
2911                     */
2912                     if (!buf_len || buf[buf_len-1] != ' ')
2913                     {
2914                         *bp++ = ' ';
2915                         buf_len++;
2916                     }
2917                 }
2918                 continue;
2919             }
2920             else if (cmatch)
2921             {
2922                 /*
2923                     match failed, so we have to copy what matched before
2924                     falling through and copying this character.  In reality,
2925                     this will only ever be just the newline character, but
2926                     it doesn't hurt to be precise.
2927                 */
2928                 strncpy(bp, cont_seq, cmatch);
2929                 bp += cmatch;
2930                 buf_len += cmatch;
2931                 cmatch = 0;
2932             }
2933         }
2934
2935         // copy this char
2936         *bp++ = data[i];
2937         buf_len++;
2938     }
2939
2940         buf[buf_len] = NULLCHAR;
2941 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2942         next_out = 0;
2943         leftover_start = 0;
2944
2945         i = 0;
2946         while (i < buf_len) {
2947             /* Deal with part of the TELNET option negotiation
2948                protocol.  We refuse to do anything beyond the
2949                defaults, except that we allow the WILL ECHO option,
2950                which ICS uses to turn off password echoing when we are
2951                directly connected to it.  We reject this option
2952                if localLineEditing mode is on (always on in xboard)
2953                and we are talking to port 23, which might be a real
2954                telnet server that will try to keep WILL ECHO on permanently.
2955              */
2956             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2957                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2958                 unsigned char option;
2959                 oldi = i;
2960                 switch ((unsigned char) buf[++i]) {
2961                   case TN_WILL:
2962                     if (appData.debugMode)
2963                       fprintf(debugFP, "\n<WILL ");
2964                     switch (option = (unsigned char) buf[++i]) {
2965                       case TN_ECHO:
2966                         if (appData.debugMode)
2967                           fprintf(debugFP, "ECHO ");
2968                         /* Reply only if this is a change, according
2969                            to the protocol rules. */
2970                         if (remoteEchoOption) break;
2971                         if (appData.localLineEditing &&
2972                             atoi(appData.icsPort) == TN_PORT) {
2973                             TelnetRequest(TN_DONT, TN_ECHO);
2974                         } else {
2975                             EchoOff();
2976                             TelnetRequest(TN_DO, TN_ECHO);
2977                             remoteEchoOption = TRUE;
2978                         }
2979                         break;
2980                       default:
2981                         if (appData.debugMode)
2982                           fprintf(debugFP, "%d ", option);
2983                         /* Whatever this is, we don't want it. */
2984                         TelnetRequest(TN_DONT, option);
2985                         break;
2986                     }
2987                     break;
2988                   case TN_WONT:
2989                     if (appData.debugMode)
2990                       fprintf(debugFP, "\n<WONT ");
2991                     switch (option = (unsigned char) buf[++i]) {
2992                       case TN_ECHO:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "ECHO ");
2995                         /* Reply only if this is a change, according
2996                            to the protocol rules. */
2997                         if (!remoteEchoOption) break;
2998                         EchoOn();
2999                         TelnetRequest(TN_DONT, TN_ECHO);
3000                         remoteEchoOption = FALSE;
3001                         break;
3002                       default:
3003                         if (appData.debugMode)
3004                           fprintf(debugFP, "%d ", (unsigned char) option);
3005                         /* Whatever this is, it must already be turned
3006                            off, because we never agree to turn on
3007                            anything non-default, so according to the
3008                            protocol rules, we don't reply. */
3009                         break;
3010                     }
3011                     break;
3012                   case TN_DO:
3013                     if (appData.debugMode)
3014                       fprintf(debugFP, "\n<DO ");
3015                     switch (option = (unsigned char) buf[++i]) {
3016                       default:
3017                         /* Whatever this is, we refuse to do it. */
3018                         if (appData.debugMode)
3019                           fprintf(debugFP, "%d ", option);
3020                         TelnetRequest(TN_WONT, option);
3021                         break;
3022                     }
3023                     break;
3024                   case TN_DONT:
3025                     if (appData.debugMode)
3026                       fprintf(debugFP, "\n<DONT ");
3027                     switch (option = (unsigned char) buf[++i]) {
3028                       default:
3029                         if (appData.debugMode)
3030                           fprintf(debugFP, "%d ", option);
3031                         /* Whatever this is, we are already not doing
3032                            it, because we never agree to do anything
3033                            non-default, so according to the protocol
3034                            rules, we don't reply. */
3035                         break;
3036                     }
3037                     break;
3038                   case TN_IAC:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<IAC ");
3041                     /* Doubled IAC; pass it through */
3042                     i--;
3043                     break;
3044                   default:
3045                     if (appData.debugMode)
3046                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3047                     /* Drop all other telnet commands on the floor */
3048                     break;
3049                 }
3050                 if (oldi > next_out)
3051                   SendToPlayer(&buf[next_out], oldi - next_out);
3052                 if (++i > next_out)
3053                   next_out = i;
3054                 continue;
3055             }
3056
3057             /* OK, this at least will *usually* work */
3058             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3059                 loggedOn = TRUE;
3060             }
3061
3062             if (loggedOn && !intfSet) {
3063                 if (ics_type == ICS_ICC) {
3064                   snprintf(str, MSG_SIZ,
3065                           "/set-quietly interface %s\n/set-quietly style 12\n",
3066                           programVersion);
3067                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3068                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3069                 } else if (ics_type == ICS_CHESSNET) {
3070                   snprintf(str, MSG_SIZ, "/style 12\n");
3071                 } else {
3072                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3073                   strcat(str, programVersion);
3074                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3075                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3076                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3077 #ifdef WIN32
3078                   strcat(str, "$iset nohighlight 1\n");
3079 #endif
3080                   strcat(str, "$iset lock 1\n$style 12\n");
3081                 }
3082                 SendToICS(str);
3083                 NotifyFrontendLogin();
3084                 intfSet = TRUE;
3085             }
3086
3087             if (started == STARTED_COMMENT) {
3088                 /* Accumulate characters in comment */
3089                 parse[parse_pos++] = buf[i];
3090                 if (buf[i] == '\n') {
3091                     parse[parse_pos] = NULLCHAR;
3092                     if(chattingPartner>=0) {
3093                         char mess[MSG_SIZ];
3094                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3095                         OutputChatMessage(chattingPartner, mess);
3096                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3097                             int p;
3098                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3099                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3100                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3101                                 OutputChatMessage(p, mess);
3102                                 break;
3103                             }
3104                         }
3105                         chattingPartner = -1;
3106                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3107                         collective = 0;
3108                     } else
3109                     if(!suppressKibitz) // [HGM] kibitz
3110                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3111                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3112                         int nrDigit = 0, nrAlph = 0, j;
3113                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3114                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3115                         parse[parse_pos] = NULLCHAR;
3116                         // try to be smart: if it does not look like search info, it should go to
3117                         // ICS interaction window after all, not to engine-output window.
3118                         for(j=0; j<parse_pos; j++) { // count letters and digits
3119                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3120                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3121                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3122                         }
3123                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3124                             int depth=0; float score;
3125                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3126                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3127                                 pvInfoList[forwardMostMove-1].depth = depth;
3128                                 pvInfoList[forwardMostMove-1].score = 100*score;
3129                             }
3130                             OutputKibitz(suppressKibitz, parse);
3131                         } else {
3132                             char tmp[MSG_SIZ];
3133                             if(gameMode == IcsObserving) // restore original ICS messages
3134                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3135                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3136                             else
3137                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3138                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3139                             SendToPlayer(tmp, strlen(tmp));
3140                         }
3141                         next_out = i+1; // [HGM] suppress printing in ICS window
3142                     }
3143                     started = STARTED_NONE;
3144                 } else {
3145                     /* Don't match patterns against characters in comment */
3146                     i++;
3147                     continue;
3148                 }
3149             }
3150             if (started == STARTED_CHATTER) {
3151                 if (buf[i] != '\n') {
3152                     /* Don't match patterns against characters in chatter */
3153                     i++;
3154                     continue;
3155                 }
3156                 started = STARTED_NONE;
3157                 if(suppressKibitz) next_out = i+1;
3158             }
3159
3160             /* Kludge to deal with rcmd protocol */
3161             if (firstTime && looking_at(buf, &i, "\001*")) {
3162                 DisplayFatalError(&buf[1], 0, 1);
3163                 continue;
3164             } else {
3165                 firstTime = FALSE;
3166             }
3167
3168             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3169                 ics_type = ICS_ICC;
3170                 ics_prefix = "/";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3176                 ics_type = ICS_FICS;
3177                 ics_prefix = "$";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3183                 ics_type = ICS_CHESSNET;
3184                 ics_prefix = "/";
3185                 if (appData.debugMode)
3186                   fprintf(debugFP, "ics_type %d\n", ics_type);
3187                 continue;
3188             }
3189
3190             if (!loggedOn &&
3191                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3192                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3193                  looking_at(buf, &i, "will be \"*\""))) {
3194               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3195               continue;
3196             }
3197
3198             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3199               char buf[MSG_SIZ];
3200               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3201               DisplayIcsInteractionTitle(buf);
3202               have_set_title = TRUE;
3203             }
3204
3205             /* skip finger notes */
3206             if (started == STARTED_NONE &&
3207                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3208                  (buf[i] == '1' && buf[i+1] == '0')) &&
3209                 buf[i+2] == ':' && buf[i+3] == ' ') {
3210               started = STARTED_CHATTER;
3211               i += 3;
3212               continue;
3213             }
3214
3215             oldi = i;
3216             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3217             if(appData.seekGraph) {
3218                 if(soughtPending && MatchSoughtLine(buf+i)) {
3219                     i = strstr(buf+i, "rated") - buf;
3220                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                     next_out = leftover_start = i;
3222                     started = STARTED_CHATTER;
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3227                         && looking_at(buf, &i, "* ads displayed")) {
3228                     soughtPending = FALSE;
3229                     seekGraphUp = TRUE;
3230                     DrawSeekGraph();
3231                     continue;
3232                 }
3233                 if(appData.autoRefresh) {
3234                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3235                         int s = (ics_type == ICS_ICC); // ICC format differs
3236                         if(seekGraphUp)
3237                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3238                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3239                         looking_at(buf, &i, "*% "); // eat prompt
3240                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3241                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3242                         next_out = i; // suppress
3243                         continue;
3244                     }
3245                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3246                         char *p = star_match[0];
3247                         while(*p) {
3248                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3249                             while(*p && *p++ != ' '); // next
3250                         }
3251                         looking_at(buf, &i, "*% "); // eat prompt
3252                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3253                         next_out = i;
3254                         continue;
3255                     }
3256                 }
3257             }
3258
3259             /* skip formula vars */
3260             if (started == STARTED_NONE &&
3261                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3262               started = STARTED_CHATTER;
3263               i += 3;
3264               continue;
3265             }
3266
3267             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3268             if (appData.autoKibitz && started == STARTED_NONE &&
3269                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3270                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3271                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3272                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3273                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3274                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3275                         suppressKibitz = TRUE;
3276                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3277                         next_out = i;
3278                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3279                                 && (gameMode == IcsPlayingWhite)) ||
3280                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3281                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3282                             started = STARTED_CHATTER; // own kibitz we simply discard
3283                         else {
3284                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3285                             parse_pos = 0; parse[0] = NULLCHAR;
3286                             savingComment = TRUE;
3287                             suppressKibitz = gameMode != IcsObserving ? 2 :
3288                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3289                         }
3290                         continue;
3291                 } else
3292                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3293                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3294                          && atoi(star_match[0])) {
3295                     // suppress the acknowledgements of our own autoKibitz
3296                     char *p;
3297                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3298                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3299                     SendToPlayer(star_match[0], strlen(star_match[0]));
3300                     if(looking_at(buf, &i, "*% ")) // eat prompt
3301                         suppressKibitz = FALSE;
3302                     next_out = i;
3303                     continue;
3304                 }
3305             } // [HGM] kibitz: end of patch
3306
3307             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3308
3309             // [HGM] chat: intercept tells by users for which we have an open chat window
3310             channel = -1;
3311             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3312                                            looking_at(buf, &i, "* whispers:") ||
3313                                            looking_at(buf, &i, "* kibitzes:") ||
3314                                            looking_at(buf, &i, "* shouts:") ||
3315                                            looking_at(buf, &i, "* c-shouts:") ||
3316                                            looking_at(buf, &i, "--> * ") ||
3317                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3318                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3319                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3321                 int p;
3322                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3323                 chattingPartner = -1; collective = 0;
3324
3325                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3326                 for(p=0; p<MAX_CHAT; p++) {
3327                     collective = 1;
3328                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3329                     talker[0] = '['; strcat(talker, "] ");
3330                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3331                     chattingPartner = p; break;
3332                     }
3333                 } else
3334                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3335                 for(p=0; p<MAX_CHAT; p++) {
3336                     collective = 1;
3337                     if(!strcmp("kibitzes", chatPartner[p])) {
3338                         talker[0] = '['; strcat(talker, "] ");
3339                         chattingPartner = p; break;
3340                     }
3341                 } else
3342                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3343                 for(p=0; p<MAX_CHAT; p++) {
3344                     collective = 1;
3345                     if(!strcmp("whispers", chatPartner[p])) {
3346                         talker[0] = '['; strcat(talker, "] ");
3347                         chattingPartner = p; break;
3348                     }
3349                 } else
3350                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3351                   if(buf[i-8] == '-' && buf[i-3] == 't')
3352                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3353                     collective = 1;
3354                     if(!strcmp("c-shouts", chatPartner[p])) {
3355                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                   if(chattingPartner < 0)
3360                   for(p=0; p<MAX_CHAT; p++) {
3361                     collective = 1;
3362                     if(!strcmp("shouts", chatPartner[p])) {
3363                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3364                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3365                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3366                         chattingPartner = p; break;
3367                     }
3368                   }
3369                 }
3370                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3371                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3372                     talker[0] = 0;
3373                     Colorize(ColorTell, FALSE);
3374                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3375                     collective |= 2;
3376                     chattingPartner = p; break;
3377                 }
3378                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3379                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3380                     started = STARTED_COMMENT;
3381                     parse_pos = 0; parse[0] = NULLCHAR;
3382                     savingComment = 3 + chattingPartner; // counts as TRUE
3383                     if(collective == 3) i = oldi; else {
3384                         suppressKibitz = TRUE;
3385                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3386                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3387                         continue;
3388                     }
3389                 }
3390             } // [HGM] chat: end of patch
3391
3392           backup = i;
3393             if (appData.zippyTalk || appData.zippyPlay) {
3394                 /* [DM] Backup address for color zippy lines */
3395 #if ZIPPY
3396                if (loggedOn == TRUE)
3397                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3398                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3399 #endif
3400             } // [DM] 'else { ' deleted
3401                 if (
3402                     /* Regular tells and says */
3403                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3404                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3405                     looking_at(buf, &i, "* says: ") ||
3406                     /* Don't color "message" or "messages" output */
3407                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3408                     looking_at(buf, &i, "*. * at *:*: ") ||
3409                     looking_at(buf, &i, "--* (*:*): ") ||
3410                     /* Message notifications (same color as tells) */
3411                     looking_at(buf, &i, "* has left a message ") ||
3412                     looking_at(buf, &i, "* just sent you a message:\n") ||
3413                     /* Whispers and kibitzes */
3414                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3415                     looking_at(buf, &i, "* kibitzes: ") ||
3416                     /* Channel tells */
3417                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3418
3419                   if (tkind == 1 && strchr(star_match[0], ':')) {
3420                       /* Avoid "tells you:" spoofs in channels */
3421                      tkind = 3;
3422                   }
3423                   if (star_match[0][0] == NULLCHAR ||
3424                       strchr(star_match[0], ' ') ||
3425                       (tkind == 3 && strchr(star_match[1], ' '))) {
3426                     /* Reject bogus matches */
3427                     i = oldi;
3428                   } else {
3429                     if (appData.colorize) {
3430                       if (oldi > next_out) {
3431                         SendToPlayer(&buf[next_out], oldi - next_out);
3432                         next_out = oldi;
3433                       }
3434                       switch (tkind) {
3435                       case 1:
3436                         Colorize(ColorTell, FALSE);
3437                         curColor = ColorTell;
3438                         break;
3439                       case 2:
3440                         Colorize(ColorKibitz, FALSE);
3441                         curColor = ColorKibitz;
3442                         break;
3443                       case 3:
3444                         p = strrchr(star_match[1], '(');
3445                         if (p == NULL) {
3446                           p = star_match[1];
3447                         } else {
3448                           p++;
3449                         }
3450                         if (atoi(p) == 1) {
3451                           Colorize(ColorChannel1, FALSE);
3452                           curColor = ColorChannel1;
3453                         } else {
3454                           Colorize(ColorChannel, FALSE);
3455                           curColor = ColorChannel;
3456                         }
3457                         break;
3458                       case 5:
3459                         curColor = ColorNormal;
3460                         break;
3461                       }
3462                     }
3463                     if (started == STARTED_NONE && appData.autoComment &&
3464                         (gameMode == IcsObserving ||
3465                          gameMode == IcsPlayingWhite ||
3466                          gameMode == IcsPlayingBlack)) {
3467                       parse_pos = i - oldi;
3468                       memcpy(parse, &buf[oldi], parse_pos);
3469                       parse[parse_pos] = NULLCHAR;
3470                       started = STARTED_COMMENT;
3471                       savingComment = TRUE;
3472                     } else if(collective != 3) {
3473                       started = STARTED_CHATTER;
3474                       savingComment = FALSE;
3475                     }
3476                     loggedOn = TRUE;
3477                     continue;
3478                   }
3479                 }
3480
3481                 if (looking_at(buf, &i, "* s-shouts: ") ||
3482                     looking_at(buf, &i, "* c-shouts: ")) {
3483                     if (appData.colorize) {
3484                         if (oldi > next_out) {
3485                             SendToPlayer(&buf[next_out], oldi - next_out);
3486                             next_out = oldi;
3487                         }
3488                         Colorize(ColorSShout, FALSE);
3489                         curColor = ColorSShout;
3490                     }
3491                     loggedOn = TRUE;
3492                     started = STARTED_CHATTER;
3493                     continue;
3494                 }
3495
3496                 if (looking_at(buf, &i, "--->")) {
3497                     loggedOn = TRUE;
3498                     continue;
3499                 }
3500
3501                 if (looking_at(buf, &i, "* shouts: ") ||
3502                     looking_at(buf, &i, "--> ")) {
3503                     if (appData.colorize) {
3504                         if (oldi > next_out) {
3505                             SendToPlayer(&buf[next_out], oldi - next_out);
3506                             next_out = oldi;
3507                         }
3508                         Colorize(ColorShout, FALSE);
3509                         curColor = ColorShout;
3510                     }
3511                     loggedOn = TRUE;
3512                     started = STARTED_CHATTER;
3513                     continue;
3514                 }
3515
3516                 if (looking_at( buf, &i, "Challenge:")) {
3517                     if (appData.colorize) {
3518                         if (oldi > next_out) {
3519                             SendToPlayer(&buf[next_out], oldi - next_out);
3520                             next_out = oldi;
3521                         }
3522                         Colorize(ColorChallenge, FALSE);
3523                         curColor = ColorChallenge;
3524                     }
3525                     loggedOn = TRUE;
3526                     continue;
3527                 }
3528
3529                 if (looking_at(buf, &i, "* offers you") ||
3530                     looking_at(buf, &i, "* offers to be") ||
3531                     looking_at(buf, &i, "* would like to") ||
3532                     looking_at(buf, &i, "* requests to") ||
3533                     looking_at(buf, &i, "Your opponent offers") ||
3534                     looking_at(buf, &i, "Your opponent requests")) {
3535
3536                     if (appData.colorize) {
3537                         if (oldi > next_out) {
3538                             SendToPlayer(&buf[next_out], oldi - next_out);
3539                             next_out = oldi;
3540                         }
3541                         Colorize(ColorRequest, FALSE);
3542                         curColor = ColorRequest;
3543                     }
3544                     continue;
3545                 }
3546
3547                 if (looking_at(buf, &i, "* (*) seeking")) {
3548                     if (appData.colorize) {
3549                         if (oldi > next_out) {
3550                             SendToPlayer(&buf[next_out], oldi - next_out);
3551                             next_out = oldi;
3552                         }
3553                         Colorize(ColorSeek, FALSE);
3554                         curColor = ColorSeek;
3555                     }
3556                     continue;
3557             }
3558
3559           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3560
3561             if (looking_at(buf, &i, "\\   ")) {
3562                 if (prevColor != ColorNormal) {
3563                     if (oldi > next_out) {
3564                         SendToPlayer(&buf[next_out], oldi - next_out);
3565                         next_out = oldi;
3566                     }
3567                     Colorize(prevColor, TRUE);
3568                     curColor = prevColor;
3569                 }
3570                 if (savingComment) {
3571                     parse_pos = i - oldi;
3572                     memcpy(parse, &buf[oldi], parse_pos);
3573                     parse[parse_pos] = NULLCHAR;
3574                     started = STARTED_COMMENT;
3575                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3576                         chattingPartner = savingComment - 3; // kludge to remember the box
3577                 } else {
3578                     started = STARTED_CHATTER;
3579                 }
3580                 continue;
3581             }
3582
3583             if (looking_at(buf, &i, "Black Strength :") ||
3584                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3585                 looking_at(buf, &i, "<10>") ||
3586                 looking_at(buf, &i, "#@#")) {
3587                 /* Wrong board style */
3588                 loggedOn = TRUE;
3589                 SendToICS(ics_prefix);
3590                 SendToICS("set style 12\n");
3591                 SendToICS(ics_prefix);
3592                 SendToICS("refresh\n");
3593                 continue;
3594             }
3595
3596             if (looking_at(buf, &i, "login:")) {
3597               if (!have_sent_ICS_logon) {
3598                 if(ICSInitScript())
3599                   have_sent_ICS_logon = 1;
3600                 else // no init script was found
3601                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3602               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3603                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3604               }
3605                 continue;
3606             }
3607
3608             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3609                 (looking_at(buf, &i, "\n<12> ") ||
3610                  looking_at(buf, &i, "<12> "))) {
3611                 loggedOn = TRUE;
3612                 if (oldi > next_out) {
3613                     SendToPlayer(&buf[next_out], oldi - next_out);
3614                 }
3615                 next_out = i;
3616                 started = STARTED_BOARD;
3617                 parse_pos = 0;
3618                 continue;
3619             }
3620
3621             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3622                 looking_at(buf, &i, "<b1> ")) {
3623                 if (oldi > next_out) {
3624                     SendToPlayer(&buf[next_out], oldi - next_out);
3625                 }
3626                 next_out = i;
3627                 started = STARTED_HOLDINGS;
3628                 parse_pos = 0;
3629                 continue;
3630             }
3631
3632             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3633                 loggedOn = TRUE;
3634                 /* Header for a move list -- first line */
3635
3636                 switch (ics_getting_history) {
3637                   case H_FALSE:
3638                     switch (gameMode) {
3639                       case IcsIdle:
3640                       case BeginningOfGame:
3641                         /* User typed "moves" or "oldmoves" while we
3642                            were idle.  Pretend we asked for these
3643                            moves and soak them up so user can step
3644                            through them and/or save them.
3645                            */
3646                         Reset(FALSE, TRUE);
3647                         gameMode = IcsObserving;
3648                         ModeHighlight();
3649                         ics_gamenum = -1;
3650                         ics_getting_history = H_GOT_UNREQ_HEADER;
3651                         break;
3652                       case EditGame: /*?*/
3653                       case EditPosition: /*?*/
3654                         /* Should above feature work in these modes too? */
3655                         /* For now it doesn't */
3656                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3657                         break;
3658                       default:
3659                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3660                         break;
3661                     }
3662                     break;
3663                   case H_REQUESTED:
3664                     /* Is this the right one? */
3665                     if (gameInfo.white && gameInfo.black &&
3666                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3667                         strcmp(gameInfo.black, star_match[2]) == 0) {
3668                         /* All is well */
3669                         ics_getting_history = H_GOT_REQ_HEADER;
3670                     }
3671                     break;
3672                   case H_GOT_REQ_HEADER:
3673                   case H_GOT_UNREQ_HEADER:
3674                   case H_GOT_UNWANTED_HEADER:
3675                   case H_GETTING_MOVES:
3676                     /* Should not happen */
3677                     DisplayError(_("Error gathering move list: two headers"), 0);
3678                     ics_getting_history = H_FALSE;
3679                     break;
3680                 }
3681
3682                 /* Save player ratings into gameInfo if needed */
3683                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3684                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3685                     (gameInfo.whiteRating == -1 ||
3686                      gameInfo.blackRating == -1)) {
3687
3688                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3689                     gameInfo.blackRating = string_to_rating(star_match[3]);
3690                     if (appData.debugMode)
3691                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3692                               gameInfo.whiteRating, gameInfo.blackRating);
3693                 }
3694                 continue;
3695             }
3696
3697             if (looking_at(buf, &i,
3698               "* * match, initial time: * minute*, increment: * second")) {
3699                 /* Header for a move list -- second line */
3700                 /* Initial board will follow if this is a wild game */
3701                 if (gameInfo.event != NULL) free(gameInfo.event);
3702                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3703                 gameInfo.event = StrSave(str);
3704                 /* [HGM] we switched variant. Translate boards if needed. */
3705                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3706                 continue;
3707             }
3708
3709             if (looking_at(buf, &i, "Move  ")) {
3710                 /* Beginning of a move list */
3711                 switch (ics_getting_history) {
3712                   case H_FALSE:
3713                     /* Normally should not happen */
3714                     /* Maybe user hit reset while we were parsing */
3715                     break;
3716                   case H_REQUESTED:
3717                     /* Happens if we are ignoring a move list that is not
3718                      * the one we just requested.  Common if the user
3719                      * tries to observe two games without turning off
3720                      * getMoveList */
3721                     break;
3722                   case H_GETTING_MOVES:
3723                     /* Should not happen */
3724                     DisplayError(_("Error gathering move list: nested"), 0);
3725                     ics_getting_history = H_FALSE;
3726                     break;
3727                   case H_GOT_REQ_HEADER:
3728                     ics_getting_history = H_GETTING_MOVES;
3729                     started = STARTED_MOVES;
3730                     parse_pos = 0;
3731                     if (oldi > next_out) {
3732                         SendToPlayer(&buf[next_out], oldi - next_out);
3733                     }
3734                     break;
3735                   case H_GOT_UNREQ_HEADER:
3736                     ics_getting_history = H_GETTING_MOVES;
3737                     started = STARTED_MOVES_NOHIDE;
3738                     parse_pos = 0;
3739                     break;
3740                   case H_GOT_UNWANTED_HEADER:
3741                     ics_getting_history = H_FALSE;
3742                     break;
3743                 }
3744                 continue;
3745             }
3746
3747             if (looking_at(buf, &i, "% ") ||
3748                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3749                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3750                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3751                     soughtPending = FALSE;
3752                     seekGraphUp = TRUE;
3753                     DrawSeekGraph();
3754                 }
3755                 if(suppressKibitz) next_out = i;
3756                 savingComment = FALSE;
3757                 suppressKibitz = 0;
3758                 switch (started) {
3759                   case STARTED_MOVES:
3760                   case STARTED_MOVES_NOHIDE:
3761                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3762                     parse[parse_pos + i - oldi] = NULLCHAR;
3763                     ParseGameHistory(parse);
3764 #if ZIPPY
3765                     if (appData.zippyPlay && first.initDone) {
3766                         FeedMovesToProgram(&first, forwardMostMove);
3767                         if (gameMode == IcsPlayingWhite) {
3768                             if (WhiteOnMove(forwardMostMove)) {
3769                                 if (first.sendTime) {
3770                                   if (first.useColors) {
3771                                     SendToProgram("black\n", &first);
3772                                   }
3773                                   SendTimeRemaining(&first, TRUE);
3774                                 }
3775                                 if (first.useColors) {
3776                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3777                                 }
3778                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3779                                 first.maybeThinking = TRUE;
3780                             } else {
3781                                 if (first.usePlayother) {
3782                                   if (first.sendTime) {
3783                                     SendTimeRemaining(&first, TRUE);
3784                                   }
3785                                   SendToProgram("playother\n", &first);
3786                                   firstMove = FALSE;
3787                                 } else {
3788                                   firstMove = TRUE;
3789                                 }
3790                             }
3791                         } else if (gameMode == IcsPlayingBlack) {
3792                             if (!WhiteOnMove(forwardMostMove)) {
3793                                 if (first.sendTime) {
3794                                   if (first.useColors) {
3795                                     SendToProgram("white\n", &first);
3796                                   }
3797                                   SendTimeRemaining(&first, FALSE);
3798                                 }
3799                                 if (first.useColors) {
3800                                   SendToProgram("black\n", &first);
3801                                 }
3802                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3803                                 first.maybeThinking = TRUE;
3804                             } else {
3805                                 if (first.usePlayother) {
3806                                   if (first.sendTime) {
3807                                     SendTimeRemaining(&first, FALSE);
3808                                   }
3809                                   SendToProgram("playother\n", &first);
3810                                   firstMove = FALSE;
3811                                 } else {
3812                                   firstMove = TRUE;
3813                                 }
3814                             }
3815                         }
3816                     }
3817 #endif
3818                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3819                         /* Moves came from oldmoves or moves command
3820                            while we weren't doing anything else.
3821                            */
3822                         currentMove = forwardMostMove;
3823                         ClearHighlights();/*!!could figure this out*/
3824                         flipView = appData.flipView;
3825                         DrawPosition(TRUE, boards[currentMove]);
3826                         DisplayBothClocks();
3827                         snprintf(str, MSG_SIZ, "%s %s %s",
3828                                 gameInfo.white, _("vs."),  gameInfo.black);
3829                         DisplayTitle(str);
3830                         gameMode = IcsIdle;
3831                     } else {
3832                         /* Moves were history of an active game */
3833                         if (gameInfo.resultDetails != NULL) {
3834                             free(gameInfo.resultDetails);
3835                             gameInfo.resultDetails = NULL;
3836                         }
3837                     }
3838                     HistorySet(parseList, backwardMostMove,
3839                                forwardMostMove, currentMove-1);
3840                     DisplayMove(currentMove - 1);
3841                     if (started == STARTED_MOVES) next_out = i;
3842                     started = STARTED_NONE;
3843                     ics_getting_history = H_FALSE;
3844                     break;
3845
3846                   case STARTED_OBSERVE:
3847                     started = STARTED_NONE;
3848                     SendToICS(ics_prefix);
3849                     SendToICS("refresh\n");
3850                     break;
3851
3852                   default:
3853                     break;
3854                 }
3855                 if(bookHit) { // [HGM] book: simulate book reply
3856                     static char bookMove[MSG_SIZ]; // a bit generous?
3857
3858                     programStats.nodes = programStats.depth = programStats.time =
3859                     programStats.score = programStats.got_only_move = 0;
3860                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3861
3862                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3863                     strcat(bookMove, bookHit);
3864                     HandleMachineMove(bookMove, &first);
3865                 }
3866                 continue;
3867             }
3868
3869             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3870                  started == STARTED_HOLDINGS ||
3871                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3872                 /* Accumulate characters in move list or board */
3873                 parse[parse_pos++] = buf[i];
3874             }
3875
3876             /* Start of game messages.  Mostly we detect start of game
3877                when the first board image arrives.  On some versions
3878                of the ICS, though, we need to do a "refresh" after starting
3879                to observe in order to get the current board right away. */
3880             if (looking_at(buf, &i, "Adding game * to observation list")) {
3881                 started = STARTED_OBSERVE;
3882                 continue;
3883             }
3884
3885             /* Handle auto-observe */
3886             if (appData.autoObserve &&
3887                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3888                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3889                 char *player;
3890                 /* Choose the player that was highlighted, if any. */
3891                 if (star_match[0][0] == '\033' ||
3892                     star_match[1][0] != '\033') {
3893                     player = star_match[0];
3894                 } else {
3895                     player = star_match[2];
3896                 }
3897                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3898                         ics_prefix, StripHighlightAndTitle(player));
3899                 SendToICS(str);
3900
3901                 /* Save ratings from notify string */
3902                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3903                 player1Rating = string_to_rating(star_match[1]);
3904                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3905                 player2Rating = string_to_rating(star_match[3]);
3906
3907                 if (appData.debugMode)
3908                   fprintf(debugFP,
3909                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3910                           player1Name, player1Rating,
3911                           player2Name, player2Rating);
3912
3913                 continue;
3914             }
3915
3916             /* Deal with automatic examine mode after a game,
3917                and with IcsObserving -> IcsExamining transition */
3918             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3919                 looking_at(buf, &i, "has made you an examiner of game *")) {
3920
3921                 int gamenum = atoi(star_match[0]);
3922                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3923                     gamenum == ics_gamenum) {
3924                     /* We were already playing or observing this game;
3925                        no need to refetch history */
3926                     gameMode = IcsExamining;
3927                     if (pausing) {
3928                         pauseExamForwardMostMove = forwardMostMove;
3929                     } else if (currentMove < forwardMostMove) {
3930                         ForwardInner(forwardMostMove);
3931                     }
3932                 } else {
3933                     /* I don't think this case really can happen */
3934                     SendToICS(ics_prefix);
3935                     SendToICS("refresh\n");
3936                 }
3937                 continue;
3938             }
3939
3940             /* Error messages */
3941 //          if (ics_user_moved) {
3942             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3943                 if (looking_at(buf, &i, "Illegal move") ||
3944                     looking_at(buf, &i, "Not a legal move") ||
3945                     looking_at(buf, &i, "Your king is in check") ||
3946                     looking_at(buf, &i, "It isn't your turn") ||
3947                     looking_at(buf, &i, "It is not your move")) {
3948                     /* Illegal move */
3949                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3950                         currentMove = forwardMostMove-1;
3951                         DisplayMove(currentMove - 1); /* before DMError */
3952                         DrawPosition(FALSE, boards[currentMove]);
3953                         SwitchClocks(forwardMostMove-1); // [HGM] race
3954                         DisplayBothClocks();
3955                     }
3956                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3957                     ics_user_moved = 0;
3958                     continue;
3959                 }
3960             }
3961
3962             if (looking_at(buf, &i, "still have time") ||
3963                 looking_at(buf, &i, "not out of time") ||
3964                 looking_at(buf, &i, "either player is out of time") ||
3965                 looking_at(buf, &i, "has timeseal; checking")) {
3966                 /* We must have called his flag a little too soon */
3967                 whiteFlag = blackFlag = FALSE;
3968                 continue;
3969             }
3970
3971             if (looking_at(buf, &i, "added * seconds to") ||
3972                 looking_at(buf, &i, "seconds were added to")) {
3973                 /* Update the clocks */
3974                 SendToICS(ics_prefix);
3975                 SendToICS("refresh\n");
3976                 continue;
3977             }
3978
3979             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3980                 ics_clock_paused = TRUE;
3981                 StopClocks();
3982                 continue;
3983             }
3984
3985             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3986                 ics_clock_paused = FALSE;
3987                 StartClocks();
3988                 continue;
3989             }
3990
3991             /* Grab player ratings from the Creating: message.
3992                Note we have to check for the special case when
3993                the ICS inserts things like [white] or [black]. */
3994             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3995                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3996                 /* star_matches:
3997                    0    player 1 name (not necessarily white)
3998                    1    player 1 rating
3999                    2    empty, white, or black (IGNORED)
4000                    3    player 2 name (not necessarily black)
4001                    4    player 2 rating
4002
4003                    The names/ratings are sorted out when the game
4004                    actually starts (below).
4005                 */
4006                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4007                 player1Rating = string_to_rating(star_match[1]);
4008                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4009                 player2Rating = string_to_rating(star_match[4]);
4010
4011                 if (appData.debugMode)
4012                   fprintf(debugFP,
4013                           "Ratings from 'Creating:' %s %d, %s %d\n",
4014                           player1Name, player1Rating,
4015                           player2Name, player2Rating);
4016
4017                 continue;
4018             }
4019
4020             /* Improved generic start/end-of-game messages */
4021             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4022                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4023                 /* If tkind == 0: */
4024                 /* star_match[0] is the game number */
4025                 /*           [1] is the white player's name */
4026                 /*           [2] is the black player's name */
4027                 /* For end-of-game: */
4028                 /*           [3] is the reason for the game end */
4029                 /*           [4] is a PGN end game-token, preceded by " " */
4030                 /* For start-of-game: */
4031                 /*           [3] begins with "Creating" or "Continuing" */
4032                 /*           [4] is " *" or empty (don't care). */
4033                 int gamenum = atoi(star_match[0]);
4034                 char *whitename, *blackname, *why, *endtoken;
4035                 ChessMove endtype = EndOfFile;
4036
4037                 if (tkind == 0) {
4038                   whitename = star_match[1];
4039                   blackname = star_match[2];
4040                   why = star_match[3];
4041                   endtoken = star_match[4];
4042                 } else {
4043                   whitename = star_match[1];
4044                   blackname = star_match[3];
4045                   why = star_match[5];
4046                   endtoken = star_match[6];
4047                 }
4048
4049                 /* Game start messages */
4050                 if (strncmp(why, "Creating ", 9) == 0 ||
4051                     strncmp(why, "Continuing ", 11) == 0) {
4052                     gs_gamenum = gamenum;
4053                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4054                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4055                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4056 #if ZIPPY
4057                     if (appData.zippyPlay) {
4058                         ZippyGameStart(whitename, blackname);
4059                     }
4060 #endif /*ZIPPY*/
4061                     partnerBoardValid = FALSE; // [HGM] bughouse
4062                     continue;
4063                 }
4064
4065                 /* Game end messages */
4066                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4067                     ics_gamenum != gamenum) {
4068                     continue;
4069                 }
4070                 while (endtoken[0] == ' ') endtoken++;
4071                 switch (endtoken[0]) {
4072                   case '*':
4073                   default:
4074                     endtype = GameUnfinished;
4075                     break;
4076                   case '0':
4077                     endtype = BlackWins;
4078                     break;
4079                   case '1':
4080                     if (endtoken[1] == '/')
4081                       endtype = GameIsDrawn;
4082                     else
4083                       endtype = WhiteWins;
4084                     break;
4085                 }
4086                 GameEnds(endtype, why, GE_ICS);
4087 #if ZIPPY
4088                 if (appData.zippyPlay && first.initDone) {
4089                     ZippyGameEnd(endtype, why);
4090                     if (first.pr == NoProc) {
4091                       /* Start the next process early so that we'll
4092                          be ready for the next challenge */
4093                       StartChessProgram(&first);
4094                     }
4095                     /* Send "new" early, in case this command takes
4096                        a long time to finish, so that we'll be ready
4097                        for the next challenge. */
4098                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4099                     Reset(TRUE, TRUE);
4100                 }
4101 #endif /*ZIPPY*/
4102                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4103                 continue;
4104             }
4105
4106             if (looking_at(buf, &i, "Removing game * from observation") ||
4107                 looking_at(buf, &i, "no longer observing game *") ||
4108                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4109                 if (gameMode == IcsObserving &&
4110                     atoi(star_match[0]) == ics_gamenum)
4111                   {
4112                       /* icsEngineAnalyze */
4113                       if (appData.icsEngineAnalyze) {
4114                             ExitAnalyzeMode();
4115                             ModeHighlight();
4116                       }
4117                       StopClocks();
4118                       gameMode = IcsIdle;
4119                       ics_gamenum = -1;
4120                       ics_user_moved = FALSE;
4121                   }
4122                 continue;
4123             }
4124
4125             if (looking_at(buf, &i, "no longer examining game *")) {
4126                 if (gameMode == IcsExamining &&
4127                     atoi(star_match[0]) == ics_gamenum)
4128                   {
4129                       gameMode = IcsIdle;
4130                       ics_gamenum = -1;
4131                       ics_user_moved = FALSE;
4132                   }
4133                 continue;
4134             }
4135
4136             /* Advance leftover_start past any newlines we find,
4137                so only partial lines can get reparsed */
4138             if (looking_at(buf, &i, "\n")) {
4139                 prevColor = curColor;
4140                 if (curColor != ColorNormal) {
4141                     if (oldi > next_out) {
4142                         SendToPlayer(&buf[next_out], oldi - next_out);
4143                         next_out = oldi;
4144                     }
4145                     Colorize(ColorNormal, FALSE);
4146                     curColor = ColorNormal;
4147                 }
4148                 if (started == STARTED_BOARD) {
4149                     started = STARTED_NONE;
4150                     parse[parse_pos] = NULLCHAR;
4151                     ParseBoard12(parse);
4152                     ics_user_moved = 0;
4153
4154                     /* Send premove here */
4155                     if (appData.premove) {
4156                       char str[MSG_SIZ];
4157                       if (currentMove == 0 &&
4158                           gameMode == IcsPlayingWhite &&
4159                           appData.premoveWhite) {
4160                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4161                         if (appData.debugMode)
4162                           fprintf(debugFP, "Sending premove:\n");
4163                         SendToICS(str);
4164                       } else if (currentMove == 1 &&
4165                                  gameMode == IcsPlayingBlack &&
4166                                  appData.premoveBlack) {
4167                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4168                         if (appData.debugMode)
4169                           fprintf(debugFP, "Sending premove:\n");
4170                         SendToICS(str);
4171                       } else if (gotPremove) {
4172                         int oldFMM = forwardMostMove;
4173                         gotPremove = 0;
4174                         ClearPremoveHighlights();
4175                         if (appData.debugMode)
4176                           fprintf(debugFP, "Sending premove:\n");
4177                           UserMoveEvent(premoveFromX, premoveFromY,
4178                                         premoveToX, premoveToY,
4179                                         premovePromoChar);
4180                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4181                           if(moveList[oldFMM-1][1] != '@')
4182                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4183                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4184                           else // (drop)
4185                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4186                         }
4187                       }
4188                     }
4189
4190                     /* Usually suppress following prompt */
4191                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4192                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4193                         if (looking_at(buf, &i, "*% ")) {
4194                             savingComment = FALSE;
4195                             suppressKibitz = 0;
4196                         }
4197                     }
4198                     next_out = i;
4199                 } else if (started == STARTED_HOLDINGS) {
4200                     int gamenum;
4201                     char new_piece[MSG_SIZ];
4202                     started = STARTED_NONE;
4203                     parse[parse_pos] = NULLCHAR;
4204                     if (appData.debugMode)
4205                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4206                                                         parse, currentMove);
4207                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4208                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4209                         if (gameInfo.variant == VariantNormal) {
4210                           /* [HGM] We seem to switch variant during a game!
4211                            * Presumably no holdings were displayed, so we have
4212                            * to move the position two files to the right to
4213                            * create room for them!
4214                            */
4215                           VariantClass newVariant;
4216                           switch(gameInfo.boardWidth) { // base guess on board width
4217                                 case 9:  newVariant = VariantShogi; break;
4218                                 case 10: newVariant = VariantGreat; break;
4219                                 default: newVariant = VariantCrazyhouse; break;
4220                           }
4221                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4222                           /* Get a move list just to see the header, which
4223                              will tell us whether this is really bug or zh */
4224                           if (ics_getting_history == H_FALSE) {
4225                             ics_getting_history = H_REQUESTED;
4226                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4227                             SendToICS(str);
4228                           }
4229                         }
4230                         new_piece[0] = NULLCHAR;
4231                         sscanf(parse, "game %d white [%s black [%s <- %s",
4232                                &gamenum, white_holding, black_holding,
4233                                new_piece);
4234                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4235                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4236                         /* [HGM] copy holdings to board holdings area */
4237                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4238                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4239                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4240 #if ZIPPY
4241                         if (appData.zippyPlay && first.initDone) {
4242                             ZippyHoldings(white_holding, black_holding,
4243                                           new_piece);
4244                         }
4245 #endif /*ZIPPY*/
4246                         if (tinyLayout || smallLayout) {
4247                             char wh[16], bh[16];
4248                             PackHolding(wh, white_holding);
4249                             PackHolding(bh, black_holding);
4250                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4251                                     gameInfo.white, gameInfo.black);
4252                         } else {
4253                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4254                                     gameInfo.white, white_holding, _("vs."),
4255                                     gameInfo.black, black_holding);
4256                         }
4257                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4258                         DrawPosition(FALSE, boards[currentMove]);
4259                         DisplayTitle(str);
4260                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4261                         sscanf(parse, "game %d white [%s black [%s <- %s",
4262                                &gamenum, white_holding, black_holding,
4263                                new_piece);
4264                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4265                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4266                         /* [HGM] copy holdings to partner-board holdings area */
4267                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4268                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4269                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4270                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4271                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4272                       }
4273                     }
4274                     /* Suppress following prompt */
4275                     if (looking_at(buf, &i, "*% ")) {
4276                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4277                         savingComment = FALSE;
4278                         suppressKibitz = 0;
4279                     }
4280                     next_out = i;
4281                 }
4282                 continue;
4283             }
4284
4285             i++;                /* skip unparsed character and loop back */
4286         }
4287
4288         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4289 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4290 //          SendToPlayer(&buf[next_out], i - next_out);
4291             started != STARTED_HOLDINGS && leftover_start > next_out) {
4292             SendToPlayer(&buf[next_out], leftover_start - next_out);
4293             next_out = i;
4294         }
4295
4296         leftover_len = buf_len - leftover_start;
4297         /* if buffer ends with something we couldn't parse,
4298            reparse it after appending the next read */
4299
4300     } else if (count == 0) {
4301         RemoveInputSource(isr);
4302         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4303     } else {
4304         DisplayFatalError(_("Error reading from ICS"), error, 1);
4305     }
4306 }
4307
4308
4309 /* Board style 12 looks like this:
4310
4311    <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
4312
4313  * The "<12> " is stripped before it gets to this routine.  The two
4314  * trailing 0's (flip state and clock ticking) are later addition, and
4315  * some chess servers may not have them, or may have only the first.
4316  * Additional trailing fields may be added in the future.
4317  */
4318
4319 #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"
4320
4321 #define RELATION_OBSERVING_PLAYED    0
4322 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4323 #define RELATION_PLAYING_MYMOVE      1
4324 #define RELATION_PLAYING_NOTMYMOVE  -1
4325 #define RELATION_EXAMINING           2
4326 #define RELATION_ISOLATED_BOARD     -3
4327 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4328
4329 void
4330 ParseBoard12 (char *string)
4331 {
4332 #if ZIPPY
4333     int i, takeback;
4334     char *bookHit = NULL; // [HGM] book
4335 #endif
4336     GameMode newGameMode;
4337     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4338     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4339     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4340     char to_play, board_chars[200];
4341     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4342     char black[32], white[32];
4343     Board board;
4344     int prevMove = currentMove;
4345     int ticking = 2;
4346     ChessMove moveType;
4347     int fromX, fromY, toX, toY;
4348     char promoChar;
4349     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4350     Boolean weird = FALSE, reqFlag = FALSE;
4351
4352     fromX = fromY = toX = toY = -1;
4353
4354     newGame = FALSE;
4355
4356     if (appData.debugMode)
4357       fprintf(debugFP, "Parsing board: %s\n", string);
4358
4359     move_str[0] = NULLCHAR;
4360     elapsed_time[0] = NULLCHAR;
4361     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4362         int  i = 0, j;
4363         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4364             if(string[i] == ' ') { ranks++; files = 0; }
4365             else files++;
4366             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4367             i++;
4368         }
4369         for(j = 0; j <i; j++) board_chars[j] = string[j];
4370         board_chars[i] = '\0';
4371         string += i + 1;
4372     }
4373     n = sscanf(string, PATTERN, &to_play, &double_push,
4374                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4375                &gamenum, white, black, &relation, &basetime, &increment,
4376                &white_stren, &black_stren, &white_time, &black_time,
4377                &moveNum, str, elapsed_time, move_str, &ics_flip,
4378                &ticking);
4379
4380     if (n < 21) {
4381         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4382         DisplayError(str, 0);
4383         return;
4384     }
4385
4386     /* Convert the move number to internal form */
4387     moveNum = (moveNum - 1) * 2;
4388     if (to_play == 'B') moveNum++;
4389     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4390       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4391                         0, 1);
4392       return;
4393     }
4394
4395     switch (relation) {
4396       case RELATION_OBSERVING_PLAYED:
4397       case RELATION_OBSERVING_STATIC:
4398         if (gamenum == -1) {
4399             /* Old ICC buglet */
4400             relation = RELATION_OBSERVING_STATIC;
4401         }
4402         newGameMode = IcsObserving;
4403         break;
4404       case RELATION_PLAYING_MYMOVE:
4405       case RELATION_PLAYING_NOTMYMOVE:
4406         newGameMode =
4407           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4408             IcsPlayingWhite : IcsPlayingBlack;
4409         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4410         break;
4411       case RELATION_EXAMINING:
4412         newGameMode = IcsExamining;
4413         break;
4414       case RELATION_ISOLATED_BOARD:
4415       default:
4416         /* Just display this board.  If user was doing something else,
4417            we will forget about it until the next board comes. */
4418         newGameMode = IcsIdle;
4419         break;
4420       case RELATION_STARTING_POSITION:
4421         newGameMode = gameMode;
4422         break;
4423     }
4424
4425     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4426         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4427          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4428       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4429       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4430       static int lastBgGame = -1;
4431       char *toSqr;
4432       for (k = 0; k < ranks; k++) {
4433         for (j = 0; j < files; j++)
4434           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4435         if(gameInfo.holdingsWidth > 1) {
4436              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4437              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4438         }
4439       }
4440       CopyBoard(partnerBoard, board);
4441       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4442         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4443         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4444       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4445       if(toSqr = strchr(str, '-')) {
4446         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4447         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4448       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4449       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4450       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4451       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4452       if(twoBoards) {
4453           DisplayWhiteClock(white_time*fac, to_play == 'W');
4454           DisplayBlackClock(black_time*fac, to_play != 'W');
4455           activePartner = to_play;
4456           if(gamenum != lastBgGame) {
4457               char buf[MSG_SIZ];
4458               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4459               DisplayTitle(buf);
4460           }
4461           lastBgGame = gamenum;
4462           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4463                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4464       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4465                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4466       if(!twoBoards) DisplayMessage(partnerStatus, "");
4467         partnerBoardValid = TRUE;
4468       return;
4469     }
4470
4471     if(appData.dualBoard && appData.bgObserve) {
4472         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4473             SendToICS(ics_prefix), SendToICS("pobserve\n");
4474         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4475             char buf[MSG_SIZ];
4476             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4477             SendToICS(buf);
4478         }
4479     }
4480
4481     /* Modify behavior for initial board display on move listing
4482        of wild games.
4483        */
4484     switch (ics_getting_history) {
4485       case H_FALSE:
4486       case H_REQUESTED:
4487         break;
4488       case H_GOT_REQ_HEADER:
4489       case H_GOT_UNREQ_HEADER:
4490         /* This is the initial position of the current game */
4491         gamenum = ics_gamenum;
4492         moveNum = 0;            /* old ICS bug workaround */
4493         if (to_play == 'B') {
4494           startedFromSetupPosition = TRUE;
4495           blackPlaysFirst = TRUE;
4496           moveNum = 1;
4497           if (forwardMostMove == 0) forwardMostMove = 1;
4498           if (backwardMostMove == 0) backwardMostMove = 1;
4499           if (currentMove == 0) currentMove = 1;
4500         }
4501         newGameMode = gameMode;
4502         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4503         break;
4504       case H_GOT_UNWANTED_HEADER:
4505         /* This is an initial board that we don't want */
4506         return;
4507       case H_GETTING_MOVES:
4508         /* Should not happen */
4509         DisplayError(_("Error gathering move list: extra board"), 0);
4510         ics_getting_history = H_FALSE;
4511         return;
4512     }
4513
4514    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4515                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4516                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4517      /* [HGM] We seem to have switched variant unexpectedly
4518       * Try to guess new variant from board size
4519       */
4520           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4521           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4522           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4523           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4524           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4525           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4526           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4527           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4528           /* Get a move list just to see the header, which
4529              will tell us whether this is really bug or zh */
4530           if (ics_getting_history == H_FALSE) {
4531             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4532             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4533             SendToICS(str);
4534           }
4535     }
4536
4537     /* Take action if this is the first board of a new game, or of a
4538        different game than is currently being displayed.  */
4539     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4540         relation == RELATION_ISOLATED_BOARD) {
4541
4542         /* Forget the old game and get the history (if any) of the new one */
4543         if (gameMode != BeginningOfGame) {
4544           Reset(TRUE, TRUE);
4545         }
4546         newGame = TRUE;
4547         if (appData.autoRaiseBoard) BoardToTop();
4548         prevMove = -3;
4549         if (gamenum == -1) {
4550             newGameMode = IcsIdle;
4551         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4552                    appData.getMoveList && !reqFlag) {
4553             /* Need to get game history */
4554             ics_getting_history = H_REQUESTED;
4555             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4556             SendToICS(str);
4557         }
4558
4559         /* Initially flip the board to have black on the bottom if playing
4560            black or if the ICS flip flag is set, but let the user change
4561            it with the Flip View button. */
4562         flipView = appData.autoFlipView ?
4563           (newGameMode == IcsPlayingBlack) || ics_flip :
4564           appData.flipView;
4565
4566         /* Done with values from previous mode; copy in new ones */
4567         gameMode = newGameMode;
4568         ModeHighlight();
4569         ics_gamenum = gamenum;
4570         if (gamenum == gs_gamenum) {
4571             int klen = strlen(gs_kind);
4572             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4573             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4574             gameInfo.event = StrSave(str);
4575         } else {
4576             gameInfo.event = StrSave("ICS game");
4577         }
4578         gameInfo.site = StrSave(appData.icsHost);
4579         gameInfo.date = PGNDate();
4580         gameInfo.round = StrSave("-");
4581         gameInfo.white = StrSave(white);
4582         gameInfo.black = StrSave(black);
4583         timeControl = basetime * 60 * 1000;
4584         timeControl_2 = 0;
4585         timeIncrement = increment * 1000;
4586         movesPerSession = 0;
4587         gameInfo.timeControl = TimeControlTagValue();
4588         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4589   if (appData.debugMode) {
4590     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4591     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4592     setbuf(debugFP, NULL);
4593   }
4594
4595         gameInfo.outOfBook = NULL;
4596
4597         /* Do we have the ratings? */
4598         if (strcmp(player1Name, white) == 0 &&
4599             strcmp(player2Name, black) == 0) {
4600             if (appData.debugMode)
4601               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4602                       player1Rating, player2Rating);
4603             gameInfo.whiteRating = player1Rating;
4604             gameInfo.blackRating = player2Rating;
4605         } else if (strcmp(player2Name, white) == 0 &&
4606                    strcmp(player1Name, black) == 0) {
4607             if (appData.debugMode)
4608               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4609                       player2Rating, player1Rating);
4610             gameInfo.whiteRating = player2Rating;
4611             gameInfo.blackRating = player1Rating;
4612         }
4613         player1Name[0] = player2Name[0] = NULLCHAR;
4614
4615         /* Silence shouts if requested */
4616         if (appData.quietPlay &&
4617             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4618             SendToICS(ics_prefix);
4619             SendToICS("set shout 0\n");
4620         }
4621     }
4622
4623     /* Deal with midgame name changes */
4624     if (!newGame) {
4625         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4626             if (gameInfo.white) free(gameInfo.white);
4627             gameInfo.white = StrSave(white);
4628         }
4629         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4630             if (gameInfo.black) free(gameInfo.black);
4631             gameInfo.black = StrSave(black);
4632         }
4633     }
4634
4635     /* Throw away game result if anything actually changes in examine mode */
4636     if (gameMode == IcsExamining && !newGame) {
4637         gameInfo.result = GameUnfinished;
4638         if (gameInfo.resultDetails != NULL) {
4639             free(gameInfo.resultDetails);
4640             gameInfo.resultDetails = NULL;
4641         }
4642     }
4643
4644     /* In pausing && IcsExamining mode, we ignore boards coming
4645        in if they are in a different variation than we are. */
4646     if (pauseExamInvalid) return;
4647     if (pausing && gameMode == IcsExamining) {
4648         if (moveNum <= pauseExamForwardMostMove) {
4649             pauseExamInvalid = TRUE;
4650             forwardMostMove = pauseExamForwardMostMove;
4651             return;
4652         }
4653     }
4654
4655   if (appData.debugMode) {
4656     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4657   }
4658     /* Parse the board */
4659     for (k = 0; k < ranks; k++) {
4660       for (j = 0; j < files; j++)
4661         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4662       if(gameInfo.holdingsWidth > 1) {
4663            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4664            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4665       }
4666     }
4667     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4668       board[5][BOARD_RGHT+1] = WhiteAngel;
4669       board[6][BOARD_RGHT+1] = WhiteMarshall;
4670       board[1][0] = BlackMarshall;
4671       board[2][0] = BlackAngel;
4672       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4673     }
4674     CopyBoard(boards[moveNum], board);
4675     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4676     if (moveNum == 0) {
4677         startedFromSetupPosition =
4678           !CompareBoards(board, initialPosition);
4679         if(startedFromSetupPosition)
4680             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4681     }
4682
4683     /* [HGM] Set castling rights. Take the outermost Rooks,
4684        to make it also work for FRC opening positions. Note that board12
4685        is really defective for later FRC positions, as it has no way to
4686        indicate which Rook can castle if they are on the same side of King.
4687        For the initial position we grant rights to the outermost Rooks,
4688        and remember thos rights, and we then copy them on positions
4689        later in an FRC game. This means WB might not recognize castlings with
4690        Rooks that have moved back to their original position as illegal,
4691        but in ICS mode that is not its job anyway.
4692     */
4693     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4694     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4695
4696         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4697             if(board[0][i] == WhiteRook) j = i;
4698         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4699         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4700             if(board[0][i] == WhiteRook) j = i;
4701         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4702         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4703             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4704         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4705         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4706             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4707         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4708
4709         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4710         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4711         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4712             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4713         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4714             if(board[BOARD_HEIGHT-1][k] == bKing)
4715                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4716         if(gameInfo.variant == VariantTwoKings) {
4717             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4718             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4719             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4720         }
4721     } else { int r;
4722         r = boards[moveNum][CASTLING][0] = initialRights[0];
4723         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4724         r = boards[moveNum][CASTLING][1] = initialRights[1];
4725         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4726         r = boards[moveNum][CASTLING][3] = initialRights[3];
4727         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4728         r = boards[moveNum][CASTLING][4] = initialRights[4];
4729         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4730         /* wildcastle kludge: always assume King has rights */
4731         r = boards[moveNum][CASTLING][2] = initialRights[2];
4732         r = boards[moveNum][CASTLING][5] = initialRights[5];
4733     }
4734     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4735     boards[moveNum][EP_STATUS] = EP_NONE;
4736     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4737     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4738     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4739
4740
4741     if (ics_getting_history == H_GOT_REQ_HEADER ||
4742         ics_getting_history == H_GOT_UNREQ_HEADER) {
4743         /* This was an initial position from a move list, not
4744            the current position */
4745         return;
4746     }
4747
4748     /* Update currentMove and known move number limits */
4749     newMove = newGame || moveNum > forwardMostMove;
4750
4751     if (newGame) {
4752         forwardMostMove = backwardMostMove = currentMove = moveNum;
4753         if (gameMode == IcsExamining && moveNum == 0) {
4754           /* Workaround for ICS limitation: we are not told the wild
4755              type when starting to examine a game.  But if we ask for
4756              the move list, the move list header will tell us */
4757             ics_getting_history = H_REQUESTED;
4758             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4759             SendToICS(str);
4760         }
4761     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4762                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4763 #if ZIPPY
4764         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4765         /* [HGM] applied this also to an engine that is silently watching        */
4766         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4767             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4768             gameInfo.variant == currentlyInitializedVariant) {
4769           takeback = forwardMostMove - moveNum;
4770           for (i = 0; i < takeback; i++) {
4771             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4772             SendToProgram("undo\n", &first);
4773           }
4774         }
4775 #endif
4776
4777         forwardMostMove = moveNum;
4778         if (!pausing || currentMove > forwardMostMove)
4779           currentMove = forwardMostMove;
4780     } else {
4781         /* New part of history that is not contiguous with old part */
4782         if (pausing && gameMode == IcsExamining) {
4783             pauseExamInvalid = TRUE;
4784             forwardMostMove = pauseExamForwardMostMove;
4785             return;
4786         }
4787         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4788 #if ZIPPY
4789             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4790                 // [HGM] when we will receive the move list we now request, it will be
4791                 // fed to the engine from the first move on. So if the engine is not
4792                 // in the initial position now, bring it there.
4793                 InitChessProgram(&first, 0);
4794             }
4795 #endif
4796             ics_getting_history = H_REQUESTED;
4797             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4798             SendToICS(str);
4799         }
4800         forwardMostMove = backwardMostMove = currentMove = moveNum;
4801     }
4802
4803     /* Update the clocks */
4804     if (strchr(elapsed_time, '.')) {
4805       /* Time is in ms */
4806       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4807       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4808     } else {
4809       /* Time is in seconds */
4810       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4811       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4812     }
4813
4814
4815 #if ZIPPY
4816     if (appData.zippyPlay && newGame &&
4817         gameMode != IcsObserving && gameMode != IcsIdle &&
4818         gameMode != IcsExamining)
4819       ZippyFirstBoard(moveNum, basetime, increment);
4820 #endif
4821
4822     /* Put the move on the move list, first converting
4823        to canonical algebraic form. */
4824     if (moveNum > 0) {
4825   if (appData.debugMode) {
4826     int f = forwardMostMove;
4827     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4828             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4829             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4830     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4831     fprintf(debugFP, "moveNum = %d\n", moveNum);
4832     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4833     setbuf(debugFP, NULL);
4834   }
4835         if (moveNum <= backwardMostMove) {
4836             /* We don't know what the board looked like before
4837                this move.  Punt. */
4838           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4839             strcat(parseList[moveNum - 1], " ");
4840             strcat(parseList[moveNum - 1], elapsed_time);
4841             moveList[moveNum - 1][0] = NULLCHAR;
4842         } else if (strcmp(move_str, "none") == 0) {
4843             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4844             /* Again, we don't know what the board looked like;
4845                this is really the start of the game. */
4846             parseList[moveNum - 1][0] = NULLCHAR;
4847             moveList[moveNum - 1][0] = NULLCHAR;
4848             backwardMostMove = moveNum;
4849             startedFromSetupPosition = TRUE;
4850             fromX = fromY = toX = toY = -1;
4851         } else {
4852           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4853           //                 So we parse the long-algebraic move string in stead of the SAN move
4854           int valid; char buf[MSG_SIZ], *prom;
4855
4856           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4857                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4858           // str looks something like "Q/a1-a2"; kill the slash
4859           if(str[1] == '/')
4860             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4861           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4862           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4863                 strcat(buf, prom); // long move lacks promo specification!
4864           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4865                 if(appData.debugMode)
4866                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4867                 safeStrCpy(move_str, buf, MSG_SIZ);
4868           }
4869           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4870                                 &fromX, &fromY, &toX, &toY, &promoChar)
4871                || ParseOneMove(buf, moveNum - 1, &moveType,
4872                                 &fromX, &fromY, &toX, &toY, &promoChar);
4873           // end of long SAN patch
4874           if (valid) {
4875             (void) CoordsToAlgebraic(boards[moveNum - 1],
4876                                      PosFlags(moveNum - 1),
4877                                      fromY, fromX, toY, toX, promoChar,
4878                                      parseList[moveNum-1]);
4879             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4880               case MT_NONE:
4881               case MT_STALEMATE:
4882               default:
4883                 break;
4884               case MT_CHECK:
4885                 if(!IS_SHOGI(gameInfo.variant))
4886                     strcat(parseList[moveNum - 1], "+");
4887                 break;
4888               case MT_CHECKMATE:
4889               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4890                 strcat(parseList[moveNum - 1], "#");
4891                 break;
4892             }
4893             strcat(parseList[moveNum - 1], " ");
4894             strcat(parseList[moveNum - 1], elapsed_time);
4895             /* currentMoveString is set as a side-effect of ParseOneMove */
4896             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4897             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4898             strcat(moveList[moveNum - 1], "\n");
4899
4900             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4901                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4902               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4903                 ChessSquare old, new = boards[moveNum][k][j];
4904                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4905                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4906                   if(old == new) continue;
4907                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4908                   else if(new == WhiteWazir || new == BlackWazir) {
4909                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4910                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4911                       else boards[moveNum][k][j] = old; // preserve type of Gold
4912                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4913                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4914               }
4915           } else {
4916             /* Move from ICS was illegal!?  Punt. */
4917             if (appData.debugMode) {
4918               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4919               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4920             }
4921             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4922             strcat(parseList[moveNum - 1], " ");
4923             strcat(parseList[moveNum - 1], elapsed_time);
4924             moveList[moveNum - 1][0] = NULLCHAR;
4925             fromX = fromY = toX = toY = -1;
4926           }
4927         }
4928   if (appData.debugMode) {
4929     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4930     setbuf(debugFP, NULL);
4931   }
4932
4933 #if ZIPPY
4934         /* Send move to chess program (BEFORE animating it). */
4935         if (appData.zippyPlay && !newGame && newMove &&
4936            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4937
4938             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4939                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4940                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4941                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4942                             move_str);
4943                     DisplayError(str, 0);
4944                 } else {
4945                     if (first.sendTime) {
4946                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4947                     }
4948                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4949                     if (firstMove && !bookHit) {
4950                         firstMove = FALSE;
4951                         if (first.useColors) {
4952                           SendToProgram(gameMode == IcsPlayingWhite ?
4953                                         "white\ngo\n" :
4954                                         "black\ngo\n", &first);
4955                         } else {
4956                           SendToProgram("go\n", &first);
4957                         }
4958                         first.maybeThinking = TRUE;
4959                     }
4960                 }
4961             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4962               if (moveList[moveNum - 1][0] == NULLCHAR) {
4963                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4964                 DisplayError(str, 0);
4965               } else {
4966                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4967                 SendMoveToProgram(moveNum - 1, &first);
4968               }
4969             }
4970         }
4971 #endif
4972     }
4973
4974     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4975         /* If move comes from a remote source, animate it.  If it
4976            isn't remote, it will have already been animated. */
4977         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4978             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4979         }
4980         if (!pausing && appData.highlightLastMove) {
4981             SetHighlights(fromX, fromY, toX, toY);
4982         }
4983     }
4984
4985     /* Start the clocks */
4986     whiteFlag = blackFlag = FALSE;
4987     appData.clockMode = !(basetime == 0 && increment == 0);
4988     if (ticking == 0) {
4989       ics_clock_paused = TRUE;
4990       StopClocks();
4991     } else if (ticking == 1) {
4992       ics_clock_paused = FALSE;
4993     }
4994     if (gameMode == IcsIdle ||
4995         relation == RELATION_OBSERVING_STATIC ||
4996         relation == RELATION_EXAMINING ||
4997         ics_clock_paused)
4998       DisplayBothClocks();
4999     else
5000       StartClocks();
5001
5002     /* Display opponents and material strengths */
5003     if (gameInfo.variant != VariantBughouse &&
5004         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5005         if (tinyLayout || smallLayout) {
5006             if(gameInfo.variant == VariantNormal)
5007               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5008                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5009                     basetime, increment);
5010             else
5011               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5012                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5013                     basetime, increment, (int) gameInfo.variant);
5014         } else {
5015             if(gameInfo.variant == VariantNormal)
5016               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5017                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5018                     basetime, increment);
5019             else
5020               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5021                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5022                     basetime, increment, VariantName(gameInfo.variant));
5023         }
5024         DisplayTitle(str);
5025   if (appData.debugMode) {
5026     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5027   }
5028     }
5029
5030
5031     /* Display the board */
5032     if (!pausing && !appData.noGUI) {
5033
5034       if (appData.premove)
5035           if (!gotPremove ||
5036              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5037              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5038               ClearPremoveHighlights();
5039
5040       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5041         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5042       DrawPosition(j, boards[currentMove]);
5043
5044       DisplayMove(moveNum - 1);
5045       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5046             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5047               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5048         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5049       }
5050     }
5051
5052     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5053 #if ZIPPY
5054     if(bookHit) { // [HGM] book: simulate book reply
5055         static char bookMove[MSG_SIZ]; // a bit generous?
5056
5057         programStats.nodes = programStats.depth = programStats.time =
5058         programStats.score = programStats.got_only_move = 0;
5059         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5060
5061         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5062         strcat(bookMove, bookHit);
5063         HandleMachineMove(bookMove, &first);
5064     }
5065 #endif
5066 }
5067
5068 void
5069 GetMoveListEvent ()
5070 {
5071     char buf[MSG_SIZ];
5072     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5073         ics_getting_history = H_REQUESTED;
5074         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5075         SendToICS(buf);
5076     }
5077 }
5078
5079 void
5080 SendToBoth (char *msg)
5081 {   // to make it easy to keep two engines in step in dual analysis
5082     SendToProgram(msg, &first);
5083     if(second.analyzing) SendToProgram(msg, &second);
5084 }
5085
5086 void
5087 AnalysisPeriodicEvent (int force)
5088 {
5089     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5090          && !force) || !appData.periodicUpdates)
5091       return;
5092
5093     /* Send . command to Crafty to collect stats */
5094     SendToBoth(".\n");
5095
5096     /* Don't send another until we get a response (this makes
5097        us stop sending to old Crafty's which don't understand
5098        the "." command (sending illegal cmds resets node count & time,
5099        which looks bad)) */
5100     programStats.ok_to_send = 0;
5101 }
5102
5103 void
5104 ics_update_width (int new_width)
5105 {
5106         ics_printf("set width %d\n", new_width);
5107 }
5108
5109 void
5110 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5111 {
5112     char buf[MSG_SIZ];
5113
5114     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5115         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5116             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5117             SendToProgram(buf, cps);
5118             return;
5119         }
5120         // null move in variant where engine does not understand it (for analysis purposes)
5121         SendBoard(cps, moveNum + 1); // send position after move in stead.
5122         return;
5123     }
5124     if (cps->useUsermove) {
5125       SendToProgram("usermove ", cps);
5126     }
5127     if (cps->useSAN) {
5128       char *space;
5129       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5130         int len = space - parseList[moveNum];
5131         memcpy(buf, parseList[moveNum], len);
5132         buf[len++] = '\n';
5133         buf[len] = NULLCHAR;
5134       } else {
5135         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5136       }
5137       SendToProgram(buf, cps);
5138     } else {
5139       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5140         AlphaRank(moveList[moveNum], 4);
5141         SendToProgram(moveList[moveNum], cps);
5142         AlphaRank(moveList[moveNum], 4); // and back
5143       } else
5144       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5145        * the engine. It would be nice to have a better way to identify castle
5146        * moves here. */
5147       if(appData.fischerCastling && cps->useOOCastle) {
5148         int fromX = moveList[moveNum][0] - AAA;
5149         int fromY = moveList[moveNum][1] - ONE;
5150         int toX = moveList[moveNum][2] - AAA;
5151         int toY = moveList[moveNum][3] - ONE;
5152         if((boards[moveNum][fromY][fromX] == WhiteKing
5153             && boards[moveNum][toY][toX] == WhiteRook)
5154            || (boards[moveNum][fromY][fromX] == BlackKing
5155                && boards[moveNum][toY][toX] == BlackRook)) {
5156           if(toX > fromX) SendToProgram("O-O\n", cps);
5157           else SendToProgram("O-O-O\n", cps);
5158         }
5159         else SendToProgram(moveList[moveNum], cps);
5160       } else
5161       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5162         char *m = moveList[moveNum];
5163         static char c[2];
5164         *c = m[7]; // promoChar
5165         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
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5167                                                m[2], m[3] - '0',
5168                                                m[5], m[6] - '0',
5169                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5170         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5171           *c = m[9];
5172           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
5173                                                m[7], m[8] - '0',
5174                                                m[7], m[8] - '0',
5175                                                m[5], m[6] - '0',
5176                                                m[5], m[6] - '0',
5177                                                m[2], m[3] - '0', c);
5178         } else
5179           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5180                                                m[5], m[6] - '0',
5181                                                m[5], m[6] - '0',
5182                                                m[2], m[3] - '0', c);
5183           SendToProgram(buf, cps);
5184       } else
5185       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5186         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5187           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5188           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5189                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5190         } else
5191           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5192                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5193         SendToProgram(buf, cps);
5194       }
5195       else SendToProgram(moveList[moveNum], cps);
5196       /* End of additions by Tord */
5197     }
5198
5199     /* [HGM] setting up the opening has brought engine in force mode! */
5200     /*       Send 'go' if we are in a mode where machine should play. */
5201     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5202         (gameMode == TwoMachinesPlay   ||
5203 #if ZIPPY
5204          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5205 #endif
5206          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5207         SendToProgram("go\n", cps);
5208   if (appData.debugMode) {
5209     fprintf(debugFP, "(extra)\n");
5210   }
5211     }
5212     setboardSpoiledMachineBlack = 0;
5213 }
5214
5215 void
5216 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5217 {
5218     char user_move[MSG_SIZ];
5219     char suffix[4];
5220
5221     if(gameInfo.variant == VariantSChess && promoChar) {
5222         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5223         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5224     } else suffix[0] = NULLCHAR;
5225
5226     switch (moveType) {
5227       default:
5228         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5229                 (int)moveType, fromX, fromY, toX, toY);
5230         DisplayError(user_move + strlen("say "), 0);
5231         break;
5232       case WhiteKingSideCastle:
5233       case BlackKingSideCastle:
5234       case WhiteQueenSideCastleWild:
5235       case BlackQueenSideCastleWild:
5236       /* PUSH Fabien */
5237       case WhiteHSideCastleFR:
5238       case BlackHSideCastleFR:
5239       /* POP Fabien */
5240         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5241         break;
5242       case WhiteQueenSideCastle:
5243       case BlackQueenSideCastle:
5244       case WhiteKingSideCastleWild:
5245       case BlackKingSideCastleWild:
5246       /* PUSH Fabien */
5247       case WhiteASideCastleFR:
5248       case BlackASideCastleFR:
5249       /* POP Fabien */
5250         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5251         break;
5252       case WhiteNonPromotion:
5253       case BlackNonPromotion:
5254         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5255         break;
5256       case WhitePromotion:
5257       case BlackPromotion:
5258         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5259            gameInfo.variant == VariantMakruk)
5260           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5261                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5262                 PieceToChar(WhiteFerz));
5263         else if(gameInfo.variant == VariantGreat)
5264           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5265                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5266                 PieceToChar(WhiteMan));
5267         else
5268           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5269                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5270                 promoChar);
5271         break;
5272       case WhiteDrop:
5273       case BlackDrop:
5274       drop:
5275         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5276                  ToUpper(PieceToChar((ChessSquare) fromX)),
5277                  AAA + toX, ONE + toY);
5278         break;
5279       case IllegalMove:  /* could be a variant we don't quite understand */
5280         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5281       case NormalMove:
5282       case WhiteCapturesEnPassant:
5283       case BlackCapturesEnPassant:
5284         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5285                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5286         break;
5287     }
5288     SendToICS(user_move);
5289     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5290         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5291 }
5292
5293 void
5294 UploadGameEvent ()
5295 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5296     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5297     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5298     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5299       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5300       return;
5301     }
5302     if(gameMode != IcsExamining) { // is this ever not the case?
5303         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5304
5305         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5306           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5307         } else { // on FICS we must first go to general examine mode
5308           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5309         }
5310         if(gameInfo.variant != VariantNormal) {
5311             // try figure out wild number, as xboard names are not always valid on ICS
5312             for(i=1; i<=36; i++) {
5313               snprintf(buf, MSG_SIZ, "wild/%d", i);
5314                 if(StringToVariant(buf) == gameInfo.variant) break;
5315             }
5316             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5317             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5318             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5319         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5320         SendToICS(ics_prefix);
5321         SendToICS(buf);
5322         if(startedFromSetupPosition || backwardMostMove != 0) {
5323           fen = PositionToFEN(backwardMostMove, NULL, 1);
5324           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5325             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5326             SendToICS(buf);
5327           } else { // FICS: everything has to set by separate bsetup commands
5328             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5329             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5330             SendToICS(buf);
5331             if(!WhiteOnMove(backwardMostMove)) {
5332                 SendToICS("bsetup tomove black\n");
5333             }
5334             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5335             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5336             SendToICS(buf);
5337             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5338             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5339             SendToICS(buf);
5340             i = boards[backwardMostMove][EP_STATUS];
5341             if(i >= 0) { // set e.p.
5342               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5343                 SendToICS(buf);
5344             }
5345             bsetup++;
5346           }
5347         }
5348       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5349             SendToICS("bsetup done\n"); // switch to normal examining.
5350     }
5351     for(i = backwardMostMove; i<last; i++) {
5352         char buf[20];
5353         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5354         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5355             int len = strlen(moveList[i]);
5356             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5357             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5358         }
5359         SendToICS(buf);
5360     }
5361     SendToICS(ics_prefix);
5362     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5363 }
5364
5365 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5366 int legNr = 1;
5367
5368 void
5369 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5370 {
5371     if (rf == DROP_RANK) {
5372       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5373       sprintf(move, "%c@%c%c\n",
5374                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5375     } else {
5376         if (promoChar == 'x' || promoChar == NULLCHAR) {
5377           sprintf(move, "%c%c%c%c\n",
5378                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5379           if(killX >= 0 && killY >= 0) {
5380             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5381             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5382           }
5383         } else {
5384             sprintf(move, "%c%c%c%c%c\n",
5385                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5386           if(killX >= 0 && killY >= 0) {
5387             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5388             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5389           }
5390         }
5391     }
5392 }
5393
5394 void
5395 ProcessICSInitScript (FILE *f)
5396 {
5397     char buf[MSG_SIZ];
5398
5399     while (fgets(buf, MSG_SIZ, f)) {
5400         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5401     }
5402
5403     fclose(f);
5404 }
5405
5406
5407 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5408 int dragging;
5409 static ClickType lastClickType;
5410
5411 int
5412 PieceInString (char *s, ChessSquare piece)
5413 {
5414   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5415   while((p = strchr(s, ID))) {
5416     if(!suffix || p[1] == suffix) return TRUE;
5417     s = p;
5418   }
5419   return FALSE;
5420 }
5421
5422 int
5423 Partner (ChessSquare *p)
5424 { // change piece into promotion partner if one shogi-promotes to the other
5425   ChessSquare partner = promoPartner[*p];
5426   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5427   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5428   *p = partner;
5429   return 1;
5430 }
5431
5432 void
5433 Sweep (int step)
5434 {
5435     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5436     static int toggleFlag;
5437     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5438     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5439     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5440     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5441     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5442     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5443     do {
5444         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5445         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5446         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5447         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5448         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5449         if(!step) step = -1;
5450     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5451             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5452             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5453             promoSweep == pawn ||
5454             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5455             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5456     if(toX >= 0) {
5457         int victim = boards[currentMove][toY][toX];
5458         boards[currentMove][toY][toX] = promoSweep;
5459         DrawPosition(FALSE, boards[currentMove]);
5460         boards[currentMove][toY][toX] = victim;
5461     } else
5462     ChangeDragPiece(promoSweep);
5463 }
5464
5465 int
5466 PromoScroll (int x, int y)
5467 {
5468   int step = 0;
5469
5470   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5471   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5472   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5473   if(!step) return FALSE;
5474   lastX = x; lastY = y;
5475   if((promoSweep < BlackPawn) == flipView) step = -step;
5476   if(step > 0) selectFlag = 1;
5477   if(!selectFlag) Sweep(step);
5478   return FALSE;
5479 }
5480
5481 void
5482 NextPiece (int step)
5483 {
5484     ChessSquare piece = boards[currentMove][toY][toX];
5485     do {
5486         pieceSweep -= step;
5487         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5488         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5489         if(!step) step = -1;
5490     } while(PieceToChar(pieceSweep) == '.');
5491     boards[currentMove][toY][toX] = pieceSweep;
5492     DrawPosition(FALSE, boards[currentMove]);
5493     boards[currentMove][toY][toX] = piece;
5494 }
5495 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5496 void
5497 AlphaRank (char *move, int n)
5498 {
5499 //    char *p = move, c; int x, y;
5500
5501     if (appData.debugMode) {
5502         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5503     }
5504
5505     if(move[1]=='*' &&
5506        move[2]>='0' && move[2]<='9' &&
5507        move[3]>='a' && move[3]<='x'    ) {
5508         move[1] = '@';
5509         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5510         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5511     } else
5512     if(move[0]>='0' && move[0]<='9' &&
5513        move[1]>='a' && move[1]<='x' &&
5514        move[2]>='0' && move[2]<='9' &&
5515        move[3]>='a' && move[3]<='x'    ) {
5516         /* input move, Shogi -> normal */
5517         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5518         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5519         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5520         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5521     } else
5522     if(move[1]=='@' &&
5523        move[3]>='0' && move[3]<='9' &&
5524        move[2]>='a' && move[2]<='x'    ) {
5525         move[1] = '*';
5526         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5527         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5528     } else
5529     if(
5530        move[0]>='a' && move[0]<='x' &&
5531        move[3]>='0' && move[3]<='9' &&
5532        move[2]>='a' && move[2]<='x'    ) {
5533          /* output move, normal -> Shogi */
5534         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5535         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5536         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5537         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5538         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5539     }
5540     if (appData.debugMode) {
5541         fprintf(debugFP, "   out = '%s'\n", move);
5542     }
5543 }
5544
5545 char yy_textstr[8000];
5546
5547 /* Parser for moves from gnuchess, ICS, or user typein box */
5548 Boolean
5549 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5550 {
5551     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5552
5553     switch (*moveType) {
5554       case WhitePromotion:
5555       case BlackPromotion:
5556       case WhiteNonPromotion:
5557       case BlackNonPromotion:
5558       case NormalMove:
5559       case FirstLeg:
5560       case WhiteCapturesEnPassant:
5561       case BlackCapturesEnPassant:
5562       case WhiteKingSideCastle:
5563       case WhiteQueenSideCastle:
5564       case BlackKingSideCastle:
5565       case BlackQueenSideCastle:
5566       case WhiteKingSideCastleWild:
5567       case WhiteQueenSideCastleWild:
5568       case BlackKingSideCastleWild:
5569       case BlackQueenSideCastleWild:
5570       /* Code added by Tord: */
5571       case WhiteHSideCastleFR:
5572       case WhiteASideCastleFR:
5573       case BlackHSideCastleFR:
5574       case BlackASideCastleFR:
5575       /* End of code added by Tord */
5576       case IllegalMove:         /* bug or odd chess variant */
5577         if(currentMoveString[1] == '@') { // illegal drop
5578           *fromX = WhiteOnMove(moveNum) ?
5579             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5580             (int) CharToPiece(ToLower(currentMoveString[0]));
5581           goto drop;
5582         }
5583         *fromX = currentMoveString[0] - AAA;
5584         *fromY = currentMoveString[1] - ONE;
5585         *toX = currentMoveString[2] - AAA;
5586         *toY = currentMoveString[3] - ONE;
5587         *promoChar = currentMoveString[4];
5588         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5589         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5590             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5591     if (appData.debugMode) {
5592         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5593     }
5594             *fromX = *fromY = *toX = *toY = 0;
5595             return FALSE;
5596         }
5597         if (appData.testLegality) {
5598           return (*moveType != IllegalMove);
5599         } else {
5600           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5601                          // [HGM] lion: if this is a double move we are less critical
5602                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5603         }
5604
5605       case WhiteDrop:
5606       case BlackDrop:
5607         *fromX = *moveType == WhiteDrop ?
5608           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5609           (int) CharToPiece(ToLower(currentMoveString[0]));
5610       drop:
5611         *fromY = DROP_RANK;
5612         *toX = currentMoveString[2] - AAA;
5613         *toY = currentMoveString[3] - ONE;
5614         *promoChar = NULLCHAR;
5615         return TRUE;
5616
5617       case AmbiguousMove:
5618       case ImpossibleMove:
5619       case EndOfFile:
5620       case ElapsedTime:
5621       case Comment:
5622       case PGNTag:
5623       case NAG:
5624       case WhiteWins:
5625       case BlackWins:
5626       case GameIsDrawn:
5627       default:
5628     if (appData.debugMode) {
5629         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5630     }
5631         /* bug? */
5632         *fromX = *fromY = *toX = *toY = 0;
5633         *promoChar = NULLCHAR;
5634         return FALSE;
5635     }
5636 }
5637
5638 Boolean pushed = FALSE;
5639 char *lastParseAttempt;
5640
5641 void
5642 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5643 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5644   int fromX, fromY, toX, toY; char promoChar;
5645   ChessMove moveType;
5646   Boolean valid;
5647   int nr = 0;
5648
5649   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5650   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5651     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5652     pushed = TRUE;
5653   }
5654   endPV = forwardMostMove;
5655   do {
5656     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5657     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5658     lastParseAttempt = pv;
5659     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5660     if(!valid && nr == 0 &&
5661        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5662         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5663         // Hande case where played move is different from leading PV move
5664         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5665         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5666         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5667         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5668           endPV += 2; // if position different, keep this
5669           moveList[endPV-1][0] = fromX + AAA;
5670           moveList[endPV-1][1] = fromY + ONE;
5671           moveList[endPV-1][2] = toX + AAA;
5672           moveList[endPV-1][3] = toY + ONE;
5673           parseList[endPV-1][0] = NULLCHAR;
5674           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5675         }
5676       }
5677     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5678     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5679     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5680     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5681         valid++; // allow comments in PV
5682         continue;
5683     }
5684     nr++;
5685     if(endPV+1 > framePtr) break; // no space, truncate
5686     if(!valid) break;
5687     endPV++;
5688     CopyBoard(boards[endPV], boards[endPV-1]);
5689     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5690     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5691     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5692     CoordsToAlgebraic(boards[endPV - 1],
5693                              PosFlags(endPV - 1),
5694                              fromY, fromX, toY, toX, promoChar,
5695                              parseList[endPV - 1]);
5696   } while(valid);
5697   if(atEnd == 2) return; // used hidden, for PV conversion
5698   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5699   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5700   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5701                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5702   DrawPosition(TRUE, boards[currentMove]);
5703 }
5704
5705 int
5706 MultiPV (ChessProgramState *cps, int kind)
5707 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5708         int i;
5709         for(i=0; i<cps->nrOptions; i++) {
5710             char *s = cps->option[i].name;
5711             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5712             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5713                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5714         }
5715         return -1;
5716 }
5717
5718 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5719 static int multi, pv_margin;
5720 static ChessProgramState *activeCps;
5721
5722 Boolean
5723 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5724 {
5725         int startPV, lineStart, origIndex = index;
5726         char *p, buf2[MSG_SIZ];
5727         ChessProgramState *cps = (pane ? &second : &first);
5728
5729         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5730         lastX = x; lastY = y;
5731         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5732         lineStart = startPV = index;
5733         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5734         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5735         index = startPV;
5736         do{ while(buf[index] && buf[index] != '\n') index++;
5737         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5738         buf[index] = 0;
5739         if(lineStart == 0 && gameMode == AnalyzeMode) {
5740             int n = 0;
5741             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5742             if(n == 0) { // click not on "fewer" or "more"
5743                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5744                     pv_margin = cps->option[multi].value;
5745                     activeCps = cps; // non-null signals margin adjustment
5746                 }
5747             } else if((multi = MultiPV(cps, 1)) >= 0) {
5748                 n += cps->option[multi].value; if(n < 1) n = 1;
5749                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5750                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5751                 cps->option[multi].value = n;
5752                 *start = *end = 0;
5753                 return FALSE;
5754             }
5755         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5756                 ExcludeClick(origIndex - lineStart);
5757                 return FALSE;
5758         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5759                 Collapse(origIndex - lineStart);
5760                 return FALSE;
5761         }
5762         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5763         *start = startPV; *end = index-1;
5764         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5765         return TRUE;
5766 }
5767
5768 char *
5769 PvToSAN (char *pv)
5770 {
5771         static char buf[10*MSG_SIZ];
5772         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5773         *buf = NULLCHAR;
5774         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5775         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5776         for(i = forwardMostMove; i<endPV; i++){
5777             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5778             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5779             k += strlen(buf+k);
5780         }
5781         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5782         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5783         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5784         endPV = savedEnd;
5785         return buf;
5786 }
5787
5788 Boolean
5789 LoadPV (int x, int y)
5790 { // called on right mouse click to load PV
5791   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5792   lastX = x; lastY = y;
5793   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5794   extendGame = FALSE;
5795   return TRUE;
5796 }
5797
5798 void
5799 UnLoadPV ()
5800 {
5801   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5802   if(activeCps) {
5803     if(pv_margin != activeCps->option[multi].value) {
5804       char buf[MSG_SIZ];
5805       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5806       SendToProgram(buf, activeCps);
5807       activeCps->option[multi].value = pv_margin;
5808     }
5809     activeCps = NULL;
5810     return;
5811   }
5812   if(endPV < 0) return;
5813   if(appData.autoCopyPV) CopyFENToClipboard();
5814   endPV = -1;
5815   if(extendGame && currentMove > forwardMostMove) {
5816         Boolean saveAnimate = appData.animate;
5817         if(pushed) {
5818             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5819                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5820             } else storedGames--; // abandon shelved tail of original game
5821         }
5822         pushed = FALSE;
5823         forwardMostMove = currentMove;
5824         currentMove = oldFMM;
5825         appData.animate = FALSE;
5826         ToNrEvent(forwardMostMove);
5827         appData.animate = saveAnimate;
5828   }
5829   currentMove = forwardMostMove;
5830   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5831   ClearPremoveHighlights();
5832   DrawPosition(TRUE, boards[currentMove]);
5833 }
5834
5835 void
5836 MovePV (int x, int y, int h)
5837 { // step through PV based on mouse coordinates (called on mouse move)
5838   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5839
5840   if(activeCps) { // adjusting engine's multi-pv margin
5841     if(x > lastX) pv_margin++; else
5842     if(x < lastX) pv_margin -= (pv_margin > 0);
5843     if(x != lastX) {
5844       char buf[MSG_SIZ];
5845       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5846       DisplayMessage(buf, "");
5847     }
5848     lastX = x;
5849     return;
5850   }
5851   // we must somehow check if right button is still down (might be released off board!)
5852   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5853   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5854   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5855   if(!step) return;
5856   lastX = x; lastY = y;
5857
5858   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5859   if(endPV < 0) return;
5860   if(y < margin) step = 1; else
5861   if(y > h - margin) step = -1;
5862   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5863   currentMove += step;
5864   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5865   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5866                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5867   DrawPosition(FALSE, boards[currentMove]);
5868 }
5869
5870
5871 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5872 // All positions will have equal probability, but the current method will not provide a unique
5873 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5874 #define DARK 1
5875 #define LITE 2
5876 #define ANY 3
5877
5878 int squaresLeft[4];
5879 int piecesLeft[(int)BlackPawn];
5880 int seed, nrOfShuffles;
5881
5882 void
5883 GetPositionNumber ()
5884 {       // sets global variable seed
5885         int i;
5886
5887         seed = appData.defaultFrcPosition;
5888         if(seed < 0) { // randomize based on time for negative FRC position numbers
5889                 for(i=0; i<50; i++) seed += random();
5890                 seed = random() ^ random() >> 8 ^ random() << 8;
5891                 if(seed<0) seed = -seed;
5892         }
5893 }
5894
5895 int
5896 put (Board board, int pieceType, int rank, int n, int shade)
5897 // put the piece on the (n-1)-th empty squares of the given shade
5898 {
5899         int i;
5900
5901         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5902                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5903                         board[rank][i] = (ChessSquare) pieceType;
5904                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5905                         squaresLeft[ANY]--;
5906                         piecesLeft[pieceType]--;
5907                         return i;
5908                 }
5909         }
5910         return -1;
5911 }
5912
5913
5914 void
5915 AddOnePiece (Board board, int pieceType, int rank, int shade)
5916 // calculate where the next piece goes, (any empty square), and put it there
5917 {
5918         int i;
5919
5920         i = seed % squaresLeft[shade];
5921         nrOfShuffles *= squaresLeft[shade];
5922         seed /= squaresLeft[shade];
5923         put(board, pieceType, rank, i, shade);
5924 }
5925
5926 void
5927 AddTwoPieces (Board board, int pieceType, int rank)
5928 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5929 {
5930         int i, n=squaresLeft[ANY], j=n-1, k;
5931
5932         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5933         i = seed % k;  // pick one
5934         nrOfShuffles *= k;
5935         seed /= k;
5936         while(i >= j) i -= j--;
5937         j = n - 1 - j; i += j;
5938         put(board, pieceType, rank, j, ANY);
5939         put(board, pieceType, rank, i, ANY);
5940 }
5941
5942 void
5943 SetUpShuffle (Board board, int number)
5944 {
5945         int i, p, first=1;
5946
5947         GetPositionNumber(); nrOfShuffles = 1;
5948
5949         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5950         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5951         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5952
5953         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5954
5955         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5956             p = (int) board[0][i];
5957             if(p < (int) BlackPawn) piecesLeft[p] ++;
5958             board[0][i] = EmptySquare;
5959         }
5960
5961         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5962             // shuffles restricted to allow normal castling put KRR first
5963             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5964                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5965             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5966                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5967             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5968                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5969             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5970                 put(board, WhiteRook, 0, 0, ANY);
5971             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5972         }
5973
5974         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5975             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5976             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5977                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5978                 while(piecesLeft[p] >= 2) {
5979                     AddOnePiece(board, p, 0, LITE);
5980                     AddOnePiece(board, p, 0, DARK);
5981                 }
5982                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5983             }
5984
5985         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5986             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5987             // but we leave King and Rooks for last, to possibly obey FRC restriction
5988             if(p == (int)WhiteRook) continue;
5989             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5990             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5991         }
5992
5993         // now everything is placed, except perhaps King (Unicorn) and Rooks
5994
5995         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5996             // Last King gets castling rights
5997             while(piecesLeft[(int)WhiteUnicorn]) {
5998                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5999                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6000             }
6001
6002             while(piecesLeft[(int)WhiteKing]) {
6003                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6004                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6005             }
6006
6007
6008         } else {
6009             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6010             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6011         }
6012
6013         // Only Rooks can be left; simply place them all
6014         while(piecesLeft[(int)WhiteRook]) {
6015                 i = put(board, WhiteRook, 0, 0, ANY);
6016                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6017                         if(first) {
6018                                 first=0;
6019                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6020                         }
6021                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6022                 }
6023         }
6024         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6025             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6026         }
6027
6028         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6029 }
6030
6031 int
6032 ptclen (const char *s, char *escapes)
6033 {
6034     int n = 0;
6035     if(!*escapes) return strlen(s);
6036     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6037     return n;
6038 }
6039
6040 int
6041 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6042 /* [HGM] moved here from winboard.c because of its general usefulness */
6043 /*       Basically a safe strcpy that uses the last character as King */
6044 {
6045     int result = FALSE; int NrPieces;
6046     unsigned char partner[EmptySquare];
6047
6048     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6049                     && NrPieces >= 12 && !(NrPieces&1)) {
6050         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6051
6052         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6053         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6054             char *p, c=0;
6055             if(map[j] == '/') offs = WhitePBishop - i, j++;
6056             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6057             table[i+offs] = map[j++];
6058             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6059             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6060             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6061         }
6062         table[(int) WhiteKing]  = map[j++];
6063         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6064             char *p, c=0;
6065             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6066             i = WHITE_TO_BLACK ii;
6067             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6068             table[i+offs] = map[j++];
6069             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6070             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6071             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6072         }
6073         table[(int) BlackKing]  = map[j++];
6074
6075
6076         if(*escapes) { // set up promotion pairing
6077             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6078             // pieceToChar entirely filled, so we can look up specified partners
6079             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6080                 int c = table[i];
6081                 if(c == '^' || c == '-') { // has specified partner
6082                     int p;
6083                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6084                     if(c == '^') table[i] = '+';
6085                     if(p < EmptySquare) {
6086                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6087                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6088                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6089                     }
6090                 } else if(c == '*') {
6091                     table[i] = partner[i];
6092                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6093                 }
6094             }
6095         }
6096
6097         result = TRUE;
6098     }
6099
6100     return result;
6101 }
6102
6103 int
6104 SetCharTable (unsigned char *table, const char * map)
6105 {
6106     return SetCharTableEsc(table, map, "");
6107 }
6108
6109 void
6110 Prelude (Board board)
6111 {       // [HGM] superchess: random selection of exo-pieces
6112         int i, j, k; ChessSquare p;
6113         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6114
6115         GetPositionNumber(); // use FRC position number
6116
6117         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6118             SetCharTable(pieceToChar, appData.pieceToCharTable);
6119             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6120                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6121         }
6122
6123         j = seed%4;                 seed /= 4;
6124         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6125         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6126         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6127         j = seed%3 + (seed%3 >= j); seed /= 3;
6128         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6129         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6130         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6131         j = seed%3;                 seed /= 3;
6132         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6133         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6134         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6135         j = seed%2 + (seed%2 >= j); seed /= 2;
6136         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6137         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6138         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6139         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6140         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6141         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6142         put(board, exoPieces[0],    0, 0, ANY);
6143         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6144 }
6145
6146 void
6147 InitPosition (int redraw)
6148 {
6149     ChessSquare (* pieces)[BOARD_FILES];
6150     int i, j, pawnRow=1, pieceRows=1, overrule,
6151     oldx = gameInfo.boardWidth,
6152     oldy = gameInfo.boardHeight,
6153     oldh = gameInfo.holdingsWidth;
6154     static int oldv;
6155
6156     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6157
6158     /* [AS] Initialize pv info list [HGM] and game status */
6159     {
6160         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6161             pvInfoList[i].depth = 0;
6162             boards[i][EP_STATUS] = EP_NONE;
6163             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6164         }
6165
6166         initialRulePlies = 0; /* 50-move counter start */
6167
6168         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6169         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6170     }
6171
6172
6173     /* [HGM] logic here is completely changed. In stead of full positions */
6174     /* the initialized data only consist of the two backranks. The switch */
6175     /* selects which one we will use, which is than copied to the Board   */
6176     /* initialPosition, which for the rest is initialized by Pawns and    */
6177     /* empty squares. This initial position is then copied to boards[0],  */
6178     /* possibly after shuffling, so that it remains available.            */
6179
6180     gameInfo.holdingsWidth = 0; /* default board sizes */
6181     gameInfo.boardWidth    = 8;
6182     gameInfo.boardHeight   = 8;
6183     gameInfo.holdingsSize  = 0;
6184     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6185     for(i=0; i<BOARD_FILES-6; i++)
6186       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6187     initialPosition[EP_STATUS] = EP_NONE;
6188     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6189     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6190     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6191          SetCharTable(pieceNickName, appData.pieceNickNames);
6192     else SetCharTable(pieceNickName, "............");
6193     pieces = FIDEArray;
6194
6195     switch (gameInfo.variant) {
6196     case VariantFischeRandom:
6197       shuffleOpenings = TRUE;
6198       appData.fischerCastling = TRUE;
6199     default:
6200       break;
6201     case VariantShatranj:
6202       pieces = ShatranjArray;
6203       nrCastlingRights = 0;
6204       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6205       break;
6206     case VariantMakruk:
6207       pieces = makrukArray;
6208       nrCastlingRights = 0;
6209       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6210       break;
6211     case VariantASEAN:
6212       pieces = aseanArray;
6213       nrCastlingRights = 0;
6214       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6215       break;
6216     case VariantTwoKings:
6217       pieces = twoKingsArray;
6218       break;
6219     case VariantGrand:
6220       pieces = GrandArray;
6221       nrCastlingRights = 0;
6222       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6223       gameInfo.boardWidth = 10;
6224       gameInfo.boardHeight = 10;
6225       gameInfo.holdingsSize = 7;
6226       break;
6227     case VariantCapaRandom:
6228       shuffleOpenings = TRUE;
6229       appData.fischerCastling = TRUE;
6230     case VariantCapablanca:
6231       pieces = CapablancaArray;
6232       gameInfo.boardWidth = 10;
6233       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6234       break;
6235     case VariantGothic:
6236       pieces = GothicArray;
6237       gameInfo.boardWidth = 10;
6238       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6239       break;
6240     case VariantSChess:
6241       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6242       gameInfo.holdingsSize = 7;
6243       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6244       break;
6245     case VariantJanus:
6246       pieces = JanusArray;
6247       gameInfo.boardWidth = 10;
6248       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6249       nrCastlingRights = 6;
6250         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6251         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6252         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6253         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6254         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6255         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6256       break;
6257     case VariantFalcon:
6258       pieces = FalconArray;
6259       gameInfo.boardWidth = 10;
6260       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6261       break;
6262     case VariantXiangqi:
6263       pieces = XiangqiArray;
6264       gameInfo.boardWidth  = 9;
6265       gameInfo.boardHeight = 10;
6266       nrCastlingRights = 0;
6267       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6268       break;
6269     case VariantShogi:
6270       pieces = ShogiArray;
6271       gameInfo.boardWidth  = 9;
6272       gameInfo.boardHeight = 9;
6273       gameInfo.holdingsSize = 7;
6274       nrCastlingRights = 0;
6275       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6276       break;
6277     case VariantChu:
6278       pieces = ChuArray; pieceRows = 3;
6279       gameInfo.boardWidth  = 12;
6280       gameInfo.boardHeight = 12;
6281       nrCastlingRights = 0;
6282       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6283                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6284       break;
6285     case VariantCourier:
6286       pieces = CourierArray;
6287       gameInfo.boardWidth  = 12;
6288       nrCastlingRights = 0;
6289       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6290       break;
6291     case VariantKnightmate:
6292       pieces = KnightmateArray;
6293       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6294       break;
6295     case VariantSpartan:
6296       pieces = SpartanArray;
6297       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6298       break;
6299     case VariantLion:
6300       pieces = lionArray;
6301       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6302       break;
6303     case VariantChuChess:
6304       pieces = ChuChessArray;
6305       gameInfo.boardWidth = 10;
6306       gameInfo.boardHeight = 10;
6307       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6308       break;
6309     case VariantFairy:
6310       pieces = fairyArray;
6311       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6312       break;
6313     case VariantGreat:
6314       pieces = GreatArray;
6315       gameInfo.boardWidth = 10;
6316       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6317       gameInfo.holdingsSize = 8;
6318       break;
6319     case VariantSuper:
6320       pieces = FIDEArray;
6321       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6322       gameInfo.holdingsSize = 8;
6323       startedFromSetupPosition = TRUE;
6324       break;
6325     case VariantCrazyhouse:
6326     case VariantBughouse:
6327       pieces = FIDEArray;
6328       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6329       gameInfo.holdingsSize = 5;
6330       break;
6331     case VariantWildCastle:
6332       pieces = FIDEArray;
6333       /* !!?shuffle with kings guaranteed to be on d or e file */
6334       shuffleOpenings = 1;
6335       break;
6336     case VariantNoCastle:
6337       pieces = FIDEArray;
6338       nrCastlingRights = 0;
6339       /* !!?unconstrained back-rank shuffle */
6340       shuffleOpenings = 1;
6341       break;
6342     }
6343
6344     overrule = 0;
6345     if(appData.NrFiles >= 0) {
6346         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6347         gameInfo.boardWidth = appData.NrFiles;
6348     }
6349     if(appData.NrRanks >= 0) {
6350         gameInfo.boardHeight = appData.NrRanks;
6351     }
6352     if(appData.holdingsSize >= 0) {
6353         i = appData.holdingsSize;
6354         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6355         gameInfo.holdingsSize = i;
6356     }
6357     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6358     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6359         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6360
6361     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6362     if(pawnRow < 1) pawnRow = 1;
6363     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6364        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6365     if(gameInfo.variant == VariantChu) pawnRow = 3;
6366
6367     /* User pieceToChar list overrules defaults */
6368     if(appData.pieceToCharTable != NULL)
6369         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6370
6371     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6372
6373         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6374             s = (ChessSquare) 0; /* account holding counts in guard band */
6375         for( i=0; i<BOARD_HEIGHT; i++ )
6376             initialPosition[i][j] = s;
6377
6378         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6379         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6380         initialPosition[pawnRow][j] = WhitePawn;
6381         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6382         if(gameInfo.variant == VariantXiangqi) {
6383             if(j&1) {
6384                 initialPosition[pawnRow][j] =
6385                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6386                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6387                    initialPosition[2][j] = WhiteCannon;
6388                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6389                 }
6390             }
6391         }
6392         if(gameInfo.variant == VariantChu) {
6393              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6394                initialPosition[pawnRow+1][j] = WhiteCobra,
6395                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6396              for(i=1; i<pieceRows; i++) {
6397                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6398                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6399              }
6400         }
6401         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6402             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6403                initialPosition[0][j] = WhiteRook;
6404                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6405             }
6406         }
6407         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6408     }
6409     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6410     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6411
6412             j=BOARD_LEFT+1;
6413             initialPosition[1][j] = WhiteBishop;
6414             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6415             j=BOARD_RGHT-2;
6416             initialPosition[1][j] = WhiteRook;
6417             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6418     }
6419
6420     if( nrCastlingRights == -1) {
6421         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6422         /*       This sets default castling rights from none to normal corners   */
6423         /* Variants with other castling rights must set them themselves above    */
6424         nrCastlingRights = 6;
6425
6426         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6427         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6428         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6429         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6430         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6431         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6432      }
6433
6434      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6435      if(gameInfo.variant == VariantGreat) { // promotion commoners
6436         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6437         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6438         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6439         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6440      }
6441      if( gameInfo.variant == VariantSChess ) {
6442       initialPosition[1][0] = BlackMarshall;
6443       initialPosition[2][0] = BlackAngel;
6444       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6445       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6446       initialPosition[1][1] = initialPosition[2][1] =
6447       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6448      }
6449   if (appData.debugMode) {
6450     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6451   }
6452     if(shuffleOpenings) {
6453         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6454         startedFromSetupPosition = TRUE;
6455     }
6456     if(startedFromPositionFile) {
6457       /* [HGM] loadPos: use PositionFile for every new game */
6458       CopyBoard(initialPosition, filePosition);
6459       for(i=0; i<nrCastlingRights; i++)
6460           initialRights[i] = filePosition[CASTLING][i];
6461       startedFromSetupPosition = TRUE;
6462     }
6463     if(*appData.men) LoadPieceDesc(appData.men);
6464
6465     CopyBoard(boards[0], initialPosition);
6466
6467     if(oldx != gameInfo.boardWidth ||
6468        oldy != gameInfo.boardHeight ||
6469        oldv != gameInfo.variant ||
6470        oldh != gameInfo.holdingsWidth
6471                                          )
6472             InitDrawingSizes(-2 ,0);
6473
6474     oldv = gameInfo.variant;
6475     if (redraw)
6476       DrawPosition(TRUE, boards[currentMove]);
6477 }
6478
6479 void
6480 SendBoard (ChessProgramState *cps, int moveNum)
6481 {
6482     char message[MSG_SIZ];
6483
6484     if (cps->useSetboard) {
6485       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6486       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6487       SendToProgram(message, cps);
6488       free(fen);
6489
6490     } else {
6491       ChessSquare *bp;
6492       int i, j, left=0, right=BOARD_WIDTH;
6493       /* Kludge to set black to move, avoiding the troublesome and now
6494        * deprecated "black" command.
6495        */
6496       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6497         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6498
6499       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6500
6501       SendToProgram("edit\n", cps);
6502       SendToProgram("#\n", cps);
6503       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6504         bp = &boards[moveNum][i][left];
6505         for (j = left; j < right; j++, bp++) {
6506           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6507           if ((int) *bp < (int) BlackPawn) {
6508             if(j == BOARD_RGHT+1)
6509                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6510             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6511             if(message[0] == '+' || message[0] == '~') {
6512               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6513                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6514                         AAA + j, ONE + i - '0');
6515             }
6516             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6517                 message[1] = BOARD_RGHT   - 1 - j + '1';
6518                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6519             }
6520             SendToProgram(message, cps);
6521           }
6522         }
6523       }
6524
6525       SendToProgram("c\n", cps);
6526       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6527         bp = &boards[moveNum][i][left];
6528         for (j = left; j < right; j++, bp++) {
6529           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6530           if (((int) *bp != (int) EmptySquare)
6531               && ((int) *bp >= (int) BlackPawn)) {
6532             if(j == BOARD_LEFT-2)
6533                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6534             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6535                     AAA + j, ONE + i - '0');
6536             if(message[0] == '+' || message[0] == '~') {
6537               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6538                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6539                         AAA + j, ONE + i - '0');
6540             }
6541             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6542                 message[1] = BOARD_RGHT   - 1 - j + '1';
6543                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6544             }
6545             SendToProgram(message, cps);
6546           }
6547         }
6548       }
6549
6550       SendToProgram(".\n", cps);
6551     }
6552     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6553 }
6554
6555 char exclusionHeader[MSG_SIZ];
6556 int exCnt, excludePtr;
6557 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6558 static Exclusion excluTab[200];
6559 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6560
6561 static void
6562 WriteMap (int s)
6563 {
6564     int j;
6565     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6566     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6567 }
6568
6569 static void
6570 ClearMap ()
6571 {
6572     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6573     excludePtr = 24; exCnt = 0;
6574     WriteMap(0);
6575 }
6576
6577 static void
6578 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6579 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6580     char buf[2*MOVE_LEN], *p;
6581     Exclusion *e = excluTab;
6582     int i;
6583     for(i=0; i<exCnt; i++)
6584         if(e[i].ff == fromX && e[i].fr == fromY &&
6585            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6586     if(i == exCnt) { // was not in exclude list; add it
6587         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6588         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6589             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6590             return; // abort
6591         }
6592         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6593         excludePtr++; e[i].mark = excludePtr++;
6594         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6595         exCnt++;
6596     }
6597     exclusionHeader[e[i].mark] = state;
6598 }
6599
6600 static int
6601 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6602 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6603     char buf[MSG_SIZ];
6604     int j, k;
6605     ChessMove moveType;
6606     if((signed char)promoChar == -1) { // kludge to indicate best move
6607         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6608             return 1; // if unparsable, abort
6609     }
6610     // update exclusion map (resolving toggle by consulting existing state)
6611     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6612     j = k%8; k >>= 3;
6613     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6614     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6615          excludeMap[k] |=   1<<j;
6616     else excludeMap[k] &= ~(1<<j);
6617     // update header
6618     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6619     // inform engine
6620     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6621     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6622     SendToBoth(buf);
6623     return (state == '+');
6624 }
6625
6626 static void
6627 ExcludeClick (int index)
6628 {
6629     int i, j;
6630     Exclusion *e = excluTab;
6631     if(index < 25) { // none, best or tail clicked
6632         if(index < 13) { // none: include all
6633             WriteMap(0); // clear map
6634             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6635             SendToBoth("include all\n"); // and inform engine
6636         } else if(index > 18) { // tail
6637             if(exclusionHeader[19] == '-') { // tail was excluded
6638                 SendToBoth("include all\n");
6639                 WriteMap(0); // clear map completely
6640                 // now re-exclude selected moves
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, '-');
6643             } else { // tail was included or in mixed state
6644                 SendToBoth("exclude all\n");
6645                 WriteMap(0xFF); // fill map completely
6646                 // now re-include selected moves
6647                 j = 0; // count them
6648                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6649                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6650                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6651             }
6652         } else { // best
6653             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6654         }
6655     } else {
6656         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6657             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6658             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6659             break;
6660         }
6661     }
6662 }
6663
6664 ChessSquare
6665 DefaultPromoChoice (int white)
6666 {
6667     ChessSquare result;
6668     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6669        gameInfo.variant == VariantMakruk)
6670         result = WhiteFerz; // no choice
6671     else if(gameInfo.variant == VariantASEAN)
6672         result = WhiteRook; // no choice
6673     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6674         result= WhiteKing; // in Suicide Q is the last thing we want
6675     else if(gameInfo.variant == VariantSpartan)
6676         result = white ? WhiteQueen : WhiteAngel;
6677     else result = WhiteQueen;
6678     if(!white) result = WHITE_TO_BLACK result;
6679     return result;
6680 }
6681
6682 static int autoQueen; // [HGM] oneclick
6683
6684 int
6685 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6686 {
6687     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6688     /* [HGM] add Shogi promotions */
6689     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6690     ChessSquare piece, partner;
6691     ChessMove moveType;
6692     Boolean premove;
6693
6694     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6695     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6696
6697     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6698       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6699         return FALSE;
6700
6701     piece = boards[currentMove][fromY][fromX];
6702     if(gameInfo.variant == VariantChu) {
6703         promotionZoneSize = BOARD_HEIGHT/3;
6704         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6705     } else if(gameInfo.variant == VariantShogi) {
6706         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6707         highestPromotingPiece = (int)WhiteAlfil;
6708     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6709         promotionZoneSize = 3;
6710     }
6711
6712     // Treat Lance as Pawn when it is not representing Amazon or Lance
6713     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6714         if(piece == WhiteLance) piece = WhitePawn; else
6715         if(piece == BlackLance) piece = BlackPawn;
6716     }
6717
6718     // next weed out all moves that do not touch the promotion zone at all
6719     if((int)piece >= BlackPawn) {
6720         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6721              return FALSE;
6722         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6723         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6724     } else {
6725         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6726            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6727         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6728              return FALSE;
6729     }
6730
6731     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6732
6733     // weed out mandatory Shogi promotions
6734     if(gameInfo.variant == VariantShogi) {
6735         if(piece >= BlackPawn) {
6736             if(toY == 0 && piece == BlackPawn ||
6737                toY == 0 && piece == BlackQueen ||
6738                toY <= 1 && piece == BlackKnight) {
6739                 *promoChoice = '+';
6740                 return FALSE;
6741             }
6742         } else {
6743             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6744                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6745                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6746                 *promoChoice = '+';
6747                 return FALSE;
6748             }
6749         }
6750     }
6751
6752     // weed out obviously illegal Pawn moves
6753     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6754         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6755         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6756         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6757         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6758         // note we are not allowed to test for valid (non-)capture, due to premove
6759     }
6760
6761     // we either have a choice what to promote to, or (in Shogi) whether to promote
6762     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6763        gameInfo.variant == VariantMakruk) {
6764         ChessSquare p=BlackFerz;  // no choice
6765         while(p < EmptySquare) {  //but make sure we use piece that exists
6766             *promoChoice = PieceToChar(p++);
6767             if(*promoChoice != '.') break;
6768         }
6769         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6770     }
6771     // no sense asking what we must promote to if it is going to explode...
6772     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6773         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6774         return FALSE;
6775     }
6776     // give caller the default choice even if we will not make it
6777     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6778     partner = piece; // pieces can promote if the pieceToCharTable says so
6779     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6780     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6781     if(        sweepSelect && gameInfo.variant != VariantGreat
6782                            && gameInfo.variant != VariantGrand
6783                            && gameInfo.variant != VariantSuper) return FALSE;
6784     if(autoQueen) return FALSE; // predetermined
6785
6786     // suppress promotion popup on illegal moves that are not premoves
6787     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6788               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6789     if(appData.testLegality && !premove) {
6790         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6791                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6792         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6793         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6794             return FALSE;
6795     }
6796
6797     return TRUE;
6798 }
6799
6800 int
6801 InPalace (int row, int column)
6802 {   /* [HGM] for Xiangqi */
6803     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6804          column < (BOARD_WIDTH + 4)/2 &&
6805          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6806     return FALSE;
6807 }
6808
6809 int
6810 PieceForSquare (int x, int y)
6811 {
6812   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6813      return -1;
6814   else
6815      return boards[currentMove][y][x];
6816 }
6817
6818 int
6819 OKToStartUserMove (int x, int y)
6820 {
6821     ChessSquare from_piece;
6822     int white_piece;
6823
6824     if (matchMode) return FALSE;
6825     if (gameMode == EditPosition) return TRUE;
6826
6827     if (x >= 0 && y >= 0)
6828       from_piece = boards[currentMove][y][x];
6829     else
6830       from_piece = EmptySquare;
6831
6832     if (from_piece == EmptySquare) return FALSE;
6833
6834     white_piece = (int)from_piece >= (int)WhitePawn &&
6835       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6836
6837     switch (gameMode) {
6838       case AnalyzeFile:
6839       case TwoMachinesPlay:
6840       case EndOfGame:
6841         return FALSE;
6842
6843       case IcsObserving:
6844       case IcsIdle:
6845         return FALSE;
6846
6847       case MachinePlaysWhite:
6848       case IcsPlayingBlack:
6849         if (appData.zippyPlay) return FALSE;
6850         if (white_piece) {
6851             DisplayMoveError(_("You are playing Black"));
6852             return FALSE;
6853         }
6854         break;
6855
6856       case MachinePlaysBlack:
6857       case IcsPlayingWhite:
6858         if (appData.zippyPlay) return FALSE;
6859         if (!white_piece) {
6860             DisplayMoveError(_("You are playing White"));
6861             return FALSE;
6862         }
6863         break;
6864
6865       case PlayFromGameFile:
6866             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6867       case EditGame:
6868       case AnalyzeMode:
6869         if (!white_piece && WhiteOnMove(currentMove)) {
6870             DisplayMoveError(_("It is White's turn"));
6871             return FALSE;
6872         }
6873         if (white_piece && !WhiteOnMove(currentMove)) {
6874             DisplayMoveError(_("It is Black's turn"));
6875             return FALSE;
6876         }
6877         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6878             /* Editing correspondence game history */
6879             /* Could disallow this or prompt for confirmation */
6880             cmailOldMove = -1;
6881         }
6882         break;
6883
6884       case BeginningOfGame:
6885         if (appData.icsActive) return FALSE;
6886         if (!appData.noChessProgram) {
6887             if (!white_piece) {
6888                 DisplayMoveError(_("You are playing White"));
6889                 return FALSE;
6890             }
6891         }
6892         break;
6893
6894       case Training:
6895         if (!white_piece && WhiteOnMove(currentMove)) {
6896             DisplayMoveError(_("It is White's turn"));
6897             return FALSE;
6898         }
6899         if (white_piece && !WhiteOnMove(currentMove)) {
6900             DisplayMoveError(_("It is Black's turn"));
6901             return FALSE;
6902         }
6903         break;
6904
6905       default:
6906       case IcsExamining:
6907         break;
6908     }
6909     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6910         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6911         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6912         && gameMode != AnalyzeFile && gameMode != Training) {
6913         DisplayMoveError(_("Displayed position is not current"));
6914         return FALSE;
6915     }
6916     return TRUE;
6917 }
6918
6919 Boolean
6920 OnlyMove (int *x, int *y, Boolean captures)
6921 {
6922     DisambiguateClosure cl;
6923     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6924     switch(gameMode) {
6925       case MachinePlaysBlack:
6926       case IcsPlayingWhite:
6927       case BeginningOfGame:
6928         if(!WhiteOnMove(currentMove)) return FALSE;
6929         break;
6930       case MachinePlaysWhite:
6931       case IcsPlayingBlack:
6932         if(WhiteOnMove(currentMove)) return FALSE;
6933         break;
6934       case EditGame:
6935         break;
6936       default:
6937         return FALSE;
6938     }
6939     cl.pieceIn = EmptySquare;
6940     cl.rfIn = *y;
6941     cl.ffIn = *x;
6942     cl.rtIn = -1;
6943     cl.ftIn = -1;
6944     cl.promoCharIn = NULLCHAR;
6945     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6946     if( cl.kind == NormalMove ||
6947         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6948         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6949         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6950       fromX = cl.ff;
6951       fromY = cl.rf;
6952       *x = cl.ft;
6953       *y = cl.rt;
6954       return TRUE;
6955     }
6956     if(cl.kind != ImpossibleMove) return FALSE;
6957     cl.pieceIn = EmptySquare;
6958     cl.rfIn = -1;
6959     cl.ffIn = -1;
6960     cl.rtIn = *y;
6961     cl.ftIn = *x;
6962     cl.promoCharIn = NULLCHAR;
6963     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6964     if( cl.kind == NormalMove ||
6965         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6966         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6967         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6968       fromX = cl.ff;
6969       fromY = cl.rf;
6970       *x = cl.ft;
6971       *y = cl.rt;
6972       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6973       return TRUE;
6974     }
6975     return FALSE;
6976 }
6977
6978 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6979 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6980 int lastLoadGameUseList = FALSE;
6981 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6982 ChessMove lastLoadGameStart = EndOfFile;
6983 int doubleClick;
6984 Boolean addToBookFlag;
6985 static Board rightsBoard, nullBoard;
6986
6987 void
6988 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6989 {
6990     ChessMove moveType;
6991     ChessSquare pup;
6992     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6993
6994     /* Check if the user is playing in turn.  This is complicated because we
6995        let the user "pick up" a piece before it is his turn.  So the piece he
6996        tried to pick up may have been captured by the time he puts it down!
6997        Therefore we use the color the user is supposed to be playing in this
6998        test, not the color of the piece that is currently on the starting
6999        square---except in EditGame mode, where the user is playing both
7000        sides; fortunately there the capture race can't happen.  (It can
7001        now happen in IcsExamining mode, but that's just too bad.  The user
7002        will get a somewhat confusing message in that case.)
7003        */
7004
7005     switch (gameMode) {
7006       case AnalyzeFile:
7007       case TwoMachinesPlay:
7008       case EndOfGame:
7009       case IcsObserving:
7010       case IcsIdle:
7011         /* We switched into a game mode where moves are not accepted,
7012            perhaps while the mouse button was down. */
7013         return;
7014
7015       case MachinePlaysWhite:
7016         /* User is moving for Black */
7017         if (WhiteOnMove(currentMove)) {
7018             DisplayMoveError(_("It is White's turn"));
7019             return;
7020         }
7021         break;
7022
7023       case MachinePlaysBlack:
7024         /* User is moving for White */
7025         if (!WhiteOnMove(currentMove)) {
7026             DisplayMoveError(_("It is Black's turn"));
7027             return;
7028         }
7029         break;
7030
7031       case PlayFromGameFile:
7032             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7033       case EditGame:
7034       case IcsExamining:
7035       case BeginningOfGame:
7036       case AnalyzeMode:
7037       case Training:
7038         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7039         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7040             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7041             /* User is moving for Black */
7042             if (WhiteOnMove(currentMove)) {
7043                 DisplayMoveError(_("It is White's turn"));
7044                 return;
7045             }
7046         } else {
7047             /* User is moving for White */
7048             if (!WhiteOnMove(currentMove)) {
7049                 DisplayMoveError(_("It is Black's turn"));
7050                 return;
7051             }
7052         }
7053         break;
7054
7055       case IcsPlayingBlack:
7056         /* User is moving for Black */
7057         if (WhiteOnMove(currentMove)) {
7058             if (!appData.premove) {
7059                 DisplayMoveError(_("It is White's turn"));
7060             } else if (toX >= 0 && toY >= 0) {
7061                 premoveToX = toX;
7062                 premoveToY = toY;
7063                 premoveFromX = fromX;
7064                 premoveFromY = fromY;
7065                 premovePromoChar = promoChar;
7066                 gotPremove = 1;
7067                 if (appData.debugMode)
7068                     fprintf(debugFP, "Got premove: fromX %d,"
7069                             "fromY %d, toX %d, toY %d\n",
7070                             fromX, fromY, toX, toY);
7071             }
7072             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7073             return;
7074         }
7075         break;
7076
7077       case IcsPlayingWhite:
7078         /* User is moving for White */
7079         if (!WhiteOnMove(currentMove)) {
7080             if (!appData.premove) {
7081                 DisplayMoveError(_("It is Black's turn"));
7082             } else if (toX >= 0 && toY >= 0) {
7083                 premoveToX = toX;
7084                 premoveToY = toY;
7085                 premoveFromX = fromX;
7086                 premoveFromY = fromY;
7087                 premovePromoChar = promoChar;
7088                 gotPremove = 1;
7089                 if (appData.debugMode)
7090                     fprintf(debugFP, "Got premove: fromX %d,"
7091                             "fromY %d, toX %d, toY %d\n",
7092                             fromX, fromY, toX, toY);
7093             }
7094             DrawPosition(TRUE, boards[currentMove]);
7095             return;
7096         }
7097         break;
7098
7099       default:
7100         break;
7101
7102       case EditPosition:
7103         /* EditPosition, empty square, or different color piece;
7104            click-click move is possible */
7105         if (toX == -2 || toY == -2) {
7106             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7107             DrawPosition(FALSE, boards[currentMove]);
7108             return;
7109         } else if (toX >= 0 && toY >= 0) {
7110             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7111                 ChessSquare p = boards[0][rf][ff];
7112                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7113                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7114                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook) {
7115                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7116                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7117                     gatingPiece = p;
7118                 }
7119             }
7120             boards[0][toY][toX] = boards[0][fromY][fromX];
7121             rightsBoard[toY][toX] = 0;  // revoke rights on moving
7122             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7123                 if(boards[0][fromY][0] != EmptySquare) {
7124                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7125                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7126                 }
7127             } else
7128             if(fromX == BOARD_RGHT+1) {
7129                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7130                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7131                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7132                 }
7133             } else
7134             boards[0][fromY][fromX] = gatingPiece;
7135             ClearHighlights();
7136             DrawPosition(FALSE, boards[currentMove]);
7137             return;
7138         }
7139         return;
7140     }
7141
7142     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7143     pup = boards[currentMove][toY][toX];
7144
7145     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7146     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7147          if( pup != EmptySquare ) return;
7148          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7149            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7150                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7151            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7152            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7153            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7154            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7155          fromY = DROP_RANK;
7156     }
7157
7158     /* [HGM] always test for legality, to get promotion info */
7159     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7160                                          fromY, fromX, toY, toX, promoChar);
7161
7162     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7163
7164     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7165
7166     /* [HGM] but possibly ignore an IllegalMove result */
7167     if (appData.testLegality) {
7168         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7169             DisplayMoveError(_("Illegal move"));
7170             return;
7171         }
7172     }
7173
7174     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7175         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7176              ClearPremoveHighlights(); // was included
7177         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7178         DrawPosition(FALSE, NULL);
7179         return;
7180     }
7181
7182     if(addToBookFlag) { // adding moves to book
7183         char buf[MSG_SIZ], move[MSG_SIZ];
7184         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7185         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7186                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7187         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7188         AddBookMove(buf);
7189         addToBookFlag = FALSE;
7190         ClearHighlights();
7191         return;
7192     }
7193
7194     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7195 }
7196
7197 /* Common tail of UserMoveEvent and DropMenuEvent */
7198 int
7199 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7200 {
7201     char *bookHit = 0;
7202
7203     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7204         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7205         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7206         if(WhiteOnMove(currentMove)) {
7207             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7208         } else {
7209             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7210         }
7211     }
7212
7213     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7214        move type in caller when we know the move is a legal promotion */
7215     if(moveType == NormalMove && promoChar)
7216         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7217
7218     /* [HGM] <popupFix> The following if has been moved here from
7219        UserMoveEvent(). Because it seemed to belong here (why not allow
7220        piece drops in training games?), and because it can only be
7221        performed after it is known to what we promote. */
7222     if (gameMode == Training) {
7223       /* compare the move played on the board to the next move in the
7224        * game. If they match, display the move and the opponent's response.
7225        * If they don't match, display an error message.
7226        */
7227       int saveAnimate;
7228       Board testBoard;
7229       CopyBoard(testBoard, boards[currentMove]);
7230       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7231
7232       if (CompareBoards(testBoard, boards[currentMove+1])) {
7233         ForwardInner(currentMove+1);
7234
7235         /* Autoplay the opponent's response.
7236          * if appData.animate was TRUE when Training mode was entered,
7237          * the response will be animated.
7238          */
7239         saveAnimate = appData.animate;
7240         appData.animate = animateTraining;
7241         ForwardInner(currentMove+1);
7242         appData.animate = saveAnimate;
7243
7244         /* check for the end of the game */
7245         if (currentMove >= forwardMostMove) {
7246           gameMode = PlayFromGameFile;
7247           ModeHighlight();
7248           SetTrainingModeOff();
7249           DisplayInformation(_("End of game"));
7250         }
7251       } else {
7252         DisplayError(_("Incorrect move"), 0);
7253       }
7254       return 1;
7255     }
7256
7257   /* Ok, now we know that the move is good, so we can kill
7258      the previous line in Analysis Mode */
7259   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7260                                 && currentMove < forwardMostMove) {
7261     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7262     else forwardMostMove = currentMove;
7263   }
7264
7265   ClearMap();
7266
7267   /* If we need the chess program but it's dead, restart it */
7268   ResurrectChessProgram();
7269
7270   /* A user move restarts a paused game*/
7271   if (pausing)
7272     PauseEvent();
7273
7274   thinkOutput[0] = NULLCHAR;
7275
7276   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7277
7278   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7279     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7280     return 1;
7281   }
7282
7283   if (gameMode == BeginningOfGame) {
7284     if (appData.noChessProgram) {
7285       gameMode = EditGame;
7286       SetGameInfo();
7287     } else {
7288       char buf[MSG_SIZ];
7289       gameMode = MachinePlaysBlack;
7290       StartClocks();
7291       SetGameInfo();
7292       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7293       DisplayTitle(buf);
7294       if (first.sendName) {
7295         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7296         SendToProgram(buf, &first);
7297       }
7298       StartClocks();
7299     }
7300     ModeHighlight();
7301   }
7302
7303   /* Relay move to ICS or chess engine */
7304   if (appData.icsActive) {
7305     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7306         gameMode == IcsExamining) {
7307       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7308         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7309         SendToICS("draw ");
7310         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7311       }
7312       // also send plain move, in case ICS does not understand atomic claims
7313       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7314       ics_user_moved = 1;
7315     }
7316   } else {
7317     if (first.sendTime && (gameMode == BeginningOfGame ||
7318                            gameMode == MachinePlaysWhite ||
7319                            gameMode == MachinePlaysBlack)) {
7320       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7321     }
7322     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7323          // [HGM] book: if program might be playing, let it use book
7324         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7325         first.maybeThinking = TRUE;
7326     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7327         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7328         SendBoard(&first, currentMove+1);
7329         if(second.analyzing) {
7330             if(!second.useSetboard) SendToProgram("undo\n", &second);
7331             SendBoard(&second, currentMove+1);
7332         }
7333     } else {
7334         SendMoveToProgram(forwardMostMove-1, &first);
7335         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7336     }
7337     if (currentMove == cmailOldMove + 1) {
7338       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7339     }
7340   }
7341
7342   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7343
7344   switch (gameMode) {
7345   case EditGame:
7346     if(appData.testLegality)
7347     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7348     case MT_NONE:
7349     case MT_CHECK:
7350       break;
7351     case MT_CHECKMATE:
7352     case MT_STAINMATE:
7353       if (WhiteOnMove(currentMove)) {
7354         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7355       } else {
7356         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7357       }
7358       break;
7359     case MT_STALEMATE:
7360       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7361       break;
7362     }
7363     break;
7364
7365   case MachinePlaysBlack:
7366   case MachinePlaysWhite:
7367     /* disable certain menu options while machine is thinking */
7368     SetMachineThinkingEnables();
7369     break;
7370
7371   default:
7372     break;
7373   }
7374
7375   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7376   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7377
7378   if(bookHit) { // [HGM] book: simulate book reply
7379         static char bookMove[MSG_SIZ]; // a bit generous?
7380
7381         programStats.nodes = programStats.depth = programStats.time =
7382         programStats.score = programStats.got_only_move = 0;
7383         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7384
7385         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7386         strcat(bookMove, bookHit);
7387         HandleMachineMove(bookMove, &first);
7388   }
7389   return 1;
7390 }
7391
7392 void
7393 MarkByFEN(char *fen)
7394 {
7395         int r, f;
7396         if(!appData.markers || !appData.highlightDragging) return;
7397         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7398         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7399         while(*fen) {
7400             int s = 0;
7401             marker[r][f] = 0;
7402             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7403             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7404             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7405             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7406             if(*fen == 'T') marker[r][f++] = 0; else
7407             if(*fen == 'Y') marker[r][f++] = 1; else
7408             if(*fen == 'G') marker[r][f++] = 3; else
7409             if(*fen == 'B') marker[r][f++] = 4; else
7410             if(*fen == 'C') marker[r][f++] = 5; else
7411             if(*fen == 'M') marker[r][f++] = 6; else
7412             if(*fen == 'W') marker[r][f++] = 7; else
7413             if(*fen == 'D') marker[r][f++] = 8; else
7414             if(*fen == 'R') marker[r][f++] = 2; else {
7415                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7416               f += s; fen -= s>0;
7417             }
7418             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7419             if(r < 0) break;
7420             fen++;
7421         }
7422         DrawPosition(TRUE, NULL);
7423 }
7424
7425 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7426
7427 void
7428 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7429 {
7430     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7431     Markers *m = (Markers *) closure;
7432     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7433                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7434         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7435                          || kind == WhiteCapturesEnPassant
7436                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7437     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7438 }
7439
7440 static int hoverSavedValid;
7441
7442 void
7443 MarkTargetSquares (int clear)
7444 {
7445   int x, y, sum=0;
7446   if(clear) { // no reason to ever suppress clearing
7447     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7448     hoverSavedValid = 0;
7449     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7450   } else {
7451     int capt = 0;
7452     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7453        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7454     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7455     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7456       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7457       if(capt)
7458       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7459     }
7460   }
7461   DrawPosition(FALSE, NULL);
7462 }
7463
7464 int
7465 Explode (Board board, int fromX, int fromY, int toX, int toY)
7466 {
7467     if(gameInfo.variant == VariantAtomic &&
7468        (board[toY][toX] != EmptySquare ||                     // capture?
7469         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7470                          board[fromY][fromX] == BlackPawn   )
7471       )) {
7472         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7473         return TRUE;
7474     }
7475     return FALSE;
7476 }
7477
7478 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7479
7480 int
7481 CanPromote (ChessSquare piece, int y)
7482 {
7483         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7484         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7485         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7486         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7487            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7488           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7489            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7490         return (piece == BlackPawn && y <= zone ||
7491                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7492                 piece == BlackLance && y <= zone ||
7493                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7494 }
7495
7496 void
7497 HoverEvent (int xPix, int yPix, int x, int y)
7498 {
7499         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7500         int r, f;
7501         if(!first.highlight) return;
7502         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7503         if(x == oldX && y == oldY) return; // only do something if we enter new square
7504         oldFromX = fromX; oldFromY = fromY;
7505         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7506           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7507             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7508           hoverSavedValid = 1;
7509         } else if(oldX != x || oldY != y) {
7510           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7511           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7512           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7513             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7514           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7515             char buf[MSG_SIZ];
7516             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7517             SendToProgram(buf, &first);
7518           }
7519           oldX = x; oldY = y;
7520 //        SetHighlights(fromX, fromY, x, y);
7521         }
7522 }
7523
7524 void ReportClick(char *action, int x, int y)
7525 {
7526         char buf[MSG_SIZ]; // Inform engine of what user does
7527         int r, f;
7528         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7529           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7530             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7531         if(!first.highlight || gameMode == EditPosition) return;
7532         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7533         SendToProgram(buf, &first);
7534 }
7535
7536 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7537
7538 void
7539 LeftClick (ClickType clickType, int xPix, int yPix)
7540 {
7541     int x, y;
7542     Boolean saveAnimate;
7543     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7544     char promoChoice = NULLCHAR;
7545     ChessSquare piece;
7546     static TimeMark lastClickTime, prevClickTime;
7547
7548     if(flashing) return;
7549
7550     x = EventToSquare(xPix, BOARD_WIDTH);
7551     y = EventToSquare(yPix, BOARD_HEIGHT);
7552     if (!flipView && y >= 0) {
7553         y = BOARD_HEIGHT - 1 - y;
7554     }
7555     if (flipView && x >= 0) {
7556         x = BOARD_WIDTH - 1 - x;
7557     }
7558
7559     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7560         static int dummy;
7561         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7562         right = TRUE;
7563         return;
7564     }
7565
7566     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7567
7568     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7569
7570     if (clickType == Press) ErrorPopDown();
7571     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7572
7573     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7574         defaultPromoChoice = promoSweep;
7575         promoSweep = EmptySquare;   // terminate sweep
7576         promoDefaultAltered = TRUE;
7577         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7578     }
7579
7580     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7581         if(clickType == Release) return; // ignore upclick of click-click destination
7582         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7583         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7584         if(gameInfo.holdingsWidth &&
7585                 (WhiteOnMove(currentMove)
7586                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7587                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7588             // click in right holdings, for determining promotion piece
7589             ChessSquare p = boards[currentMove][y][x];
7590             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7591             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7592             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7593                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7594                 fromX = fromY = -1;
7595                 return;
7596             }
7597         }
7598         DrawPosition(FALSE, boards[currentMove]);
7599         return;
7600     }
7601
7602     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7603     if(clickType == Press
7604             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7605               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7606               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7607         return;
7608
7609     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7610         // could be static click on premove from-square: abort premove
7611         gotPremove = 0;
7612         ClearPremoveHighlights();
7613     }
7614
7615     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7616         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7617
7618     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7619         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7620                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7621         defaultPromoChoice = DefaultPromoChoice(side);
7622     }
7623
7624     autoQueen = appData.alwaysPromoteToQueen;
7625
7626     if (fromX == -1) {
7627       int originalY = y;
7628       gatingPiece = EmptySquare;
7629       if (clickType != Press) {
7630         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7631             DragPieceEnd(xPix, yPix); dragging = 0;
7632             DrawPosition(FALSE, NULL);
7633         }
7634         return;
7635       }
7636       doubleClick = FALSE;
7637       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7638         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7639       }
7640       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7641       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7642          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7643          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7644             /* First square */
7645             if (OKToStartUserMove(fromX, fromY)) {
7646                 second = 0;
7647                 ReportClick("lift", x, y);
7648                 MarkTargetSquares(0);
7649                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7650                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7651                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7652                     promoSweep = defaultPromoChoice;
7653                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7654                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7655                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7656                 }
7657                 if (appData.highlightDragging) {
7658                     SetHighlights(fromX, fromY, -1, -1);
7659                 } else {
7660                     ClearHighlights();
7661                 }
7662             } else fromX = fromY = -1;
7663             return;
7664         }
7665     }
7666
7667     /* fromX != -1 */
7668     if (clickType == Press && gameMode != EditPosition) {
7669         ChessSquare fromP;
7670         ChessSquare toP;
7671         int frc;
7672
7673         // ignore off-board to clicks
7674         if(y < 0 || x < 0) return;
7675
7676         /* Check if clicking again on the same color piece */
7677         fromP = boards[currentMove][fromY][fromX];
7678         toP = boards[currentMove][y][x];
7679         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7680         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7681             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7682            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7683              WhitePawn <= toP && toP <= WhiteKing &&
7684              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7685              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7686             (BlackPawn <= fromP && fromP <= BlackKing &&
7687              BlackPawn <= toP && toP <= BlackKing &&
7688              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7689              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7690             /* Clicked again on same color piece -- changed his mind */
7691             second = (x == fromX && y == fromY);
7692             killX = killY = kill2X = kill2Y = -1;
7693             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7694                 second = FALSE; // first double-click rather than scond click
7695                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7696             }
7697             promoDefaultAltered = FALSE;
7698            if(!second) MarkTargetSquares(1);
7699            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7700             if (appData.highlightDragging) {
7701                 SetHighlights(x, y, -1, -1);
7702             } else {
7703                 ClearHighlights();
7704             }
7705             if (OKToStartUserMove(x, y)) {
7706                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7707                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7708                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7709                  gatingPiece = boards[currentMove][fromY][fromX];
7710                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7711                 fromX = x;
7712                 fromY = y; dragging = 1;
7713                 if(!second) ReportClick("lift", x, y);
7714                 MarkTargetSquares(0);
7715                 DragPieceBegin(xPix, yPix, FALSE);
7716                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7717                     promoSweep = defaultPromoChoice;
7718                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7719                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7720                 }
7721             }
7722            }
7723            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7724            second = FALSE;
7725         }
7726         // ignore clicks on holdings
7727         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7728     }
7729
7730     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7731         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7732         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7733         return;
7734     }
7735
7736     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7737         DragPieceEnd(xPix, yPix); dragging = 0;
7738         if(clearFlag) {
7739             // a deferred attempt to click-click move an empty square on top of a piece
7740             boards[currentMove][y][x] = EmptySquare;
7741             ClearHighlights();
7742             DrawPosition(FALSE, boards[currentMove]);
7743             fromX = fromY = -1; clearFlag = 0;
7744             return;
7745         }
7746         if (appData.animateDragging) {
7747             /* Undo animation damage if any */
7748             DrawPosition(FALSE, NULL);
7749         }
7750         if (second) {
7751             /* Second up/down in same square; just abort move */
7752             second = 0;
7753             fromX = fromY = -1;
7754             gatingPiece = EmptySquare;
7755             ClearHighlights();
7756             gotPremove = 0;
7757             ClearPremoveHighlights();
7758             MarkTargetSquares(-1);
7759             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7760         } else {
7761             /* First upclick in same square; start click-click mode */
7762             SetHighlights(x, y, -1, -1);
7763         }
7764         return;
7765     }
7766
7767     clearFlag = 0;
7768
7769     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7770        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7771         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7772         DisplayMessage(_("only marked squares are legal"),"");
7773         DrawPosition(TRUE, NULL);
7774         return; // ignore to-click
7775     }
7776
7777     /* we now have a different from- and (possibly off-board) to-square */
7778     /* Completed move */
7779     if(!sweepSelecting) {
7780         toX = x;
7781         toY = y;
7782     }
7783
7784     piece = boards[currentMove][fromY][fromX];
7785
7786     saveAnimate = appData.animate;
7787     if (clickType == Press) {
7788         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7789         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7790             // must be Edit Position mode with empty-square selected
7791             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7792             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7793             return;
7794         }
7795         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7796             return;
7797         }
7798         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7799             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7800         } else
7801         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7802         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7803           if(appData.sweepSelect) {
7804             promoSweep = defaultPromoChoice;
7805             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7806             selectFlag = 0; lastX = xPix; lastY = yPix;
7807             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7808             saveFlash = appData.flashCount; appData.flashCount = 0;
7809             Sweep(0); // Pawn that is going to promote: preview promotion piece
7810             sweepSelecting = 1;
7811             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7812             MarkTargetSquares(1);
7813           }
7814           return; // promo popup appears on up-click
7815         }
7816         /* Finish clickclick move */
7817         if (appData.animate || appData.highlightLastMove) {
7818             SetHighlights(fromX, fromY, toX, toY);
7819         } else {
7820             ClearHighlights();
7821         }
7822         MarkTargetSquares(1);
7823     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7824         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7825         *promoRestrict = 0; appData.flashCount = saveFlash;
7826         if (appData.animate || appData.highlightLastMove) {
7827             SetHighlights(fromX, fromY, toX, toY);
7828         } else {
7829             ClearHighlights();
7830         }
7831         MarkTargetSquares(1);
7832     } else {
7833 #if 0
7834 // [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
7835         /* Finish drag move */
7836         if (appData.highlightLastMove) {
7837             SetHighlights(fromX, fromY, toX, toY);
7838         } else {
7839             ClearHighlights();
7840         }
7841 #endif
7842         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7843           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7844         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7845         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7846           dragging *= 2;            // flag button-less dragging if we are dragging
7847           MarkTargetSquares(1);
7848           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7849           else {
7850             kill2X = killX; kill2Y = killY;
7851             killX = x; killY = y;     // remember this square as intermediate
7852             ReportClick("put", x, y); // and inform engine
7853             ReportClick("lift", x, y);
7854             MarkTargetSquares(0);
7855             return;
7856           }
7857         }
7858         DragPieceEnd(xPix, yPix); dragging = 0;
7859         /* Don't animate move and drag both */
7860         appData.animate = FALSE;
7861         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7862     }
7863
7864     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7865     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7866         ChessSquare piece = boards[currentMove][fromY][fromX];
7867         if(gameMode == EditPosition && piece != EmptySquare &&
7868            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7869             int n;
7870
7871             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7872                 n = PieceToNumber(piece - (int)BlackPawn);
7873                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7874                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7875                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7876             } else
7877             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7878                 n = PieceToNumber(piece);
7879                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7880                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7881                 boards[currentMove][n][BOARD_WIDTH-2]++;
7882             }
7883             boards[currentMove][fromY][fromX] = EmptySquare;
7884         }
7885         ClearHighlights();
7886         fromX = fromY = -1;
7887         MarkTargetSquares(1);
7888         DrawPosition(TRUE, boards[currentMove]);
7889         return;
7890     }
7891
7892     // off-board moves should not be highlighted
7893     if(x < 0 || y < 0) ClearHighlights();
7894     else ReportClick("put", x, y);
7895
7896     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7897
7898     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7899
7900     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7901         SetHighlights(fromX, fromY, toX, toY);
7902         MarkTargetSquares(1);
7903         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7904             // [HGM] super: promotion to captured piece selected from holdings
7905             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7906             promotionChoice = TRUE;
7907             // kludge follows to temporarily execute move on display, without promoting yet
7908             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7909             boards[currentMove][toY][toX] = p;
7910             DrawPosition(FALSE, boards[currentMove]);
7911             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7912             boards[currentMove][toY][toX] = q;
7913             DisplayMessage("Click in holdings to choose piece", "");
7914             return;
7915         }
7916         PromotionPopUp(promoChoice);
7917     } else {
7918         int oldMove = currentMove;
7919         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7920         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7921         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7922         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7923         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7924            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7925             DrawPosition(TRUE, boards[currentMove]);
7926         fromX = fromY = -1;
7927         flashing = 0;
7928     }
7929     appData.animate = saveAnimate;
7930     if (appData.animate || appData.animateDragging) {
7931         /* Undo animation damage if needed */
7932 //      DrawPosition(FALSE, NULL);
7933     }
7934 }
7935
7936 int
7937 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7938 {   // front-end-free part taken out of PieceMenuPopup
7939     int whichMenu; int xSqr, ySqr;
7940
7941     if(seekGraphUp) { // [HGM] seekgraph
7942         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7943         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7944         return -2;
7945     }
7946
7947     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7948          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7949         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7950         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7951         if(action == Press)   {
7952             originalFlip = flipView;
7953             flipView = !flipView; // temporarily flip board to see game from partners perspective
7954             DrawPosition(TRUE, partnerBoard);
7955             DisplayMessage(partnerStatus, "");
7956             partnerUp = TRUE;
7957         } else if(action == Release) {
7958             flipView = originalFlip;
7959             DrawPosition(TRUE, boards[currentMove]);
7960             partnerUp = FALSE;
7961         }
7962         return -2;
7963     }
7964
7965     xSqr = EventToSquare(x, BOARD_WIDTH);
7966     ySqr = EventToSquare(y, BOARD_HEIGHT);
7967     if (action == Release) {
7968         if(pieceSweep != EmptySquare) {
7969             EditPositionMenuEvent(pieceSweep, toX, toY);
7970             pieceSweep = EmptySquare;
7971         } else UnLoadPV(); // [HGM] pv
7972     }
7973     if (action != Press) return -2; // return code to be ignored
7974     switch (gameMode) {
7975       case IcsExamining:
7976         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7977       case EditPosition:
7978         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7979         if (xSqr < 0 || ySqr < 0) return -1;
7980         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7981         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7982         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7983         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7984         NextPiece(0);
7985         return 2; // grab
7986       case IcsObserving:
7987         if(!appData.icsEngineAnalyze) return -1;
7988       case IcsPlayingWhite:
7989       case IcsPlayingBlack:
7990         if(!appData.zippyPlay) goto noZip;
7991       case AnalyzeMode:
7992       case AnalyzeFile:
7993       case MachinePlaysWhite:
7994       case MachinePlaysBlack:
7995       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7996         if (!appData.dropMenu) {
7997           LoadPV(x, y);
7998           return 2; // flag front-end to grab mouse events
7999         }
8000         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8001            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8002       case EditGame:
8003       noZip:
8004         if (xSqr < 0 || ySqr < 0) return -1;
8005         if (!appData.dropMenu || appData.testLegality &&
8006             gameInfo.variant != VariantBughouse &&
8007             gameInfo.variant != VariantCrazyhouse) return -1;
8008         whichMenu = 1; // drop menu
8009         break;
8010       default:
8011         return -1;
8012     }
8013
8014     if (((*fromX = xSqr) < 0) ||
8015         ((*fromY = ySqr) < 0)) {
8016         *fromX = *fromY = -1;
8017         return -1;
8018     }
8019     if (flipView)
8020       *fromX = BOARD_WIDTH - 1 - *fromX;
8021     else
8022       *fromY = BOARD_HEIGHT - 1 - *fromY;
8023
8024     return whichMenu;
8025 }
8026
8027 void
8028 Wheel (int dir, int x, int y)
8029 {
8030     if(gameMode == EditPosition) {
8031         int xSqr = EventToSquare(x, BOARD_WIDTH);
8032         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8033         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8034         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8035         do {
8036             boards[currentMove][ySqr][xSqr] += dir;
8037             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8038             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8039         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8040         DrawPosition(FALSE, boards[currentMove]);
8041     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8042 }
8043
8044 void
8045 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8046 {
8047 //    char * hint = lastHint;
8048     FrontEndProgramStats stats;
8049
8050     stats.which = cps == &first ? 0 : 1;
8051     stats.depth = cpstats->depth;
8052     stats.nodes = cpstats->nodes;
8053     stats.score = cpstats->score;
8054     stats.time = cpstats->time;
8055     stats.pv = cpstats->movelist;
8056     stats.hint = lastHint;
8057     stats.an_move_index = 0;
8058     stats.an_move_count = 0;
8059
8060     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8061         stats.hint = cpstats->move_name;
8062         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8063         stats.an_move_count = cpstats->nr_moves;
8064     }
8065
8066     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
8067
8068     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8069         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8070
8071     SetProgramStats( &stats );
8072 }
8073
8074 void
8075 ClearEngineOutputPane (int which)
8076 {
8077     static FrontEndProgramStats dummyStats;
8078     dummyStats.which = which;
8079     dummyStats.pv = "#";
8080     SetProgramStats( &dummyStats );
8081 }
8082
8083 #define MAXPLAYERS 500
8084
8085 char *
8086 TourneyStandings (int display)
8087 {
8088     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8089     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8090     char result, *p, *names[MAXPLAYERS];
8091
8092     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8093         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8094     names[0] = p = strdup(appData.participants);
8095     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8096
8097     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8098
8099     while(result = appData.results[nr]) {
8100         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8101         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8102         wScore = bScore = 0;
8103         switch(result) {
8104           case '+': wScore = 2; break;
8105           case '-': bScore = 2; break;
8106           case '=': wScore = bScore = 1; break;
8107           case ' ':
8108           case '*': return strdup("busy"); // tourney not finished
8109         }
8110         score[w] += wScore;
8111         score[b] += bScore;
8112         games[w]++;
8113         games[b]++;
8114         nr++;
8115     }
8116     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8117     for(w=0; w<nPlayers; w++) {
8118         bScore = -1;
8119         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8120         ranking[w] = b; points[w] = bScore; score[b] = -2;
8121     }
8122     p = malloc(nPlayers*34+1);
8123     for(w=0; w<nPlayers && w<display; w++)
8124         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8125     free(names[0]);
8126     return p;
8127 }
8128
8129 void
8130 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8131 {       // count all piece types
8132         int p, f, r;
8133         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8134         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8135         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8136                 p = board[r][f];
8137                 pCnt[p]++;
8138                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8139                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8140                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8141                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8142                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8143                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8144         }
8145 }
8146
8147 int
8148 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8149 {
8150         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8151         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8152
8153         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8154         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8155         if(myPawns == 2 && nMine == 3) // KPP
8156             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8157         if(myPawns == 1 && nMine == 2) // KP
8158             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8159         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8160             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8161         if(myPawns) return FALSE;
8162         if(pCnt[WhiteRook+side])
8163             return pCnt[BlackRook-side] ||
8164                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8165                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8166                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8167         if(pCnt[WhiteCannon+side]) {
8168             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8169             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8170         }
8171         if(pCnt[WhiteKnight+side])
8172             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8173         return FALSE;
8174 }
8175
8176 int
8177 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8178 {
8179         VariantClass v = gameInfo.variant;
8180
8181         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8182         if(v == VariantShatranj) return TRUE; // always winnable through baring
8183         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8184         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8185
8186         if(v == VariantXiangqi) {
8187                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8188
8189                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8190                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8191                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8192                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8193                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8194                 if(stale) // we have at least one last-rank P plus perhaps C
8195                     return majors // KPKX
8196                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8197                 else // KCA*E*
8198                     return pCnt[WhiteFerz+side] // KCAK
8199                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8200                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8201                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8202
8203         } else if(v == VariantKnightmate) {
8204                 if(nMine == 1) return FALSE;
8205                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8206         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8207                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8208
8209                 if(nMine == 1) return FALSE; // bare King
8210                 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
8211                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8212                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8213                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8214                 if(pCnt[WhiteKnight+side])
8215                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8216                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8217                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8218                 if(nBishops)
8219                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8220                 if(pCnt[WhiteAlfil+side])
8221                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8222                 if(pCnt[WhiteWazir+side])
8223                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8224         }
8225
8226         return TRUE;
8227 }
8228
8229 int
8230 CompareWithRights (Board b1, Board b2)
8231 {
8232     int rights = 0;
8233     if(!CompareBoards(b1, b2)) return FALSE;
8234     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8235     /* compare castling rights */
8236     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8237            rights++; /* King lost rights, while rook still had them */
8238     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8239         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8240            rights++; /* but at least one rook lost them */
8241     }
8242     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8243            rights++;
8244     if( b1[CASTLING][5] != NoRights ) {
8245         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8246            rights++;
8247     }
8248     return rights == 0;
8249 }
8250
8251 int
8252 Adjudicate (ChessProgramState *cps)
8253 {       // [HGM] some adjudications useful with buggy engines
8254         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8255         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8256         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8257         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8258         int k, drop, count = 0; static int bare = 1;
8259         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8260         Boolean canAdjudicate = !appData.icsActive;
8261
8262         // most tests only when we understand the game, i.e. legality-checking on
8263             if( appData.testLegality )
8264             {   /* [HGM] Some more adjudications for obstinate engines */
8265                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8266                 static int moveCount = 6;
8267                 ChessMove result;
8268                 char *reason = NULL;
8269
8270                 /* Count what is on board. */
8271                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8272
8273                 /* Some material-based adjudications that have to be made before stalemate test */
8274                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8275                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8276                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8277                      if(canAdjudicate && appData.checkMates) {
8278                          if(engineOpponent)
8279                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8280                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8281                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8282                          return 1;
8283                      }
8284                 }
8285
8286                 /* Bare King in Shatranj (loses) or Losers (wins) */
8287                 if( nrW == 1 || nrB == 1) {
8288                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8289                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8290                      if(canAdjudicate && appData.checkMates) {
8291                          if(engineOpponent)
8292                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8293                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8294                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8295                          return 1;
8296                      }
8297                   } else
8298                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8299                   {    /* bare King */
8300                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8301                         if(canAdjudicate && appData.checkMates) {
8302                             /* but only adjudicate if adjudication enabled */
8303                             if(engineOpponent)
8304                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8305                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8306                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8307                             return 1;
8308                         }
8309                   }
8310                 } else bare = 1;
8311
8312
8313             // don't wait for engine to announce game end if we can judge ourselves
8314             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8315               case MT_CHECK:
8316                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8317                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8318                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8319                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8320                             checkCnt++;
8321                         if(checkCnt >= 2) {
8322                             reason = "Xboard adjudication: 3rd check";
8323                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8324                             break;
8325                         }
8326                     }
8327                 }
8328               case MT_NONE:
8329               default:
8330                 break;
8331               case MT_STEALMATE:
8332               case MT_STALEMATE:
8333               case MT_STAINMATE:
8334                 reason = "Xboard adjudication: Stalemate";
8335                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8336                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8337                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8338                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8339                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8340                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8341                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8342                                                                         EP_CHECKMATE : EP_WINS);
8343                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8344                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8345                 }
8346                 break;
8347               case MT_CHECKMATE:
8348                 reason = "Xboard adjudication: Checkmate";
8349                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8350                 if(gameInfo.variant == VariantShogi) {
8351                     if(forwardMostMove > backwardMostMove
8352                        && moveList[forwardMostMove-1][1] == '@'
8353                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8354                         reason = "XBoard adjudication: pawn-drop mate";
8355                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8356                     }
8357                 }
8358                 break;
8359             }
8360
8361                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8362                     case EP_STALEMATE:
8363                         result = GameIsDrawn; break;
8364                     case EP_CHECKMATE:
8365                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8366                     case EP_WINS:
8367                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8368                     default:
8369                         result = EndOfFile;
8370                 }
8371                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8372                     if(engineOpponent)
8373                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8374                     GameEnds( result, reason, GE_XBOARD );
8375                     return 1;
8376                 }
8377
8378                 /* Next absolutely insufficient mating material. */
8379                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8380                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8381                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8382
8383                      /* always flag draws, for judging claims */
8384                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8385
8386                      if(canAdjudicate && appData.materialDraws) {
8387                          /* but only adjudicate them if adjudication enabled */
8388                          if(engineOpponent) {
8389                            SendToProgram("force\n", engineOpponent); // suppress reply
8390                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8391                          }
8392                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8393                          return 1;
8394                      }
8395                 }
8396
8397                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8398                 if(gameInfo.variant == VariantXiangqi ?
8399                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8400                  : nrW + nrB == 4 &&
8401                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8402                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8403                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8404                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8405                    ) ) {
8406                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8407                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8408                           if(engineOpponent) {
8409                             SendToProgram("force\n", engineOpponent); // suppress reply
8410                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8411                           }
8412                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8413                           return 1;
8414                      }
8415                 } else moveCount = 6;
8416             }
8417
8418         // Repetition draws and 50-move rule can be applied independently of legality testing
8419
8420                 /* Check for rep-draws */
8421                 count = 0;
8422                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8423                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8424                 for(k = forwardMostMove-2;
8425                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8426                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8427                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8428                     k-=2)
8429                 {   int rights=0;
8430                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8431                         /* compare castling rights */
8432                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8433                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8434                                 rights++; /* King lost rights, while rook still had them */
8435                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8436                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8437                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8438                                    rights++; /* but at least one rook lost them */
8439                         }
8440                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8441                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8442                                 rights++;
8443                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8444                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8445                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8446                                    rights++;
8447                         }
8448                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8449                             && appData.drawRepeats > 1) {
8450                              /* adjudicate after user-specified nr of repeats */
8451                              int result = GameIsDrawn;
8452                              char *details = "XBoard adjudication: repetition draw";
8453                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8454                                 // [HGM] xiangqi: check for forbidden perpetuals
8455                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8456                                 for(m=forwardMostMove; m>k; m-=2) {
8457                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8458                                         ourPerpetual = 0; // the current mover did not always check
8459                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8460                                         hisPerpetual = 0; // the opponent did not always check
8461                                 }
8462                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8463                                                                         ourPerpetual, hisPerpetual);
8464                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8465                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8466                                     details = "Xboard adjudication: perpetual checking";
8467                                 } else
8468                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8469                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8470                                 } else
8471                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8472                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8473                                         result = BlackWins;
8474                                         details = "Xboard adjudication: repetition";
8475                                     }
8476                                 } else // it must be XQ
8477                                 // Now check for perpetual chases
8478                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8479                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8480                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8481                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8482                                         static char resdet[MSG_SIZ];
8483                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8484                                         details = resdet;
8485                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8486                                     } else
8487                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8488                                         break; // Abort repetition-checking loop.
8489                                 }
8490                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8491                              }
8492                              if(engineOpponent) {
8493                                SendToProgram("force\n", engineOpponent); // suppress reply
8494                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8495                              }
8496                              GameEnds( result, details, GE_XBOARD );
8497                              return 1;
8498                         }
8499                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8500                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8501                     }
8502                 }
8503
8504                 /* Now we test for 50-move draws. Determine ply count */
8505                 count = forwardMostMove;
8506                 /* look for last irreversble move */
8507                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8508                     count--;
8509                 /* if we hit starting position, add initial plies */
8510                 if( count == backwardMostMove )
8511                     count -= initialRulePlies;
8512                 count = forwardMostMove - count;
8513                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8514                         // adjust reversible move counter for checks in Xiangqi
8515                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8516                         if(i < backwardMostMove) i = backwardMostMove;
8517                         while(i <= forwardMostMove) {
8518                                 lastCheck = inCheck; // check evasion does not count
8519                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8520                                 if(inCheck || lastCheck) count--; // check does not count
8521                                 i++;
8522                         }
8523                 }
8524                 if( count >= 100)
8525                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8526                          /* this is used to judge if draw claims are legal */
8527                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8528                          if(engineOpponent) {
8529                            SendToProgram("force\n", engineOpponent); // suppress reply
8530                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8531                          }
8532                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8533                          return 1;
8534                 }
8535
8536                 /* if draw offer is pending, treat it as a draw claim
8537                  * when draw condition present, to allow engines a way to
8538                  * claim draws before making their move to avoid a race
8539                  * condition occurring after their move
8540                  */
8541                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8542                          char *p = NULL;
8543                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8544                              p = "Draw claim: 50-move rule";
8545                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8546                              p = "Draw claim: 3-fold repetition";
8547                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8548                              p = "Draw claim: insufficient mating material";
8549                          if( p != NULL && canAdjudicate) {
8550                              if(engineOpponent) {
8551                                SendToProgram("force\n", engineOpponent); // suppress reply
8552                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8553                              }
8554                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8555                              return 1;
8556                          }
8557                 }
8558
8559                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8560                     if(engineOpponent) {
8561                       SendToProgram("force\n", engineOpponent); // suppress reply
8562                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8563                     }
8564                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8565                     return 1;
8566                 }
8567         return 0;
8568 }
8569
8570 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8571 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8572 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8573
8574 static int
8575 BitbaseProbe ()
8576 {
8577     int pieces[10], squares[10], cnt=0, r, f, res;
8578     static int loaded;
8579     static PPROBE_EGBB probeBB;
8580     if(!appData.testLegality) return 10;
8581     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8582     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8583     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8584     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8585         ChessSquare piece = boards[forwardMostMove][r][f];
8586         int black = (piece >= BlackPawn);
8587         int type = piece - black*BlackPawn;
8588         if(piece == EmptySquare) continue;
8589         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8590         if(type == WhiteKing) type = WhiteQueen + 1;
8591         type = egbbCode[type];
8592         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8593         pieces[cnt] = type + black*6;
8594         if(++cnt > 5) return 11;
8595     }
8596     pieces[cnt] = squares[cnt] = 0;
8597     // probe EGBB
8598     if(loaded == 2) return 13; // loading failed before
8599     if(loaded == 0) {
8600         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8601         HMODULE lib;
8602         PLOAD_EGBB loadBB;
8603         loaded = 2; // prepare for failure
8604         if(!path) return 13; // no egbb installed
8605         strncpy(buf, path + 8, MSG_SIZ);
8606         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8607         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8608         lib = LoadLibrary(buf);
8609         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8610         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8611         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8612         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8613         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8614         loaded = 1; // success!
8615     }
8616     res = probeBB(forwardMostMove & 1, pieces, squares);
8617     return res > 0 ? 1 : res < 0 ? -1 : 0;
8618 }
8619
8620 char *
8621 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8622 {   // [HGM] book: this routine intercepts moves to simulate book replies
8623     char *bookHit = NULL;
8624
8625     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8626         char buf[MSG_SIZ];
8627         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8628         SendToProgram(buf, cps);
8629     }
8630     //first determine if the incoming move brings opponent into his book
8631     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8632         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8633     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8634     if(bookHit != NULL && !cps->bookSuspend) {
8635         // make sure opponent is not going to reply after receiving move to book position
8636         SendToProgram("force\n", cps);
8637         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8638     }
8639     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8640     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8641     // now arrange restart after book miss
8642     if(bookHit) {
8643         // after a book hit we never send 'go', and the code after the call to this routine
8644         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8645         char buf[MSG_SIZ], *move = bookHit;
8646         if(cps->useSAN) {
8647             int fromX, fromY, toX, toY;
8648             char promoChar;
8649             ChessMove moveType;
8650             move = buf + 30;
8651             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8652                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8653                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8654                                     PosFlags(forwardMostMove),
8655                                     fromY, fromX, toY, toX, promoChar, move);
8656             } else {
8657                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8658                 bookHit = NULL;
8659             }
8660         }
8661         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8662         SendToProgram(buf, cps);
8663         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8664     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8665         SendToProgram("go\n", cps);
8666         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8667     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8668         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8669             SendToProgram("go\n", cps);
8670         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8671     }
8672     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8673 }
8674
8675 int
8676 LoadError (char *errmess, ChessProgramState *cps)
8677 {   // unloads engine and switches back to -ncp mode if it was first
8678     if(cps->initDone) return FALSE;
8679     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8680     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8681     cps->pr = NoProc;
8682     if(cps == &first) {
8683         appData.noChessProgram = TRUE;
8684         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8685         gameMode = BeginningOfGame; ModeHighlight();
8686         SetNCPMode();
8687     }
8688     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8689     DisplayMessage("", ""); // erase waiting message
8690     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8691     return TRUE;
8692 }
8693
8694 char *savedMessage;
8695 ChessProgramState *savedState;
8696 void
8697 DeferredBookMove (void)
8698 {
8699         if(savedState->lastPing != savedState->lastPong)
8700                     ScheduleDelayedEvent(DeferredBookMove, 10);
8701         else
8702         HandleMachineMove(savedMessage, savedState);
8703 }
8704
8705 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8706 static ChessProgramState *stalledEngine;
8707 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8708
8709 void
8710 HandleMachineMove (char *message, ChessProgramState *cps)
8711 {
8712     static char firstLeg[20], legs;
8713     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8714     char realname[MSG_SIZ];
8715     int fromX, fromY, toX, toY;
8716     ChessMove moveType;
8717     char promoChar, roar;
8718     char *p, *pv=buf1;
8719     int oldError;
8720     char *bookHit;
8721
8722     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8723         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8724         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8725             DisplayError(_("Invalid pairing from pairing engine"), 0);
8726             return;
8727         }
8728         pairingReceived = 1;
8729         NextMatchGame();
8730         return; // Skim the pairing messages here.
8731     }
8732
8733     oldError = cps->userError; cps->userError = 0;
8734
8735 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8736     /*
8737      * Kludge to ignore BEL characters
8738      */
8739     while (*message == '\007') message++;
8740
8741     /*
8742      * [HGM] engine debug message: ignore lines starting with '#' character
8743      */
8744     if(cps->debug && *message == '#') return;
8745
8746     /*
8747      * Look for book output
8748      */
8749     if (cps == &first && bookRequested) {
8750         if (message[0] == '\t' || message[0] == ' ') {
8751             /* Part of the book output is here; append it */
8752             strcat(bookOutput, message);
8753             strcat(bookOutput, "  \n");
8754             return;
8755         } else if (bookOutput[0] != NULLCHAR) {
8756             /* All of book output has arrived; display it */
8757             char *p = bookOutput;
8758             while (*p != NULLCHAR) {
8759                 if (*p == '\t') *p = ' ';
8760                 p++;
8761             }
8762             DisplayInformation(bookOutput);
8763             bookRequested = FALSE;
8764             /* Fall through to parse the current output */
8765         }
8766     }
8767
8768     /*
8769      * Look for machine move.
8770      */
8771     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8772         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8773     {
8774         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8775             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8776             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8777             stalledEngine = cps;
8778             if(appData.ponderNextMove) { // bring opponent out of ponder
8779                 if(gameMode == TwoMachinesPlay) {
8780                     if(cps->other->pause)
8781                         PauseEngine(cps->other);
8782                     else
8783                         SendToProgram("easy\n", cps->other);
8784                 }
8785             }
8786             StopClocks();
8787             return;
8788         }
8789
8790       if(cps->usePing) {
8791
8792         /* This method is only useful on engines that support ping */
8793         if(abortEngineThink) {
8794             if (appData.debugMode) {
8795                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8796             }
8797             SendToProgram("undo\n", cps);
8798             return;
8799         }
8800
8801         if (cps->lastPing != cps->lastPong) {
8802             /* Extra move from before last new; ignore */
8803             if (appData.debugMode) {
8804                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8805             }
8806           return;
8807         }
8808
8809       } else {
8810
8811         int machineWhite = FALSE;
8812
8813         switch (gameMode) {
8814           case BeginningOfGame:
8815             /* Extra move from before last reset; ignore */
8816             if (appData.debugMode) {
8817                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8818             }
8819             return;
8820
8821           case EndOfGame:
8822           case IcsIdle:
8823           default:
8824             /* Extra move after we tried to stop.  The mode test is
8825                not a reliable way of detecting this problem, but it's
8826                the best we can do on engines that don't support ping.
8827             */
8828             if (appData.debugMode) {
8829                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8830                         cps->which, gameMode);
8831             }
8832             SendToProgram("undo\n", cps);
8833             return;
8834
8835           case MachinePlaysWhite:
8836           case IcsPlayingWhite:
8837             machineWhite = TRUE;
8838             break;
8839
8840           case MachinePlaysBlack:
8841           case IcsPlayingBlack:
8842             machineWhite = FALSE;
8843             break;
8844
8845           case TwoMachinesPlay:
8846             machineWhite = (cps->twoMachinesColor[0] == 'w');
8847             break;
8848         }
8849         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8850             if (appData.debugMode) {
8851                 fprintf(debugFP,
8852                         "Ignoring move out of turn by %s, gameMode %d"
8853                         ", forwardMost %d\n",
8854                         cps->which, gameMode, forwardMostMove);
8855             }
8856             return;
8857         }
8858       }
8859
8860         if(cps->alphaRank) AlphaRank(machineMove, 4);
8861
8862         // [HGM] lion: (some very limited) support for Alien protocol
8863         killX = killY = kill2X = kill2Y = -1;
8864         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8865             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8866             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8867             return;
8868         }
8869         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8870             char *q = strchr(p+1, ',');            // second comma?
8871             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8872             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8873             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8874         }
8875         if(firstLeg[0]) { // there was a previous leg;
8876             // only support case where same piece makes two step
8877             char buf[20], *p = machineMove+1, *q = buf+1, f;
8878             safeStrCpy(buf, machineMove, 20);
8879             while(isdigit(*q)) q++; // find start of to-square
8880             safeStrCpy(machineMove, firstLeg, 20);
8881             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8882             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
8883             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)
8884             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8885             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8886             firstLeg[0] = NULLCHAR; legs = 0;
8887         }
8888
8889         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8890                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8891             /* Machine move could not be parsed; ignore it. */
8892           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8893                     machineMove, _(cps->which));
8894             DisplayMoveError(buf1);
8895             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8896                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8897             if (gameMode == TwoMachinesPlay) {
8898               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8899                        buf1, GE_XBOARD);
8900             }
8901             return;
8902         }
8903
8904         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8905         /* So we have to redo legality test with true e.p. status here,  */
8906         /* to make sure an illegal e.p. capture does not slip through,   */
8907         /* to cause a forfeit on a justified illegal-move complaint      */
8908         /* of the opponent.                                              */
8909         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8910            ChessMove moveType;
8911            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8912                              fromY, fromX, toY, toX, promoChar);
8913             if(moveType == IllegalMove) {
8914               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8915                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8916                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8917                            buf1, GE_XBOARD);
8918                 return;
8919            } else if(!appData.fischerCastling)
8920            /* [HGM] Kludge to handle engines that send FRC-style castling
8921               when they shouldn't (like TSCP-Gothic) */
8922            switch(moveType) {
8923              case WhiteASideCastleFR:
8924              case BlackASideCastleFR:
8925                toX+=2;
8926                currentMoveString[2]++;
8927                break;
8928              case WhiteHSideCastleFR:
8929              case BlackHSideCastleFR:
8930                toX--;
8931                currentMoveString[2]--;
8932                break;
8933              default: ; // nothing to do, but suppresses warning of pedantic compilers
8934            }
8935         }
8936         hintRequested = FALSE;
8937         lastHint[0] = NULLCHAR;
8938         bookRequested = FALSE;
8939         /* Program may be pondering now */
8940         cps->maybeThinking = TRUE;
8941         if (cps->sendTime == 2) cps->sendTime = 1;
8942         if (cps->offeredDraw) cps->offeredDraw--;
8943
8944         /* [AS] Save move info*/
8945         pvInfoList[ forwardMostMove ].score = programStats.score;
8946         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8947         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8948
8949         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8950
8951         /* Test suites abort the 'game' after one move */
8952         if(*appData.finger) {
8953            static FILE *f;
8954            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8955            if(!f) f = fopen(appData.finger, "w");
8956            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8957            else { DisplayFatalError("Bad output file", errno, 0); return; }
8958            free(fen);
8959            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8960         }
8961         if(appData.epd) {
8962            if(solvingTime >= 0) {
8963               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8964               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8965            } else {
8966               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8967               if(solvingTime == -2) second.matchWins++;
8968            }
8969            OutputKibitz(2, buf1);
8970            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8971         }
8972
8973         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8974         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8975             int count = 0;
8976
8977             while( count < adjudicateLossPlies ) {
8978                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8979
8980                 if( count & 1 ) {
8981                     score = -score; /* Flip score for winning side */
8982                 }
8983
8984                 if( score > appData.adjudicateLossThreshold ) {
8985                     break;
8986                 }
8987
8988                 count++;
8989             }
8990
8991             if( count >= adjudicateLossPlies ) {
8992                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8993
8994                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8995                     "Xboard adjudication",
8996                     GE_XBOARD );
8997
8998                 return;
8999             }
9000         }
9001
9002         if(Adjudicate(cps)) {
9003             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9004             return; // [HGM] adjudicate: for all automatic game ends
9005         }
9006
9007 #if ZIPPY
9008         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9009             first.initDone) {
9010           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9011                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9012                 SendToICS("draw ");
9013                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9014           }
9015           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9016           ics_user_moved = 1;
9017           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9018                 char buf[3*MSG_SIZ];
9019
9020                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9021                         programStats.score / 100.,
9022                         programStats.depth,
9023                         programStats.time / 100.,
9024                         (unsigned int)programStats.nodes,
9025                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9026                         programStats.movelist);
9027                 SendToICS(buf);
9028           }
9029         }
9030 #endif
9031
9032         /* [AS] Clear stats for next move */
9033         ClearProgramStats();
9034         thinkOutput[0] = NULLCHAR;
9035         hiddenThinkOutputState = 0;
9036
9037         bookHit = NULL;
9038         if (gameMode == TwoMachinesPlay) {
9039             /* [HGM] relaying draw offers moved to after reception of move */
9040             /* and interpreting offer as claim if it brings draw condition */
9041             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9042                 SendToProgram("draw\n", cps->other);
9043             }
9044             if (cps->other->sendTime) {
9045                 SendTimeRemaining(cps->other,
9046                                   cps->other->twoMachinesColor[0] == 'w');
9047             }
9048             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9049             if (firstMove && !bookHit) {
9050                 firstMove = FALSE;
9051                 if (cps->other->useColors) {
9052                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9053                 }
9054                 SendToProgram("go\n", cps->other);
9055             }
9056             cps->other->maybeThinking = TRUE;
9057         }
9058
9059         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9060
9061         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9062
9063         if (!pausing && appData.ringBellAfterMoves) {
9064             if(!roar) RingBell();
9065         }
9066
9067         /*
9068          * Reenable menu items that were disabled while
9069          * machine was thinking
9070          */
9071         if (gameMode != TwoMachinesPlay)
9072             SetUserThinkingEnables();
9073
9074         // [HGM] book: after book hit opponent has received move and is now in force mode
9075         // force the book reply into it, and then fake that it outputted this move by jumping
9076         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9077         if(bookHit) {
9078                 static char bookMove[MSG_SIZ]; // a bit generous?
9079
9080                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9081                 strcat(bookMove, bookHit);
9082                 message = bookMove;
9083                 cps = cps->other;
9084                 programStats.nodes = programStats.depth = programStats.time =
9085                 programStats.score = programStats.got_only_move = 0;
9086                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9087
9088                 if(cps->lastPing != cps->lastPong) {
9089                     savedMessage = message; // args for deferred call
9090                     savedState = cps;
9091                     ScheduleDelayedEvent(DeferredBookMove, 10);
9092                     return;
9093                 }
9094                 goto FakeBookMove;
9095         }
9096
9097         return;
9098     }
9099
9100     /* Set special modes for chess engines.  Later something general
9101      *  could be added here; for now there is just one kludge feature,
9102      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9103      *  when "xboard" is given as an interactive command.
9104      */
9105     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9106         cps->useSigint = FALSE;
9107         cps->useSigterm = FALSE;
9108     }
9109     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9110       ParseFeatures(message+8, cps);
9111       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9112     }
9113
9114     if (!strncmp(message, "setup ", 6) && 
9115         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9116           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9117                                         ) { // [HGM] allow first engine to define opening position
9118       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9119       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9120       *buf = NULLCHAR;
9121       if(sscanf(message, "setup (%s", buf) == 1) {
9122         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9123         ASSIGN(appData.pieceToCharTable, buf);
9124       }
9125       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9126       if(dummy >= 3) {
9127         while(message[s] && message[s++] != ' ');
9128         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9129            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9130             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9131             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9132           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9133           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9134           startedFromSetupPosition = FALSE;
9135         }
9136       }
9137       if(startedFromSetupPosition) return;
9138       ParseFEN(boards[0], &dummy, message+s, FALSE);
9139       DrawPosition(TRUE, boards[0]);
9140       CopyBoard(initialPosition, boards[0]);
9141       startedFromSetupPosition = TRUE;
9142       return;
9143     }
9144     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9145       ChessSquare piece = WhitePawn;
9146       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9147       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9148       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9149       piece += CharToPiece(ID & 255) - WhitePawn;
9150       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9151       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9152       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9153       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9154       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9155       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9156                                                && gameInfo.variant != VariantGreat
9157                                                && gameInfo.variant != VariantFairy    ) return;
9158       if(piece < EmptySquare) {
9159         pieceDefs = TRUE;
9160         ASSIGN(pieceDesc[piece], buf1);
9161         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9162       }
9163       return;
9164     }
9165     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9166       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9167       Sweep(0);
9168       return;
9169     }
9170     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9171      * want this, I was asked to put it in, and obliged.
9172      */
9173     if (!strncmp(message, "setboard ", 9)) {
9174         Board initial_position;
9175
9176         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9177
9178         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9179             DisplayError(_("Bad FEN received from engine"), 0);
9180             return ;
9181         } else {
9182            Reset(TRUE, FALSE);
9183            CopyBoard(boards[0], initial_position);
9184            initialRulePlies = FENrulePlies;
9185            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9186            else gameMode = MachinePlaysBlack;
9187            DrawPosition(FALSE, boards[currentMove]);
9188         }
9189         return;
9190     }
9191
9192     /*
9193      * Look for communication commands
9194      */
9195     if (!strncmp(message, "telluser ", 9)) {
9196         if(message[9] == '\\' && message[10] == '\\')
9197             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9198         PlayTellSound();
9199         DisplayNote(message + 9);
9200         return;
9201     }
9202     if (!strncmp(message, "tellusererror ", 14)) {
9203         cps->userError = 1;
9204         if(message[14] == '\\' && message[15] == '\\')
9205             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9206         PlayTellSound();
9207         DisplayError(message + 14, 0);
9208         return;
9209     }
9210     if (!strncmp(message, "tellopponent ", 13)) {
9211       if (appData.icsActive) {
9212         if (loggedOn) {
9213           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9214           SendToICS(buf1);
9215         }
9216       } else {
9217         DisplayNote(message + 13);
9218       }
9219       return;
9220     }
9221     if (!strncmp(message, "tellothers ", 11)) {
9222       if (appData.icsActive) {
9223         if (loggedOn) {
9224           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9225           SendToICS(buf1);
9226         }
9227       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9228       return;
9229     }
9230     if (!strncmp(message, "tellall ", 8)) {
9231       if (appData.icsActive) {
9232         if (loggedOn) {
9233           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9234           SendToICS(buf1);
9235         }
9236       } else {
9237         DisplayNote(message + 8);
9238       }
9239       return;
9240     }
9241     if (strncmp(message, "warning", 7) == 0) {
9242         /* Undocumented feature, use tellusererror in new code */
9243         DisplayError(message, 0);
9244         return;
9245     }
9246     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9247         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9248         strcat(realname, " query");
9249         AskQuestion(realname, buf2, buf1, cps->pr);
9250         return;
9251     }
9252     /* Commands from the engine directly to ICS.  We don't allow these to be
9253      *  sent until we are logged on. Crafty kibitzes have been known to
9254      *  interfere with the login process.
9255      */
9256     if (loggedOn) {
9257         if (!strncmp(message, "tellics ", 8)) {
9258             SendToICS(message + 8);
9259             SendToICS("\n");
9260             return;
9261         }
9262         if (!strncmp(message, "tellicsnoalias ", 15)) {
9263             SendToICS(ics_prefix);
9264             SendToICS(message + 15);
9265             SendToICS("\n");
9266             return;
9267         }
9268         /* The following are for backward compatibility only */
9269         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9270             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9271             SendToICS(ics_prefix);
9272             SendToICS(message);
9273             SendToICS("\n");
9274             return;
9275         }
9276     }
9277     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9278         if(initPing == cps->lastPong) {
9279             if(gameInfo.variant == VariantUnknown) {
9280                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9281                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9282                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9283             }
9284             initPing = -1;
9285         }
9286         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9287             abortEngineThink = FALSE;
9288             DisplayMessage("", "");
9289             ThawUI();
9290         }
9291         return;
9292     }
9293     if(!strncmp(message, "highlight ", 10)) {
9294         if(appData.testLegality && !*engineVariant && appData.markers) return;
9295         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9296         return;
9297     }
9298     if(!strncmp(message, "click ", 6)) {
9299         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9300         if(appData.testLegality || !appData.oneClick) return;
9301         sscanf(message+6, "%c%d%c", &f, &y, &c);
9302         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9303         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9304         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9305         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9306         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9307         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9308             LeftClick(Release, lastLeftX, lastLeftY);
9309         controlKey  = (c == ',');
9310         LeftClick(Press, x, y);
9311         LeftClick(Release, x, y);
9312         first.highlight = f;
9313         return;
9314     }
9315     /*
9316      * If the move is illegal, cancel it and redraw the board.
9317      * Also deal with other error cases.  Matching is rather loose
9318      * here to accommodate engines written before the spec.
9319      */
9320     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9321         strncmp(message, "Error", 5) == 0) {
9322         if (StrStr(message, "name") ||
9323             StrStr(message, "rating") || StrStr(message, "?") ||
9324             StrStr(message, "result") || StrStr(message, "board") ||
9325             StrStr(message, "bk") || StrStr(message, "computer") ||
9326             StrStr(message, "variant") || StrStr(message, "hint") ||
9327             StrStr(message, "random") || StrStr(message, "depth") ||
9328             StrStr(message, "accepted")) {
9329             return;
9330         }
9331         if (StrStr(message, "protover")) {
9332           /* Program is responding to input, so it's apparently done
9333              initializing, and this error message indicates it is
9334              protocol version 1.  So we don't need to wait any longer
9335              for it to initialize and send feature commands. */
9336           FeatureDone(cps, 1);
9337           cps->protocolVersion = 1;
9338           return;
9339         }
9340         cps->maybeThinking = FALSE;
9341
9342         if (StrStr(message, "draw")) {
9343             /* Program doesn't have "draw" command */
9344             cps->sendDrawOffers = 0;
9345             return;
9346         }
9347         if (cps->sendTime != 1 &&
9348             (StrStr(message, "time") || StrStr(message, "otim"))) {
9349           /* Program apparently doesn't have "time" or "otim" command */
9350           cps->sendTime = 0;
9351           return;
9352         }
9353         if (StrStr(message, "analyze")) {
9354             cps->analysisSupport = FALSE;
9355             cps->analyzing = FALSE;
9356 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9357             EditGameEvent(); // [HGM] try to preserve loaded game
9358             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9359             DisplayError(buf2, 0);
9360             return;
9361         }
9362         if (StrStr(message, "(no matching move)st")) {
9363           /* Special kludge for GNU Chess 4 only */
9364           cps->stKludge = TRUE;
9365           SendTimeControl(cps, movesPerSession, timeControl,
9366                           timeIncrement, appData.searchDepth,
9367                           searchTime);
9368           return;
9369         }
9370         if (StrStr(message, "(no matching move)sd")) {
9371           /* Special kludge for GNU Chess 4 only */
9372           cps->sdKludge = TRUE;
9373           SendTimeControl(cps, movesPerSession, timeControl,
9374                           timeIncrement, appData.searchDepth,
9375                           searchTime);
9376           return;
9377         }
9378         if (!StrStr(message, "llegal")) {
9379             return;
9380         }
9381         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9382             gameMode == IcsIdle) return;
9383         if (forwardMostMove <= backwardMostMove) return;
9384         if (pausing) PauseEvent();
9385       if(appData.forceIllegal) {
9386             // [HGM] illegal: machine refused move; force position after move into it
9387           SendToProgram("force\n", cps);
9388           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9389                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9390                 // when black is to move, while there might be nothing on a2 or black
9391                 // might already have the move. So send the board as if white has the move.
9392                 // But first we must change the stm of the engine, as it refused the last move
9393                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9394                 if(WhiteOnMove(forwardMostMove)) {
9395                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9396                     SendBoard(cps, forwardMostMove); // kludgeless board
9397                 } else {
9398                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9399                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9400                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9401                 }
9402           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9403             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9404                  gameMode == TwoMachinesPlay)
9405               SendToProgram("go\n", cps);
9406             return;
9407       } else
9408         if (gameMode == PlayFromGameFile) {
9409             /* Stop reading this game file */
9410             gameMode = EditGame;
9411             ModeHighlight();
9412         }
9413         /* [HGM] illegal-move claim should forfeit game when Xboard */
9414         /* only passes fully legal moves                            */
9415         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9416             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9417                                 "False illegal-move claim", GE_XBOARD );
9418             return; // do not take back move we tested as valid
9419         }
9420         currentMove = forwardMostMove-1;
9421         DisplayMove(currentMove-1); /* before DisplayMoveError */
9422         SwitchClocks(forwardMostMove-1); // [HGM] race
9423         DisplayBothClocks();
9424         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9425                 parseList[currentMove], _(cps->which));
9426         DisplayMoveError(buf1);
9427         DrawPosition(FALSE, boards[currentMove]);
9428
9429         SetUserThinkingEnables();
9430         return;
9431     }
9432     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9433         /* Program has a broken "time" command that
9434            outputs a string not ending in newline.
9435            Don't use it. */
9436         cps->sendTime = 0;
9437     }
9438     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9439         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9440             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9441     }
9442
9443     /*
9444      * If chess program startup fails, exit with an error message.
9445      * Attempts to recover here are futile. [HGM] Well, we try anyway
9446      */
9447     if ((StrStr(message, "unknown host") != NULL)
9448         || (StrStr(message, "No remote directory") != NULL)
9449         || (StrStr(message, "not found") != NULL)
9450         || (StrStr(message, "No such file") != NULL)
9451         || (StrStr(message, "can't alloc") != NULL)
9452         || (StrStr(message, "Permission denied") != NULL)) {
9453
9454         cps->maybeThinking = FALSE;
9455         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9456                 _(cps->which), cps->program, cps->host, message);
9457         RemoveInputSource(cps->isr);
9458         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9459             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9460             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9461         }
9462         return;
9463     }
9464
9465     /*
9466      * Look for hint output
9467      */
9468     if (sscanf(message, "Hint: %s", buf1) == 1) {
9469         if (cps == &first && hintRequested) {
9470             hintRequested = FALSE;
9471             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9472                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9473                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9474                                     PosFlags(forwardMostMove),
9475                                     fromY, fromX, toY, toX, promoChar, buf1);
9476                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9477                 DisplayInformation(buf2);
9478             } else {
9479                 /* Hint move could not be parsed!? */
9480               snprintf(buf2, sizeof(buf2),
9481                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9482                         buf1, _(cps->which));
9483                 DisplayError(buf2, 0);
9484             }
9485         } else {
9486           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9487         }
9488         return;
9489     }
9490
9491     /*
9492      * Ignore other messages if game is not in progress
9493      */
9494     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9495         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9496
9497     /*
9498      * look for win, lose, draw, or draw offer
9499      */
9500     if (strncmp(message, "1-0", 3) == 0) {
9501         char *p, *q, *r = "";
9502         p = strchr(message, '{');
9503         if (p) {
9504             q = strchr(p, '}');
9505             if (q) {
9506                 *q = NULLCHAR;
9507                 r = p + 1;
9508             }
9509         }
9510         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9511         return;
9512     } else if (strncmp(message, "0-1", 3) == 0) {
9513         char *p, *q, *r = "";
9514         p = strchr(message, '{');
9515         if (p) {
9516             q = strchr(p, '}');
9517             if (q) {
9518                 *q = NULLCHAR;
9519                 r = p + 1;
9520             }
9521         }
9522         /* Kludge for Arasan 4.1 bug */
9523         if (strcmp(r, "Black resigns") == 0) {
9524             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9525             return;
9526         }
9527         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9528         return;
9529     } else if (strncmp(message, "1/2", 3) == 0) {
9530         char *p, *q, *r = "";
9531         p = strchr(message, '{');
9532         if (p) {
9533             q = strchr(p, '}');
9534             if (q) {
9535                 *q = NULLCHAR;
9536                 r = p + 1;
9537             }
9538         }
9539
9540         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9541         return;
9542
9543     } else if (strncmp(message, "White resign", 12) == 0) {
9544         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9545         return;
9546     } else if (strncmp(message, "Black resign", 12) == 0) {
9547         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9548         return;
9549     } else if (strncmp(message, "White matches", 13) == 0 ||
9550                strncmp(message, "Black matches", 13) == 0   ) {
9551         /* [HGM] ignore GNUShogi noises */
9552         return;
9553     } else if (strncmp(message, "White", 5) == 0 &&
9554                message[5] != '(' &&
9555                StrStr(message, "Black") == NULL) {
9556         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9557         return;
9558     } else if (strncmp(message, "Black", 5) == 0 &&
9559                message[5] != '(') {
9560         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9561         return;
9562     } else if (strcmp(message, "resign") == 0 ||
9563                strcmp(message, "computer resigns") == 0) {
9564         switch (gameMode) {
9565           case MachinePlaysBlack:
9566           case IcsPlayingBlack:
9567             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9568             break;
9569           case MachinePlaysWhite:
9570           case IcsPlayingWhite:
9571             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9572             break;
9573           case TwoMachinesPlay:
9574             if (cps->twoMachinesColor[0] == 'w')
9575               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9576             else
9577               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9578             break;
9579           default:
9580             /* can't happen */
9581             break;
9582         }
9583         return;
9584     } else if (strncmp(message, "opponent mates", 14) == 0) {
9585         switch (gameMode) {
9586           case MachinePlaysBlack:
9587           case IcsPlayingBlack:
9588             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9589             break;
9590           case MachinePlaysWhite:
9591           case IcsPlayingWhite:
9592             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9593             break;
9594           case TwoMachinesPlay:
9595             if (cps->twoMachinesColor[0] == 'w')
9596               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9597             else
9598               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9599             break;
9600           default:
9601             /* can't happen */
9602             break;
9603         }
9604         return;
9605     } else if (strncmp(message, "computer mates", 14) == 0) {
9606         switch (gameMode) {
9607           case MachinePlaysBlack:
9608           case IcsPlayingBlack:
9609             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9610             break;
9611           case MachinePlaysWhite:
9612           case IcsPlayingWhite:
9613             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9614             break;
9615           case TwoMachinesPlay:
9616             if (cps->twoMachinesColor[0] == 'w')
9617               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9618             else
9619               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9620             break;
9621           default:
9622             /* can't happen */
9623             break;
9624         }
9625         return;
9626     } else if (strncmp(message, "checkmate", 9) == 0) {
9627         if (WhiteOnMove(forwardMostMove)) {
9628             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9629         } else {
9630             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9631         }
9632         return;
9633     } else if (strstr(message, "Draw") != NULL ||
9634                strstr(message, "game is a draw") != NULL) {
9635         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9636         return;
9637     } else if (strstr(message, "offer") != NULL &&
9638                strstr(message, "draw") != NULL) {
9639 #if ZIPPY
9640         if (appData.zippyPlay && first.initDone) {
9641             /* Relay offer to ICS */
9642             SendToICS(ics_prefix);
9643             SendToICS("draw\n");
9644         }
9645 #endif
9646         cps->offeredDraw = 2; /* valid until this engine moves twice */
9647         if (gameMode == TwoMachinesPlay) {
9648             if (cps->other->offeredDraw) {
9649                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9650             /* [HGM] in two-machine mode we delay relaying draw offer      */
9651             /* until after we also have move, to see if it is really claim */
9652             }
9653         } else if (gameMode == MachinePlaysWhite ||
9654                    gameMode == MachinePlaysBlack) {
9655           if (userOfferedDraw) {
9656             DisplayInformation(_("Machine accepts your draw offer"));
9657             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9658           } else {
9659             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9660           }
9661         }
9662     }
9663
9664
9665     /*
9666      * Look for thinking output
9667      */
9668     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9669           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9670                                 ) {
9671         int plylev, mvleft, mvtot, curscore, time;
9672         char mvname[MOVE_LEN];
9673         u64 nodes; // [DM]
9674         char plyext;
9675         int ignore = FALSE;
9676         int prefixHint = FALSE;
9677         mvname[0] = NULLCHAR;
9678
9679         switch (gameMode) {
9680           case MachinePlaysBlack:
9681           case IcsPlayingBlack:
9682             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9683             break;
9684           case MachinePlaysWhite:
9685           case IcsPlayingWhite:
9686             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9687             break;
9688           case AnalyzeMode:
9689           case AnalyzeFile:
9690             break;
9691           case IcsObserving: /* [DM] icsEngineAnalyze */
9692             if (!appData.icsEngineAnalyze) ignore = TRUE;
9693             break;
9694           case TwoMachinesPlay:
9695             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9696                 ignore = TRUE;
9697             }
9698             break;
9699           default:
9700             ignore = TRUE;
9701             break;
9702         }
9703
9704         if (!ignore) {
9705             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9706             int solved = 0;
9707             buf1[0] = NULLCHAR;
9708             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9709                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9710                 char score_buf[MSG_SIZ];
9711
9712                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9713                     nodes += u64Const(0x100000000);
9714
9715                 if (plyext != ' ' && plyext != '\t') {
9716                     time *= 100;
9717                 }
9718
9719                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9720                 if( cps->scoreIsAbsolute &&
9721                     ( gameMode == MachinePlaysBlack ||
9722                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9723                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9724                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9725                      !WhiteOnMove(currentMove)
9726                     ) )
9727                 {
9728                     curscore = -curscore;
9729                 }
9730
9731                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9732
9733                 if(*bestMove) { // rememer time best EPD move was first found
9734                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9735                     ChessMove mt; char *p = bestMove;
9736                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9737                     solved = 0;
9738                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9739                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9740                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9741                             solved = 1;
9742                             break;
9743                         }
9744                         while(*p && *p != ' ') p++;
9745                         while(*p == ' ') p++;
9746                     }
9747                     if(!solved) solvingTime = -1;
9748                 }
9749                 if(*avoidMove && !solved) {
9750                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9751                     ChessMove mt; char *p = avoidMove, solved = 1;
9752                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9753                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9754                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9755                             solved = 0; solvingTime = -2;
9756                             break;
9757                         }
9758                         while(*p && *p != ' ') p++;
9759                         while(*p == ' ') p++;
9760                     }
9761                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9762                 }
9763
9764                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9765                         char buf[MSG_SIZ];
9766                         FILE *f;
9767                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9768                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9769                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9770                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9771                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9772                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9773                                 fclose(f);
9774                         }
9775                         else
9776                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9777                           DisplayError(_("failed writing PV"), 0);
9778                 }
9779
9780                 tempStats.depth = plylev;
9781                 tempStats.nodes = nodes;
9782                 tempStats.time = time;
9783                 tempStats.score = curscore;
9784                 tempStats.got_only_move = 0;
9785
9786                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9787                         int ticklen;
9788
9789                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9790                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9791                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9792                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9793                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9794                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9795                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9796                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9797                 }
9798
9799                 /* Buffer overflow protection */
9800                 if (pv[0] != NULLCHAR) {
9801                     if (strlen(pv) >= sizeof(tempStats.movelist)
9802                         && appData.debugMode) {
9803                         fprintf(debugFP,
9804                                 "PV is too long; using the first %u bytes.\n",
9805                                 (unsigned) sizeof(tempStats.movelist) - 1);
9806                     }
9807
9808                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9809                 } else {
9810                     sprintf(tempStats.movelist, " no PV\n");
9811                 }
9812
9813                 if (tempStats.seen_stat) {
9814                     tempStats.ok_to_send = 1;
9815                 }
9816
9817                 if (strchr(tempStats.movelist, '(') != NULL) {
9818                     tempStats.line_is_book = 1;
9819                     tempStats.nr_moves = 0;
9820                     tempStats.moves_left = 0;
9821                 } else {
9822                     tempStats.line_is_book = 0;
9823                 }
9824
9825                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9826                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9827
9828                 SendProgramStatsToFrontend( cps, &tempStats );
9829
9830                 /*
9831                     [AS] Protect the thinkOutput buffer from overflow... this
9832                     is only useful if buf1 hasn't overflowed first!
9833                 */
9834                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9835                 if(curscore >= MATE_SCORE) 
9836                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9837                 else if(curscore <= -MATE_SCORE) 
9838                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9839                 else
9840                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9841                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9842                          plylev,
9843                          (gameMode == TwoMachinesPlay ?
9844                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9845                          score_buf,
9846                          prefixHint ? lastHint : "",
9847                          prefixHint ? " " : "" );
9848
9849                 if( buf1[0] != NULLCHAR ) {
9850                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9851
9852                     if( strlen(pv) > max_len ) {
9853                         if( appData.debugMode) {
9854                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9855                         }
9856                         pv[max_len+1] = '\0';
9857                     }
9858
9859                     strcat( thinkOutput, pv);
9860                 }
9861
9862                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9863                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9864                     DisplayMove(currentMove - 1);
9865                 }
9866                 return;
9867
9868             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9869                 /* crafty (9.25+) says "(only move) <move>"
9870                  * if there is only 1 legal move
9871                  */
9872                 sscanf(p, "(only move) %s", buf1);
9873                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9874                 sprintf(programStats.movelist, "%s (only move)", buf1);
9875                 programStats.depth = 1;
9876                 programStats.nr_moves = 1;
9877                 programStats.moves_left = 1;
9878                 programStats.nodes = 1;
9879                 programStats.time = 1;
9880                 programStats.got_only_move = 1;
9881
9882                 /* Not really, but we also use this member to
9883                    mean "line isn't going to change" (Crafty
9884                    isn't searching, so stats won't change) */
9885                 programStats.line_is_book = 1;
9886
9887                 SendProgramStatsToFrontend( cps, &programStats );
9888
9889                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9890                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9891                     DisplayMove(currentMove - 1);
9892                 }
9893                 return;
9894             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9895                               &time, &nodes, &plylev, &mvleft,
9896                               &mvtot, mvname) >= 5) {
9897                 /* The stat01: line is from Crafty (9.29+) in response
9898                    to the "." command */
9899                 programStats.seen_stat = 1;
9900                 cps->maybeThinking = TRUE;
9901
9902                 if (programStats.got_only_move || !appData.periodicUpdates)
9903                   return;
9904
9905                 programStats.depth = plylev;
9906                 programStats.time = time;
9907                 programStats.nodes = nodes;
9908                 programStats.moves_left = mvleft;
9909                 programStats.nr_moves = mvtot;
9910                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9911                 programStats.ok_to_send = 1;
9912                 programStats.movelist[0] = '\0';
9913
9914                 SendProgramStatsToFrontend( cps, &programStats );
9915
9916                 return;
9917
9918             } else if (strncmp(message,"++",2) == 0) {
9919                 /* Crafty 9.29+ outputs this */
9920                 programStats.got_fail = 2;
9921                 return;
9922
9923             } else if (strncmp(message,"--",2) == 0) {
9924                 /* Crafty 9.29+ outputs this */
9925                 programStats.got_fail = 1;
9926                 return;
9927
9928             } else if (thinkOutput[0] != NULLCHAR &&
9929                        strncmp(message, "    ", 4) == 0) {
9930                 unsigned message_len;
9931
9932                 p = message;
9933                 while (*p && *p == ' ') p++;
9934
9935                 message_len = strlen( p );
9936
9937                 /* [AS] Avoid buffer overflow */
9938                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9939                     strcat(thinkOutput, " ");
9940                     strcat(thinkOutput, p);
9941                 }
9942
9943                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9944                     strcat(programStats.movelist, " ");
9945                     strcat(programStats.movelist, p);
9946                 }
9947
9948                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9949                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9950                     DisplayMove(currentMove - 1);
9951                 }
9952                 return;
9953             }
9954         }
9955         else {
9956             buf1[0] = NULLCHAR;
9957
9958             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9959                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9960             {
9961                 ChessProgramStats cpstats;
9962
9963                 if (plyext != ' ' && plyext != '\t') {
9964                     time *= 100;
9965                 }
9966
9967                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9968                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9969                     curscore = -curscore;
9970                 }
9971
9972                 cpstats.depth = plylev;
9973                 cpstats.nodes = nodes;
9974                 cpstats.time = time;
9975                 cpstats.score = curscore;
9976                 cpstats.got_only_move = 0;
9977                 cpstats.movelist[0] = '\0';
9978
9979                 if (buf1[0] != NULLCHAR) {
9980                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9981                 }
9982
9983                 cpstats.ok_to_send = 0;
9984                 cpstats.line_is_book = 0;
9985                 cpstats.nr_moves = 0;
9986                 cpstats.moves_left = 0;
9987
9988                 SendProgramStatsToFrontend( cps, &cpstats );
9989             }
9990         }
9991     }
9992 }
9993
9994
9995 /* Parse a game score from the character string "game", and
9996    record it as the history of the current game.  The game
9997    score is NOT assumed to start from the standard position.
9998    The display is not updated in any way.
9999    */
10000 void
10001 ParseGameHistory (char *game)
10002 {
10003     ChessMove moveType;
10004     int fromX, fromY, toX, toY, boardIndex;
10005     char promoChar;
10006     char *p, *q;
10007     char buf[MSG_SIZ];
10008
10009     if (appData.debugMode)
10010       fprintf(debugFP, "Parsing game history: %s\n", game);
10011
10012     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10013     gameInfo.site = StrSave(appData.icsHost);
10014     gameInfo.date = PGNDate();
10015     gameInfo.round = StrSave("-");
10016
10017     /* Parse out names of players */
10018     while (*game == ' ') game++;
10019     p = buf;
10020     while (*game != ' ') *p++ = *game++;
10021     *p = NULLCHAR;
10022     gameInfo.white = StrSave(buf);
10023     while (*game == ' ') game++;
10024     p = buf;
10025     while (*game != ' ' && *game != '\n') *p++ = *game++;
10026     *p = NULLCHAR;
10027     gameInfo.black = StrSave(buf);
10028
10029     /* Parse moves */
10030     boardIndex = blackPlaysFirst ? 1 : 0;
10031     yynewstr(game);
10032     for (;;) {
10033         yyboardindex = boardIndex;
10034         moveType = (ChessMove) Myylex();
10035         switch (moveType) {
10036           case IllegalMove:             /* maybe suicide chess, etc. */
10037   if (appData.debugMode) {
10038     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10039     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10040     setbuf(debugFP, NULL);
10041   }
10042           case WhitePromotion:
10043           case BlackPromotion:
10044           case WhiteNonPromotion:
10045           case BlackNonPromotion:
10046           case NormalMove:
10047           case FirstLeg:
10048           case WhiteCapturesEnPassant:
10049           case BlackCapturesEnPassant:
10050           case WhiteKingSideCastle:
10051           case WhiteQueenSideCastle:
10052           case BlackKingSideCastle:
10053           case BlackQueenSideCastle:
10054           case WhiteKingSideCastleWild:
10055           case WhiteQueenSideCastleWild:
10056           case BlackKingSideCastleWild:
10057           case BlackQueenSideCastleWild:
10058           /* PUSH Fabien */
10059           case WhiteHSideCastleFR:
10060           case WhiteASideCastleFR:
10061           case BlackHSideCastleFR:
10062           case BlackASideCastleFR:
10063           /* POP Fabien */
10064             fromX = currentMoveString[0] - AAA;
10065             fromY = currentMoveString[1] - ONE;
10066             toX = currentMoveString[2] - AAA;
10067             toY = currentMoveString[3] - ONE;
10068             promoChar = currentMoveString[4];
10069             break;
10070           case WhiteDrop:
10071           case BlackDrop:
10072             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10073             fromX = moveType == WhiteDrop ?
10074               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10075             (int) CharToPiece(ToLower(currentMoveString[0]));
10076             fromY = DROP_RANK;
10077             toX = currentMoveString[2] - AAA;
10078             toY = currentMoveString[3] - ONE;
10079             promoChar = NULLCHAR;
10080             break;
10081           case AmbiguousMove:
10082             /* bug? */
10083             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10084   if (appData.debugMode) {
10085     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10086     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10087     setbuf(debugFP, NULL);
10088   }
10089             DisplayError(buf, 0);
10090             return;
10091           case ImpossibleMove:
10092             /* bug? */
10093             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10094   if (appData.debugMode) {
10095     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10096     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10097     setbuf(debugFP, NULL);
10098   }
10099             DisplayError(buf, 0);
10100             return;
10101           case EndOfFile:
10102             if (boardIndex < backwardMostMove) {
10103                 /* Oops, gap.  How did that happen? */
10104                 DisplayError(_("Gap in move list"), 0);
10105                 return;
10106             }
10107             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10108             if (boardIndex > forwardMostMove) {
10109                 forwardMostMove = boardIndex;
10110             }
10111             return;
10112           case ElapsedTime:
10113             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10114                 strcat(parseList[boardIndex-1], " ");
10115                 strcat(parseList[boardIndex-1], yy_text);
10116             }
10117             continue;
10118           case Comment:
10119           case PGNTag:
10120           case NAG:
10121           default:
10122             /* ignore */
10123             continue;
10124           case WhiteWins:
10125           case BlackWins:
10126           case GameIsDrawn:
10127           case GameUnfinished:
10128             if (gameMode == IcsExamining) {
10129                 if (boardIndex < backwardMostMove) {
10130                     /* Oops, gap.  How did that happen? */
10131                     return;
10132                 }
10133                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10134                 return;
10135             }
10136             gameInfo.result = moveType;
10137             p = strchr(yy_text, '{');
10138             if (p == NULL) p = strchr(yy_text, '(');
10139             if (p == NULL) {
10140                 p = yy_text;
10141                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10142             } else {
10143                 q = strchr(p, *p == '{' ? '}' : ')');
10144                 if (q != NULL) *q = NULLCHAR;
10145                 p++;
10146             }
10147             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10148             gameInfo.resultDetails = StrSave(p);
10149             continue;
10150         }
10151         if (boardIndex >= forwardMostMove &&
10152             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10153             backwardMostMove = blackPlaysFirst ? 1 : 0;
10154             return;
10155         }
10156         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10157                                  fromY, fromX, toY, toX, promoChar,
10158                                  parseList[boardIndex]);
10159         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10160         /* currentMoveString is set as a side-effect of yylex */
10161         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10162         strcat(moveList[boardIndex], "\n");
10163         boardIndex++;
10164         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10165         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10166           case MT_NONE:
10167           case MT_STALEMATE:
10168           default:
10169             break;
10170           case MT_CHECK:
10171             if(!IS_SHOGI(gameInfo.variant))
10172                 strcat(parseList[boardIndex - 1], "+");
10173             break;
10174           case MT_CHECKMATE:
10175           case MT_STAINMATE:
10176             strcat(parseList[boardIndex - 1], "#");
10177             break;
10178         }
10179     }
10180 }
10181
10182
10183 /* Apply a move to the given board  */
10184 void
10185 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10186 {
10187   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10188   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10189
10190     /* [HGM] compute & store e.p. status and castling rights for new position */
10191     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10192
10193       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10194       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10195       board[EP_STATUS] = EP_NONE;
10196       board[EP_FILE] = board[EP_RANK] = 100;
10197
10198   if (fromY == DROP_RANK) {
10199         /* must be first */
10200         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10201             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10202             return;
10203         }
10204         piece = board[toY][toX] = (ChessSquare) fromX;
10205   } else {
10206 //      ChessSquare victim;
10207       int i;
10208
10209       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10210 //           victim = board[killY][killX],
10211            killed = board[killY][killX],
10212            board[killY][killX] = EmptySquare,
10213            board[EP_STATUS] = EP_CAPTURE;
10214            if( kill2X >= 0 && kill2Y >= 0)
10215              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10216       }
10217
10218       if( board[toY][toX] != EmptySquare ) {
10219            board[EP_STATUS] = EP_CAPTURE;
10220            if( (fromX != toX || fromY != toY) && // not igui!
10221                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10222                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10223                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10224            }
10225       }
10226
10227       pawn = board[fromY][fromX];
10228       if( pawn == WhiteLance || pawn == BlackLance ) {
10229            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10230                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10231                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10232            }
10233       }
10234       if( pawn == WhitePawn ) {
10235            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10236                board[EP_STATUS] = EP_PAWN_MOVE;
10237            if( toY-fromY>=2) {
10238                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10239                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10240                         gameInfo.variant != VariantBerolina || toX < fromX)
10241                       board[EP_STATUS] = toX | berolina;
10242                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10243                         gameInfo.variant != VariantBerolina || toX > fromX)
10244                       board[EP_STATUS] = toX;
10245            }
10246       } else
10247       if( pawn == BlackPawn ) {
10248            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10249                board[EP_STATUS] = EP_PAWN_MOVE;
10250            if( toY-fromY<= -2) {
10251                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10252                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10253                         gameInfo.variant != VariantBerolina || toX < fromX)
10254                       board[EP_STATUS] = toX | berolina;
10255                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10256                         gameInfo.variant != VariantBerolina || toX > fromX)
10257                       board[EP_STATUS] = toX;
10258            }
10259        }
10260
10261        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10262        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10263        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10264        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10265
10266        for(i=0; i<nrCastlingRights; i++) {
10267            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10268               board[CASTLING][i] == toX   && castlingRank[i] == toY
10269              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10270        }
10271
10272        if(gameInfo.variant == VariantSChess) { // update virginity
10273            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10274            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10275            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10276            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10277        }
10278
10279      if (fromX == toX && fromY == toY && killX < 0) return;
10280
10281      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10282      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10283      if(gameInfo.variant == VariantKnightmate)
10284          king += (int) WhiteUnicorn - (int) WhiteKing;
10285
10286     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10287        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10288         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10289         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10290         board[EP_STATUS] = EP_NONE; // capture was fake!
10291     } else
10292     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10293         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10294         board[toY][toX] = piece;
10295         board[EP_STATUS] = EP_NONE; // capture was fake!
10296     } else
10297     /* Code added by Tord: */
10298     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10299     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10300         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10301       board[EP_STATUS] = EP_NONE; // capture was fake!
10302       board[fromY][fromX] = EmptySquare;
10303       board[toY][toX] = EmptySquare;
10304       if((toX > fromX) != (piece == WhiteRook)) {
10305         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10306       } else {
10307         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10308       }
10309     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10310                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10311       board[EP_STATUS] = EP_NONE;
10312       board[fromY][fromX] = EmptySquare;
10313       board[toY][toX] = EmptySquare;
10314       if((toX > fromX) != (piece == BlackRook)) {
10315         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10316       } else {
10317         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10318       }
10319     /* End of code added by Tord */
10320
10321     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10322         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10323         board[toY][toX] = piece;
10324     } else if (board[fromY][fromX] == king
10325         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10326         && toY == fromY && toX > fromX+1) {
10327         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10328         board[fromY][toX-1] = board[fromY][rookX];
10329         board[fromY][rookX] = EmptySquare;
10330         board[fromY][fromX] = EmptySquare;
10331         board[toY][toX] = king;
10332     } else if (board[fromY][fromX] == king
10333         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10334                && toY == fromY && toX < fromX-1) {
10335         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10336         board[fromY][toX+1] = board[fromY][rookX];
10337         board[fromY][rookX] = EmptySquare;
10338         board[fromY][fromX] = EmptySquare;
10339         board[toY][toX] = king;
10340     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10341                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10342                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10343                ) {
10344         /* white pawn promotion */
10345         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10346         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10347             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10348         board[fromY][fromX] = EmptySquare;
10349     } else if ((fromY >= BOARD_HEIGHT>>1)
10350                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10351                && (toX != fromX)
10352                && gameInfo.variant != VariantXiangqi
10353                && gameInfo.variant != VariantBerolina
10354                && (pawn == WhitePawn)
10355                && (board[toY][toX] == EmptySquare)) {
10356         board[fromY][fromX] = EmptySquare;
10357         board[toY][toX] = piece;
10358         if(toY == epRank - 128 + 1)
10359             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10360         else
10361             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10362     } else if ((fromY == BOARD_HEIGHT-4)
10363                && (toX == fromX)
10364                && gameInfo.variant == VariantBerolina
10365                && (board[fromY][fromX] == WhitePawn)
10366                && (board[toY][toX] == EmptySquare)) {
10367         board[fromY][fromX] = EmptySquare;
10368         board[toY][toX] = WhitePawn;
10369         if(oldEP & EP_BEROLIN_A) {
10370                 captured = board[fromY][fromX-1];
10371                 board[fromY][fromX-1] = EmptySquare;
10372         }else{  captured = board[fromY][fromX+1];
10373                 board[fromY][fromX+1] = EmptySquare;
10374         }
10375     } else if (board[fromY][fromX] == king
10376         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10377                && toY == fromY && toX > fromX+1) {
10378         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10379         board[fromY][toX-1] = board[fromY][rookX];
10380         board[fromY][rookX] = EmptySquare;
10381         board[fromY][fromX] = EmptySquare;
10382         board[toY][toX] = king;
10383     } else if (board[fromY][fromX] == king
10384         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10385                && toY == fromY && toX < fromX-1) {
10386         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10387         board[fromY][toX+1] = board[fromY][rookX];
10388         board[fromY][rookX] = EmptySquare;
10389         board[fromY][fromX] = EmptySquare;
10390         board[toY][toX] = king;
10391     } else if (fromY == 7 && fromX == 3
10392                && board[fromY][fromX] == BlackKing
10393                && toY == 7 && toX == 5) {
10394         board[fromY][fromX] = EmptySquare;
10395         board[toY][toX] = BlackKing;
10396         board[fromY][7] = EmptySquare;
10397         board[toY][4] = BlackRook;
10398     } else if (fromY == 7 && fromX == 3
10399                && board[fromY][fromX] == BlackKing
10400                && toY == 7 && toX == 1) {
10401         board[fromY][fromX] = EmptySquare;
10402         board[toY][toX] = BlackKing;
10403         board[fromY][0] = EmptySquare;
10404         board[toY][2] = BlackRook;
10405     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10406                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10407                && toY < promoRank && promoChar
10408                ) {
10409         /* black pawn promotion */
10410         board[toY][toX] = CharToPiece(ToLower(promoChar));
10411         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10412             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10413         board[fromY][fromX] = EmptySquare;
10414     } else if ((fromY < BOARD_HEIGHT>>1)
10415                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10416                && (toX != fromX)
10417                && gameInfo.variant != VariantXiangqi
10418                && gameInfo.variant != VariantBerolina
10419                && (pawn == BlackPawn)
10420                && (board[toY][toX] == EmptySquare)) {
10421         board[fromY][fromX] = EmptySquare;
10422         board[toY][toX] = piece;
10423         if(toY == epRank - 128 - 1)
10424             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10425         else
10426             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10427     } else if ((fromY == 3)
10428                && (toX == fromX)
10429                && gameInfo.variant == VariantBerolina
10430                && (board[fromY][fromX] == BlackPawn)
10431                && (board[toY][toX] == EmptySquare)) {
10432         board[fromY][fromX] = EmptySquare;
10433         board[toY][toX] = BlackPawn;
10434         if(oldEP & EP_BEROLIN_A) {
10435                 captured = board[fromY][fromX-1];
10436                 board[fromY][fromX-1] = EmptySquare;
10437         }else{  captured = board[fromY][fromX+1];
10438                 board[fromY][fromX+1] = EmptySquare;
10439         }
10440     } else {
10441         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10442         board[fromY][fromX] = EmptySquare;
10443         board[toY][toX] = piece;
10444     }
10445   }
10446
10447     if (gameInfo.holdingsWidth != 0) {
10448
10449       /* !!A lot more code needs to be written to support holdings  */
10450       /* [HGM] OK, so I have written it. Holdings are stored in the */
10451       /* penultimate board files, so they are automaticlly stored   */
10452       /* in the game history.                                       */
10453       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10454                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10455         /* Delete from holdings, by decreasing count */
10456         /* and erasing image if necessary            */
10457         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10458         if(p < (int) BlackPawn) { /* white drop */
10459              p -= (int)WhitePawn;
10460                  p = PieceToNumber((ChessSquare)p);
10461              if(p >= gameInfo.holdingsSize) p = 0;
10462              if(--board[p][BOARD_WIDTH-2] <= 0)
10463                   board[p][BOARD_WIDTH-1] = EmptySquare;
10464              if((int)board[p][BOARD_WIDTH-2] < 0)
10465                         board[p][BOARD_WIDTH-2] = 0;
10466         } else {                  /* black drop */
10467              p -= (int)BlackPawn;
10468                  p = PieceToNumber((ChessSquare)p);
10469              if(p >= gameInfo.holdingsSize) p = 0;
10470              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10471                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10472              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10473                         board[BOARD_HEIGHT-1-p][1] = 0;
10474         }
10475       }
10476       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10477           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10478         /* [HGM] holdings: Add to holdings, if holdings exist */
10479         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10480                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10481                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10482         }
10483         p = (int) captured;
10484         if (p >= (int) BlackPawn) {
10485           p -= (int)BlackPawn;
10486           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10487                   /* Restore shogi-promoted piece to its original  first */
10488                   captured = (ChessSquare) (DEMOTED(captured));
10489                   p = DEMOTED(p);
10490           }
10491           p = PieceToNumber((ChessSquare)p);
10492           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10493           board[p][BOARD_WIDTH-2]++;
10494           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10495         } else {
10496           p -= (int)WhitePawn;
10497           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10498                   captured = (ChessSquare) (DEMOTED(captured));
10499                   p = DEMOTED(p);
10500           }
10501           p = PieceToNumber((ChessSquare)p);
10502           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10503           board[BOARD_HEIGHT-1-p][1]++;
10504           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10505         }
10506       }
10507     } else if (gameInfo.variant == VariantAtomic) {
10508       if (captured != EmptySquare) {
10509         int y, x;
10510         for (y = toY-1; y <= toY+1; y++) {
10511           for (x = toX-1; x <= toX+1; x++) {
10512             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10513                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10514               board[y][x] = EmptySquare;
10515             }
10516           }
10517         }
10518         board[toY][toX] = EmptySquare;
10519       }
10520     }
10521
10522     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10523         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10524     } else
10525     if(promoChar == '+') {
10526         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10527         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10528         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10529           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10530     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10531         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10532         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10533            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10534         board[toY][toX] = newPiece;
10535     }
10536     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10537                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10538         // [HGM] superchess: take promotion piece out of holdings
10539         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10540         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10541             if(!--board[k][BOARD_WIDTH-2])
10542                 board[k][BOARD_WIDTH-1] = EmptySquare;
10543         } else {
10544             if(!--board[BOARD_HEIGHT-1-k][1])
10545                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10546         }
10547     }
10548 }
10549
10550 /* Updates forwardMostMove */
10551 void
10552 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10553 {
10554     int x = toX, y = toY;
10555     char *s = parseList[forwardMostMove];
10556     ChessSquare p = boards[forwardMostMove][toY][toX];
10557 //    forwardMostMove++; // [HGM] bare: moved downstream
10558
10559     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10560     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10561     (void) CoordsToAlgebraic(boards[forwardMostMove],
10562                              PosFlags(forwardMostMove),
10563                              fromY, fromX, y, x, (killX < 0)*promoChar,
10564                              s);
10565     if(kill2X >= 0 && kill2Y >= 0)
10566         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10567     if(killX >= 0 && killY >= 0)
10568         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10569                                            toX + AAA, toY + ONE - '0', promoChar);
10570
10571     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10572         int timeLeft; static int lastLoadFlag=0; int king, piece;
10573         piece = boards[forwardMostMove][fromY][fromX];
10574         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10575         if(gameInfo.variant == VariantKnightmate)
10576             king += (int) WhiteUnicorn - (int) WhiteKing;
10577         if(forwardMostMove == 0) {
10578             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10579                 fprintf(serverMoves, "%s;", UserName());
10580             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10581                 fprintf(serverMoves, "%s;", second.tidy);
10582             fprintf(serverMoves, "%s;", first.tidy);
10583             if(gameMode == MachinePlaysWhite)
10584                 fprintf(serverMoves, "%s;", UserName());
10585             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10586                 fprintf(serverMoves, "%s;", second.tidy);
10587         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10588         lastLoadFlag = loadFlag;
10589         // print base move
10590         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10591         // print castling suffix
10592         if( toY == fromY && piece == king ) {
10593             if(toX-fromX > 1)
10594                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10595             if(fromX-toX >1)
10596                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10597         }
10598         // e.p. suffix
10599         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10600              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10601              boards[forwardMostMove][toY][toX] == EmptySquare
10602              && fromX != toX && fromY != toY)
10603                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10604         // promotion suffix
10605         if(promoChar != NULLCHAR) {
10606             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10607                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10608                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10609             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10610         }
10611         if(!loadFlag) {
10612                 char buf[MOVE_LEN*2], *p; int len;
10613             fprintf(serverMoves, "/%d/%d",
10614                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10615             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10616             else                      timeLeft = blackTimeRemaining/1000;
10617             fprintf(serverMoves, "/%d", timeLeft);
10618                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10619                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10620                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10621                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10622             fprintf(serverMoves, "/%s", buf);
10623         }
10624         fflush(serverMoves);
10625     }
10626
10627     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10628         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10629       return;
10630     }
10631     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10632     if (commentList[forwardMostMove+1] != NULL) {
10633         free(commentList[forwardMostMove+1]);
10634         commentList[forwardMostMove+1] = NULL;
10635     }
10636     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10637     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10638     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10639     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10640     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10641     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10642     adjustedClock = FALSE;
10643     gameInfo.result = GameUnfinished;
10644     if (gameInfo.resultDetails != NULL) {
10645         free(gameInfo.resultDetails);
10646         gameInfo.resultDetails = NULL;
10647     }
10648     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10649                               moveList[forwardMostMove - 1]);
10650     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10651       case MT_NONE:
10652       case MT_STALEMATE:
10653       default:
10654         break;
10655       case MT_CHECK:
10656         if(!IS_SHOGI(gameInfo.variant))
10657             strcat(parseList[forwardMostMove - 1], "+");
10658         break;
10659       case MT_CHECKMATE:
10660       case MT_STAINMATE:
10661         strcat(parseList[forwardMostMove - 1], "#");
10662         break;
10663     }
10664 }
10665
10666 /* Updates currentMove if not pausing */
10667 void
10668 ShowMove (int fromX, int fromY, int toX, int toY)
10669 {
10670     int instant = (gameMode == PlayFromGameFile) ?
10671         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10672     if(appData.noGUI) return;
10673     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10674         if (!instant) {
10675             if (forwardMostMove == currentMove + 1) {
10676                 AnimateMove(boards[forwardMostMove - 1],
10677                             fromX, fromY, toX, toY);
10678             }
10679         }
10680         currentMove = forwardMostMove;
10681     }
10682
10683     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10684
10685     if (instant) return;
10686
10687     DisplayMove(currentMove - 1);
10688     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10689             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10690                 SetHighlights(fromX, fromY, toX, toY);
10691             }
10692     }
10693     DrawPosition(FALSE, boards[currentMove]);
10694     DisplayBothClocks();
10695     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10696 }
10697
10698 void
10699 SendEgtPath (ChessProgramState *cps)
10700 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10701         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10702
10703         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10704
10705         while(*p) {
10706             char c, *q = name+1, *r, *s;
10707
10708             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10709             while(*p && *p != ',') *q++ = *p++;
10710             *q++ = ':'; *q = 0;
10711             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10712                 strcmp(name, ",nalimov:") == 0 ) {
10713                 // take nalimov path from the menu-changeable option first, if it is defined
10714               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10715                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10716             } else
10717             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10718                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10719                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10720                 s = r = StrStr(s, ":") + 1; // beginning of path info
10721                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10722                 c = *r; *r = 0;             // temporarily null-terminate path info
10723                     *--q = 0;               // strip of trailig ':' from name
10724                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10725                 *r = c;
10726                 SendToProgram(buf,cps);     // send egtbpath command for this format
10727             }
10728             if(*p == ',') p++; // read away comma to position for next format name
10729         }
10730 }
10731
10732 static int
10733 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10734 {
10735       int width = 8, height = 8, holdings = 0;             // most common sizes
10736       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10737       // correct the deviations default for each variant
10738       if( v == VariantXiangqi ) width = 9,  height = 10;
10739       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10740       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10741       if( v == VariantCapablanca || v == VariantCapaRandom ||
10742           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10743                                 width = 10;
10744       if( v == VariantCourier ) width = 12;
10745       if( v == VariantSuper )                            holdings = 8;
10746       if( v == VariantGreat )   width = 10,              holdings = 8;
10747       if( v == VariantSChess )                           holdings = 7;
10748       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10749       if( v == VariantChuChess) width = 10, height = 10;
10750       if( v == VariantChu )     width = 12, height = 12;
10751       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10752              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10753              holdingsSize >= 0 && holdingsSize != holdings;
10754 }
10755
10756 char variantError[MSG_SIZ];
10757
10758 char *
10759 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10760 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10761       char *p, *variant = VariantName(v);
10762       static char b[MSG_SIZ];
10763       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10764            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10765                                                holdingsSize, variant); // cook up sized variant name
10766            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10767            if(StrStr(list, b) == NULL) {
10768                // specific sized variant not known, check if general sizing allowed
10769                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10770                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10771                             boardWidth, boardHeight, holdingsSize, engine);
10772                    return NULL;
10773                }
10774                /* [HGM] here we really should compare with the maximum supported board size */
10775            }
10776       } else snprintf(b, MSG_SIZ,"%s", variant);
10777       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10778       p = StrStr(list, b);
10779       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10780       if(p == NULL) {
10781           // occurs not at all in list, or only as sub-string
10782           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10783           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10784               int l = strlen(variantError);
10785               char *q;
10786               while(p != list && p[-1] != ',') p--;
10787               q = strchr(p, ',');
10788               if(q) *q = NULLCHAR;
10789               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10790               if(q) *q= ',';
10791           }
10792           return NULL;
10793       }
10794       return b;
10795 }
10796
10797 void
10798 InitChessProgram (ChessProgramState *cps, int setup)
10799 /* setup needed to setup FRC opening position */
10800 {
10801     char buf[MSG_SIZ], *b;
10802     if (appData.noChessProgram) return;
10803     hintRequested = FALSE;
10804     bookRequested = FALSE;
10805
10806     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10807     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10808     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10809     if(cps->memSize) { /* [HGM] memory */
10810       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10811         SendToProgram(buf, cps);
10812     }
10813     SendEgtPath(cps); /* [HGM] EGT */
10814     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10815       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10816         SendToProgram(buf, cps);
10817     }
10818
10819     setboardSpoiledMachineBlack = FALSE;
10820     SendToProgram(cps->initString, cps);
10821     if (gameInfo.variant != VariantNormal &&
10822         gameInfo.variant != VariantLoadable
10823         /* [HGM] also send variant if board size non-standard */
10824         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10825
10826       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10827                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10828
10829       if (b == NULL) {
10830         VariantClass v;
10831         char c, *q = cps->variants, *p = strchr(q, ',');
10832         if(p) *p = NULLCHAR;
10833         v = StringToVariant(q);
10834         DisplayError(variantError, 0);
10835         if(v != VariantUnknown && cps == &first) {
10836             int w, h, s;
10837             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10838                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10839             ASSIGN(appData.variant, q);
10840             Reset(TRUE, FALSE);
10841         }
10842         if(p) *p = ',';
10843         return;
10844       }
10845
10846       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10847       SendToProgram(buf, cps);
10848     }
10849     currentlyInitializedVariant = gameInfo.variant;
10850
10851     /* [HGM] send opening position in FRC to first engine */
10852     if(setup) {
10853           SendToProgram("force\n", cps);
10854           SendBoard(cps, 0);
10855           /* engine is now in force mode! Set flag to wake it up after first move. */
10856           setboardSpoiledMachineBlack = 1;
10857     }
10858
10859     if (cps->sendICS) {
10860       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10861       SendToProgram(buf, cps);
10862     }
10863     cps->maybeThinking = FALSE;
10864     cps->offeredDraw = 0;
10865     if (!appData.icsActive) {
10866         SendTimeControl(cps, movesPerSession, timeControl,
10867                         timeIncrement, appData.searchDepth,
10868                         searchTime);
10869     }
10870     if (appData.showThinking
10871         // [HGM] thinking: four options require thinking output to be sent
10872         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10873                                 ) {
10874         SendToProgram("post\n", cps);
10875     }
10876     SendToProgram("hard\n", cps);
10877     if (!appData.ponderNextMove) {
10878         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10879            it without being sure what state we are in first.  "hard"
10880            is not a toggle, so that one is OK.
10881          */
10882         SendToProgram("easy\n", cps);
10883     }
10884     if (cps->usePing) {
10885       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10886       SendToProgram(buf, cps);
10887     }
10888     cps->initDone = TRUE;
10889     ClearEngineOutputPane(cps == &second);
10890 }
10891
10892
10893 void
10894 ResendOptions (ChessProgramState *cps)
10895 { // send the stored value of the options
10896   int i;
10897   char buf[MSG_SIZ];
10898   Option *opt = cps->option;
10899   for(i=0; i<cps->nrOptions; i++, opt++) {
10900       switch(opt->type) {
10901         case Spin:
10902         case Slider:
10903         case CheckBox:
10904             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10905           break;
10906         case ComboBox:
10907           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10908           break;
10909         default:
10910             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10911           break;
10912         case Button:
10913         case SaveButton:
10914           continue;
10915       }
10916       SendToProgram(buf, cps);
10917   }
10918 }
10919
10920 void
10921 StartChessProgram (ChessProgramState *cps)
10922 {
10923     char buf[MSG_SIZ];
10924     int err;
10925
10926     if (appData.noChessProgram) return;
10927     cps->initDone = FALSE;
10928
10929     if (strcmp(cps->host, "localhost") == 0) {
10930         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10931     } else if (*appData.remoteShell == NULLCHAR) {
10932         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10933     } else {
10934         if (*appData.remoteUser == NULLCHAR) {
10935           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10936                     cps->program);
10937         } else {
10938           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10939                     cps->host, appData.remoteUser, cps->program);
10940         }
10941         err = StartChildProcess(buf, "", &cps->pr);
10942     }
10943
10944     if (err != 0) {
10945       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10946         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10947         if(cps != &first) return;
10948         appData.noChessProgram = TRUE;
10949         ThawUI();
10950         SetNCPMode();
10951 //      DisplayFatalError(buf, err, 1);
10952 //      cps->pr = NoProc;
10953 //      cps->isr = NULL;
10954         return;
10955     }
10956
10957     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10958     if (cps->protocolVersion > 1) {
10959       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10960       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10961         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10962         cps->comboCnt = 0;  //                and values of combo boxes
10963       }
10964       SendToProgram(buf, cps);
10965       if(cps->reload) ResendOptions(cps);
10966     } else {
10967       SendToProgram("xboard\n", cps);
10968     }
10969 }
10970
10971 void
10972 TwoMachinesEventIfReady P((void))
10973 {
10974   static int curMess = 0;
10975   if (first.lastPing != first.lastPong) {
10976     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10977     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10978     return;
10979   }
10980   if (second.lastPing != second.lastPong) {
10981     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10982     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10983     return;
10984   }
10985   DisplayMessage("", ""); curMess = 0;
10986   TwoMachinesEvent();
10987 }
10988
10989 char *
10990 MakeName (char *template)
10991 {
10992     time_t clock;
10993     struct tm *tm;
10994     static char buf[MSG_SIZ];
10995     char *p = buf;
10996     int i;
10997
10998     clock = time((time_t *)NULL);
10999     tm = localtime(&clock);
11000
11001     while(*p++ = *template++) if(p[-1] == '%') {
11002         switch(*template++) {
11003           case 0:   *p = 0; return buf;
11004           case 'Y': i = tm->tm_year+1900; break;
11005           case 'y': i = tm->tm_year-100; break;
11006           case 'M': i = tm->tm_mon+1; break;
11007           case 'd': i = tm->tm_mday; break;
11008           case 'h': i = tm->tm_hour; break;
11009           case 'm': i = tm->tm_min; break;
11010           case 's': i = tm->tm_sec; break;
11011           default:  i = 0;
11012         }
11013         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11014     }
11015     return buf;
11016 }
11017
11018 int
11019 CountPlayers (char *p)
11020 {
11021     int n = 0;
11022     while(p = strchr(p, '\n')) p++, n++; // count participants
11023     return n;
11024 }
11025
11026 FILE *
11027 WriteTourneyFile (char *results, FILE *f)
11028 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11029     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11030     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11031         // create a file with tournament description
11032         fprintf(f, "-participants {%s}\n", appData.participants);
11033         fprintf(f, "-seedBase %d\n", appData.seedBase);
11034         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11035         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11036         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11037         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11038         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11039         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11040         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11041         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11042         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11043         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11044         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11045         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11046         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11047         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11048         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11049         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11050         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11051         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11052         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11053         fprintf(f, "-smpCores %d\n", appData.smpCores);
11054         if(searchTime > 0)
11055                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11056         else {
11057                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11058                 fprintf(f, "-tc %s\n", appData.timeControl);
11059                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11060         }
11061         fprintf(f, "-results \"%s\"\n", results);
11062     }
11063     return f;
11064 }
11065
11066 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11067
11068 void
11069 Substitute (char *participants, int expunge)
11070 {
11071     int i, changed, changes=0, nPlayers=0;
11072     char *p, *q, *r, buf[MSG_SIZ];
11073     if(participants == NULL) return;
11074     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11075     r = p = participants; q = appData.participants;
11076     while(*p && *p == *q) {
11077         if(*p == '\n') r = p+1, nPlayers++;
11078         p++; q++;
11079     }
11080     if(*p) { // difference
11081         while(*p && *p++ != '\n');
11082         while(*q && *q++ != '\n');
11083       changed = nPlayers;
11084         changes = 1 + (strcmp(p, q) != 0);
11085     }
11086     if(changes == 1) { // a single engine mnemonic was changed
11087         q = r; while(*q) nPlayers += (*q++ == '\n');
11088         p = buf; while(*r && (*p = *r++) != '\n') p++;
11089         *p = NULLCHAR;
11090         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11091         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11092         if(mnemonic[i]) { // The substitute is valid
11093             FILE *f;
11094             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11095                 flock(fileno(f), LOCK_EX);
11096                 ParseArgsFromFile(f);
11097                 fseek(f, 0, SEEK_SET);
11098                 FREE(appData.participants); appData.participants = participants;
11099                 if(expunge) { // erase results of replaced engine
11100                     int len = strlen(appData.results), w, b, dummy;
11101                     for(i=0; i<len; i++) {
11102                         Pairing(i, nPlayers, &w, &b, &dummy);
11103                         if((w == changed || b == changed) && appData.results[i] == '*') {
11104                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11105                             fclose(f);
11106                             return;
11107                         }
11108                     }
11109                     for(i=0; i<len; i++) {
11110                         Pairing(i, nPlayers, &w, &b, &dummy);
11111                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11112                     }
11113                 }
11114                 WriteTourneyFile(appData.results, f);
11115                 fclose(f); // release lock
11116                 return;
11117             }
11118         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11119     }
11120     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11121     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11122     free(participants);
11123     return;
11124 }
11125
11126 int
11127 CheckPlayers (char *participants)
11128 {
11129         int i;
11130         char buf[MSG_SIZ], *p;
11131         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11132         while(p = strchr(participants, '\n')) {
11133             *p = NULLCHAR;
11134             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11135             if(!mnemonic[i]) {
11136                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11137                 *p = '\n';
11138                 DisplayError(buf, 0);
11139                 return 1;
11140             }
11141             *p = '\n';
11142             participants = p + 1;
11143         }
11144         return 0;
11145 }
11146
11147 int
11148 CreateTourney (char *name)
11149 {
11150         FILE *f;
11151         if(matchMode && strcmp(name, appData.tourneyFile)) {
11152              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11153         }
11154         if(name[0] == NULLCHAR) {
11155             if(appData.participants[0])
11156                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11157             return 0;
11158         }
11159         f = fopen(name, "r");
11160         if(f) { // file exists
11161             ASSIGN(appData.tourneyFile, name);
11162             ParseArgsFromFile(f); // parse it
11163         } else {
11164             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11165             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11166                 DisplayError(_("Not enough participants"), 0);
11167                 return 0;
11168             }
11169             if(CheckPlayers(appData.participants)) return 0;
11170             ASSIGN(appData.tourneyFile, name);
11171             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11172             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11173         }
11174         fclose(f);
11175         appData.noChessProgram = FALSE;
11176         appData.clockMode = TRUE;
11177         SetGNUMode();
11178         return 1;
11179 }
11180
11181 int
11182 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11183 {
11184     char buf[MSG_SIZ], *p, *q;
11185     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11186     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11187     skip = !all && group[0]; // if group requested, we start in skip mode
11188     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11189         p = names; q = buf; header = 0;
11190         while(*p && *p != '\n') *q++ = *p++;
11191         *q = 0;
11192         if(*p == '\n') p++;
11193         if(buf[0] == '#') {
11194             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11195             depth++; // we must be entering a new group
11196             if(all) continue; // suppress printing group headers when complete list requested
11197             header = 1;
11198             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11199         }
11200         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11201         if(engineList[i]) free(engineList[i]);
11202         engineList[i] = strdup(buf);
11203         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11204         if(engineMnemonic[i]) free(engineMnemonic[i]);
11205         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11206             strcat(buf, " (");
11207             sscanf(q + 8, "%s", buf + strlen(buf));
11208             strcat(buf, ")");
11209         }
11210         engineMnemonic[i] = strdup(buf);
11211         i++;
11212     }
11213     engineList[i] = engineMnemonic[i] = NULL;
11214     return i;
11215 }
11216
11217 // following implemented as macro to avoid type limitations
11218 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11219
11220 void
11221 SwapEngines (int n)
11222 {   // swap settings for first engine and other engine (so far only some selected options)
11223     int h;
11224     char *p;
11225     if(n == 0) return;
11226     SWAP(directory, p)
11227     SWAP(chessProgram, p)
11228     SWAP(isUCI, h)
11229     SWAP(hasOwnBookUCI, h)
11230     SWAP(protocolVersion, h)
11231     SWAP(reuse, h)
11232     SWAP(scoreIsAbsolute, h)
11233     SWAP(timeOdds, h)
11234     SWAP(logo, p)
11235     SWAP(pgnName, p)
11236     SWAP(pvSAN, h)
11237     SWAP(engOptions, p)
11238     SWAP(engInitString, p)
11239     SWAP(computerString, p)
11240     SWAP(features, p)
11241     SWAP(fenOverride, p)
11242     SWAP(NPS, h)
11243     SWAP(accumulateTC, h)
11244     SWAP(drawDepth, h)
11245     SWAP(host, p)
11246     SWAP(pseudo, h)
11247 }
11248
11249 int
11250 GetEngineLine (char *s, int n)
11251 {
11252     int i;
11253     char buf[MSG_SIZ];
11254     extern char *icsNames;
11255     if(!s || !*s) return 0;
11256     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11257     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11258     if(!mnemonic[i]) return 0;
11259     if(n == 11) return 1; // just testing if there was a match
11260     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11261     if(n == 1) SwapEngines(n);
11262     ParseArgsFromString(buf);
11263     if(n == 1) SwapEngines(n);
11264     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11265         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11266         ParseArgsFromString(buf);
11267     }
11268     return 1;
11269 }
11270
11271 int
11272 SetPlayer (int player, char *p)
11273 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11274     int i;
11275     char buf[MSG_SIZ], *engineName;
11276     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11277     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11278     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11279     if(mnemonic[i]) {
11280         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11281         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11282         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11283         ParseArgsFromString(buf);
11284     } else { // no engine with this nickname is installed!
11285         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11286         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11287         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11288         ModeHighlight();
11289         DisplayError(buf, 0);
11290         return 0;
11291     }
11292     free(engineName);
11293     return i;
11294 }
11295
11296 char *recentEngines;
11297
11298 void
11299 RecentEngineEvent (int nr)
11300 {
11301     int n;
11302 //    SwapEngines(1); // bump first to second
11303 //    ReplaceEngine(&second, 1); // and load it there
11304     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11305     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11306     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11307         ReplaceEngine(&first, 0);
11308         FloatToFront(&appData.recentEngineList, command[n]);
11309     }
11310 }
11311
11312 int
11313 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11314 {   // determine players from game number
11315     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11316
11317     if(appData.tourneyType == 0) {
11318         roundsPerCycle = (nPlayers - 1) | 1;
11319         pairingsPerRound = nPlayers / 2;
11320     } else if(appData.tourneyType > 0) {
11321         roundsPerCycle = nPlayers - appData.tourneyType;
11322         pairingsPerRound = appData.tourneyType;
11323     }
11324     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11325     gamesPerCycle = gamesPerRound * roundsPerCycle;
11326     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11327     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11328     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11329     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11330     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11331     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11332
11333     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11334     if(appData.roundSync) *syncInterval = gamesPerRound;
11335
11336     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11337
11338     if(appData.tourneyType == 0) {
11339         if(curPairing == (nPlayers-1)/2 ) {
11340             *whitePlayer = curRound;
11341             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11342         } else {
11343             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11344             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11345             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11346             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11347         }
11348     } else if(appData.tourneyType > 1) {
11349         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11350         *whitePlayer = curRound + appData.tourneyType;
11351     } else if(appData.tourneyType > 0) {
11352         *whitePlayer = curPairing;
11353         *blackPlayer = curRound + appData.tourneyType;
11354     }
11355
11356     // take care of white/black alternation per round.
11357     // For cycles and games this is already taken care of by default, derived from matchGame!
11358     return curRound & 1;
11359 }
11360
11361 int
11362 NextTourneyGame (int nr, int *swapColors)
11363 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11364     char *p, *q;
11365     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11366     FILE *tf;
11367     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11368     tf = fopen(appData.tourneyFile, "r");
11369     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11370     ParseArgsFromFile(tf); fclose(tf);
11371     InitTimeControls(); // TC might be altered from tourney file
11372
11373     nPlayers = CountPlayers(appData.participants); // count participants
11374     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11375     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11376
11377     if(syncInterval) {
11378         p = q = appData.results;
11379         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11380         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11381             DisplayMessage(_("Waiting for other game(s)"),"");
11382             waitingForGame = TRUE;
11383             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11384             return 0;
11385         }
11386         waitingForGame = FALSE;
11387     }
11388
11389     if(appData.tourneyType < 0) {
11390         if(nr>=0 && !pairingReceived) {
11391             char buf[1<<16];
11392             if(pairing.pr == NoProc) {
11393                 if(!appData.pairingEngine[0]) {
11394                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11395                     return 0;
11396                 }
11397                 StartChessProgram(&pairing); // starts the pairing engine
11398             }
11399             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11400             SendToProgram(buf, &pairing);
11401             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11402             SendToProgram(buf, &pairing);
11403             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11404         }
11405         pairingReceived = 0;                              // ... so we continue here
11406         *swapColors = 0;
11407         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11408         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11409         matchGame = 1; roundNr = nr / syncInterval + 1;
11410     }
11411
11412     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11413
11414     // redefine engines, engine dir, etc.
11415     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11416     if(first.pr == NoProc) {
11417       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11418       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11419     }
11420     if(second.pr == NoProc) {
11421       SwapEngines(1);
11422       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11423       SwapEngines(1);         // and make that valid for second engine by swapping
11424       InitEngine(&second, 1);
11425     }
11426     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11427     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11428     return OK;
11429 }
11430
11431 void
11432 NextMatchGame ()
11433 {   // performs game initialization that does not invoke engines, and then tries to start the game
11434     int res, firstWhite, swapColors = 0;
11435     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11436     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
11437         char buf[MSG_SIZ];
11438         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11439         if(strcmp(buf, currentDebugFile)) { // name has changed
11440             FILE *f = fopen(buf, "w");
11441             if(f) { // if opening the new file failed, just keep using the old one
11442                 ASSIGN(currentDebugFile, buf);
11443                 fclose(debugFP);
11444                 debugFP = f;
11445             }
11446             if(appData.serverFileName) {
11447                 if(serverFP) fclose(serverFP);
11448                 serverFP = fopen(appData.serverFileName, "w");
11449                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11450                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11451             }
11452         }
11453     }
11454     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11455     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11456     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11457     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11458     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11459     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11460     Reset(FALSE, first.pr != NoProc);
11461     res = LoadGameOrPosition(matchGame); // setup game
11462     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11463     if(!res) return; // abort when bad game/pos file
11464     if(appData.epd) {// in EPD mode we make sure first engine is to move
11465         firstWhite = !(forwardMostMove & 1);
11466         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11467         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11468     }
11469     TwoMachinesEvent();
11470 }
11471
11472 void
11473 UserAdjudicationEvent (int result)
11474 {
11475     ChessMove gameResult = GameIsDrawn;
11476
11477     if( result > 0 ) {
11478         gameResult = WhiteWins;
11479     }
11480     else if( result < 0 ) {
11481         gameResult = BlackWins;
11482     }
11483
11484     if( gameMode == TwoMachinesPlay ) {
11485         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11486     }
11487 }
11488
11489
11490 // [HGM] save: calculate checksum of game to make games easily identifiable
11491 int
11492 StringCheckSum (char *s)
11493 {
11494         int i = 0;
11495         if(s==NULL) return 0;
11496         while(*s) i = i*259 + *s++;
11497         return i;
11498 }
11499
11500 int
11501 GameCheckSum ()
11502 {
11503         int i, sum=0;
11504         for(i=backwardMostMove; i<forwardMostMove; i++) {
11505                 sum += pvInfoList[i].depth;
11506                 sum += StringCheckSum(parseList[i]);
11507                 sum += StringCheckSum(commentList[i]);
11508                 sum *= 261;
11509         }
11510         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11511         return sum + StringCheckSum(commentList[i]);
11512 } // end of save patch
11513
11514 void
11515 GameEnds (ChessMove result, char *resultDetails, int whosays)
11516 {
11517     GameMode nextGameMode;
11518     int isIcsGame;
11519     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11520
11521     if(endingGame) return; /* [HGM] crash: forbid recursion */
11522     endingGame = 1;
11523     if(twoBoards) { // [HGM] dual: switch back to one board
11524         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11525         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11526     }
11527     if (appData.debugMode) {
11528       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11529               result, resultDetails ? resultDetails : "(null)", whosays);
11530     }
11531
11532     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11533
11534     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11535
11536     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11537         /* If we are playing on ICS, the server decides when the
11538            game is over, but the engine can offer to draw, claim
11539            a draw, or resign.
11540          */
11541 #if ZIPPY
11542         if (appData.zippyPlay && first.initDone) {
11543             if (result == GameIsDrawn) {
11544                 /* In case draw still needs to be claimed */
11545                 SendToICS(ics_prefix);
11546                 SendToICS("draw\n");
11547             } else if (StrCaseStr(resultDetails, "resign")) {
11548                 SendToICS(ics_prefix);
11549                 SendToICS("resign\n");
11550             }
11551         }
11552 #endif
11553         endingGame = 0; /* [HGM] crash */
11554         return;
11555     }
11556
11557     /* If we're loading the game from a file, stop */
11558     if (whosays == GE_FILE) {
11559       (void) StopLoadGameTimer();
11560       gameFileFP = NULL;
11561     }
11562
11563     /* Cancel draw offers */
11564     first.offeredDraw = second.offeredDraw = 0;
11565
11566     /* If this is an ICS game, only ICS can really say it's done;
11567        if not, anyone can. */
11568     isIcsGame = (gameMode == IcsPlayingWhite ||
11569                  gameMode == IcsPlayingBlack ||
11570                  gameMode == IcsObserving    ||
11571                  gameMode == IcsExamining);
11572
11573     if (!isIcsGame || whosays == GE_ICS) {
11574         /* OK -- not an ICS game, or ICS said it was done */
11575         StopClocks();
11576         if (!isIcsGame && !appData.noChessProgram)
11577           SetUserThinkingEnables();
11578
11579         /* [HGM] if a machine claims the game end we verify this claim */
11580         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11581             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11582                 char claimer;
11583                 ChessMove trueResult = (ChessMove) -1;
11584
11585                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11586                                             first.twoMachinesColor[0] :
11587                                             second.twoMachinesColor[0] ;
11588
11589                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11590                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11591                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11592                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11593                 } else
11594                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11595                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11596                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11597                 } else
11598                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11599                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11600                 }
11601
11602                 // now verify win claims, but not in drop games, as we don't understand those yet
11603                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11604                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11605                     (result == WhiteWins && claimer == 'w' ||
11606                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11607                       if (appData.debugMode) {
11608                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11609                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11610                       }
11611                       if(result != trueResult) {
11612                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11613                               result = claimer == 'w' ? BlackWins : WhiteWins;
11614                               resultDetails = buf;
11615                       }
11616                 } else
11617                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11618                     && (forwardMostMove <= backwardMostMove ||
11619                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11620                         (claimer=='b')==(forwardMostMove&1))
11621                                                                                   ) {
11622                       /* [HGM] verify: draws that were not flagged are false claims */
11623                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11624                       result = claimer == 'w' ? BlackWins : WhiteWins;
11625                       resultDetails = buf;
11626                 }
11627                 /* (Claiming a loss is accepted no questions asked!) */
11628             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11629                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11630                 result = GameUnfinished;
11631                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11632             }
11633             /* [HGM] bare: don't allow bare King to win */
11634             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11635                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11636                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11637                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11638                && result != GameIsDrawn)
11639             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11640                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11641                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11642                         if(p >= 0 && p <= (int)WhiteKing) k++;
11643                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11644                 }
11645                 if (appData.debugMode) {
11646                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11647                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11648                 }
11649                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11650                         result = GameIsDrawn;
11651                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11652                         resultDetails = buf;
11653                 }
11654             }
11655         }
11656
11657
11658         if(serverMoves != NULL && !loadFlag) { char c = '=';
11659             if(result==WhiteWins) c = '+';
11660             if(result==BlackWins) c = '-';
11661             if(resultDetails != NULL)
11662                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11663         }
11664         if (resultDetails != NULL) {
11665             gameInfo.result = result;
11666             gameInfo.resultDetails = StrSave(resultDetails);
11667
11668             /* display last move only if game was not loaded from file */
11669             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11670                 DisplayMove(currentMove - 1);
11671
11672             if (forwardMostMove != 0) {
11673                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11674                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11675                                                                 ) {
11676                     if (*appData.saveGameFile != NULLCHAR) {
11677                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11678                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11679                         else
11680                         SaveGameToFile(appData.saveGameFile, TRUE);
11681                     } else if (appData.autoSaveGames) {
11682                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11683                     }
11684                     if (*appData.savePositionFile != NULLCHAR) {
11685                         SavePositionToFile(appData.savePositionFile);
11686                     }
11687                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11688                 }
11689             }
11690
11691             /* Tell program how game ended in case it is learning */
11692             /* [HGM] Moved this to after saving the PGN, just in case */
11693             /* engine died and we got here through time loss. In that */
11694             /* case we will get a fatal error writing the pipe, which */
11695             /* would otherwise lose us the PGN.                       */
11696             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11697             /* output during GameEnds should never be fatal anymore   */
11698             if (gameMode == MachinePlaysWhite ||
11699                 gameMode == MachinePlaysBlack ||
11700                 gameMode == TwoMachinesPlay ||
11701                 gameMode == IcsPlayingWhite ||
11702                 gameMode == IcsPlayingBlack ||
11703                 gameMode == BeginningOfGame) {
11704                 char buf[MSG_SIZ];
11705                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11706                         resultDetails);
11707                 if (first.pr != NoProc) {
11708                     SendToProgram(buf, &first);
11709                 }
11710                 if (second.pr != NoProc &&
11711                     gameMode == TwoMachinesPlay) {
11712                     SendToProgram(buf, &second);
11713                 }
11714             }
11715         }
11716
11717         if (appData.icsActive) {
11718             if (appData.quietPlay &&
11719                 (gameMode == IcsPlayingWhite ||
11720                  gameMode == IcsPlayingBlack)) {
11721                 SendToICS(ics_prefix);
11722                 SendToICS("set shout 1\n");
11723             }
11724             nextGameMode = IcsIdle;
11725             ics_user_moved = FALSE;
11726             /* clean up premove.  It's ugly when the game has ended and the
11727              * premove highlights are still on the board.
11728              */
11729             if (gotPremove) {
11730               gotPremove = FALSE;
11731               ClearPremoveHighlights();
11732               DrawPosition(FALSE, boards[currentMove]);
11733             }
11734             if (whosays == GE_ICS) {
11735                 switch (result) {
11736                 case WhiteWins:
11737                     if (gameMode == IcsPlayingWhite)
11738                         PlayIcsWinSound();
11739                     else if(gameMode == IcsPlayingBlack)
11740                         PlayIcsLossSound();
11741                     break;
11742                 case BlackWins:
11743                     if (gameMode == IcsPlayingBlack)
11744                         PlayIcsWinSound();
11745                     else if(gameMode == IcsPlayingWhite)
11746                         PlayIcsLossSound();
11747                     break;
11748                 case GameIsDrawn:
11749                     PlayIcsDrawSound();
11750                     break;
11751                 default:
11752                     PlayIcsUnfinishedSound();
11753                 }
11754             }
11755             if(appData.quitNext) { ExitEvent(0); return; }
11756         } else if (gameMode == EditGame ||
11757                    gameMode == PlayFromGameFile ||
11758                    gameMode == AnalyzeMode ||
11759                    gameMode == AnalyzeFile) {
11760             nextGameMode = gameMode;
11761         } else {
11762             nextGameMode = EndOfGame;
11763         }
11764         pausing = FALSE;
11765         ModeHighlight();
11766     } else {
11767         nextGameMode = gameMode;
11768     }
11769
11770     if (appData.noChessProgram) {
11771         gameMode = nextGameMode;
11772         ModeHighlight();
11773         endingGame = 0; /* [HGM] crash */
11774         return;
11775     }
11776
11777     if (first.reuse) {
11778         /* Put first chess program into idle state */
11779         if (first.pr != NoProc &&
11780             (gameMode == MachinePlaysWhite ||
11781              gameMode == MachinePlaysBlack ||
11782              gameMode == TwoMachinesPlay ||
11783              gameMode == IcsPlayingWhite ||
11784              gameMode == IcsPlayingBlack ||
11785              gameMode == BeginningOfGame)) {
11786             SendToProgram("force\n", &first);
11787             if (first.usePing) {
11788               char buf[MSG_SIZ];
11789               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11790               SendToProgram(buf, &first);
11791             }
11792         }
11793     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11794         /* Kill off first chess program */
11795         if (first.isr != NULL)
11796           RemoveInputSource(first.isr);
11797         first.isr = NULL;
11798
11799         if (first.pr != NoProc) {
11800             ExitAnalyzeMode();
11801             DoSleep( appData.delayBeforeQuit );
11802             SendToProgram("quit\n", &first);
11803             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11804             first.reload = TRUE;
11805         }
11806         first.pr = NoProc;
11807     }
11808     if (second.reuse) {
11809         /* Put second chess program into idle state */
11810         if (second.pr != NoProc &&
11811             gameMode == TwoMachinesPlay) {
11812             SendToProgram("force\n", &second);
11813             if (second.usePing) {
11814               char buf[MSG_SIZ];
11815               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11816               SendToProgram(buf, &second);
11817             }
11818         }
11819     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11820         /* Kill off second chess program */
11821         if (second.isr != NULL)
11822           RemoveInputSource(second.isr);
11823         second.isr = NULL;
11824
11825         if (second.pr != NoProc) {
11826             DoSleep( appData.delayBeforeQuit );
11827             SendToProgram("quit\n", &second);
11828             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11829             second.reload = TRUE;
11830         }
11831         second.pr = NoProc;
11832     }
11833
11834     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11835         char resChar = '=';
11836         switch (result) {
11837         case WhiteWins:
11838           resChar = '+';
11839           if (first.twoMachinesColor[0] == 'w') {
11840             first.matchWins++;
11841           } else {
11842             second.matchWins++;
11843           }
11844           break;
11845         case BlackWins:
11846           resChar = '-';
11847           if (first.twoMachinesColor[0] == 'b') {
11848             first.matchWins++;
11849           } else {
11850             second.matchWins++;
11851           }
11852           break;
11853         case GameUnfinished:
11854           resChar = ' ';
11855         default:
11856           break;
11857         }
11858
11859         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11860         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11861             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11862             ReserveGame(nextGame, resChar); // sets nextGame
11863             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11864             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11865         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11866
11867         if (nextGame <= appData.matchGames && !abortMatch) {
11868             gameMode = nextGameMode;
11869             matchGame = nextGame; // this will be overruled in tourney mode!
11870             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11871             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11872             endingGame = 0; /* [HGM] crash */
11873             return;
11874         } else {
11875             gameMode = nextGameMode;
11876             if(appData.epd) {
11877                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11878                 OutputKibitz(2, buf);
11879                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11880                 OutputKibitz(2, buf);
11881                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11882                 if(second.matchWins) OutputKibitz(2, buf);
11883                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11884                 OutputKibitz(2, buf);
11885             }
11886             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11887                      first.tidy, second.tidy,
11888                      first.matchWins, second.matchWins,
11889                      appData.matchGames - (first.matchWins + second.matchWins));
11890             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11891             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11892             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11893             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11894                 first.twoMachinesColor = "black\n";
11895                 second.twoMachinesColor = "white\n";
11896             } else {
11897                 first.twoMachinesColor = "white\n";
11898                 second.twoMachinesColor = "black\n";
11899             }
11900         }
11901     }
11902     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11903         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11904       ExitAnalyzeMode();
11905     gameMode = nextGameMode;
11906     ModeHighlight();
11907     endingGame = 0;  /* [HGM] crash */
11908     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11909         if(matchMode == TRUE) { // match through command line: exit with or without popup
11910             if(ranking) {
11911                 ToNrEvent(forwardMostMove);
11912                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11913                 else ExitEvent(0);
11914             } else DisplayFatalError(buf, 0, 0);
11915         } else { // match through menu; just stop, with or without popup
11916             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11917             ModeHighlight();
11918             if(ranking){
11919                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11920             } else DisplayNote(buf);
11921       }
11922       if(ranking) free(ranking);
11923     }
11924 }
11925
11926 /* Assumes program was just initialized (initString sent).
11927    Leaves program in force mode. */
11928 void
11929 FeedMovesToProgram (ChessProgramState *cps, int upto)
11930 {
11931     int i;
11932
11933     if (appData.debugMode)
11934       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11935               startedFromSetupPosition ? "position and " : "",
11936               backwardMostMove, upto, cps->which);
11937     if(currentlyInitializedVariant != gameInfo.variant) {
11938       char buf[MSG_SIZ];
11939         // [HGM] variantswitch: make engine aware of new variant
11940         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11941                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11942                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11943         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11944         SendToProgram(buf, cps);
11945         currentlyInitializedVariant = gameInfo.variant;
11946     }
11947     SendToProgram("force\n", cps);
11948     if (startedFromSetupPosition) {
11949         SendBoard(cps, backwardMostMove);
11950     if (appData.debugMode) {
11951         fprintf(debugFP, "feedMoves\n");
11952     }
11953     }
11954     for (i = backwardMostMove; i < upto; i++) {
11955         SendMoveToProgram(i, cps);
11956     }
11957 }
11958
11959
11960 int
11961 ResurrectChessProgram ()
11962 {
11963      /* The chess program may have exited.
11964         If so, restart it and feed it all the moves made so far. */
11965     static int doInit = 0;
11966
11967     if (appData.noChessProgram) return 1;
11968
11969     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11970         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11971         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11972         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11973     } else {
11974         if (first.pr != NoProc) return 1;
11975         StartChessProgram(&first);
11976     }
11977     InitChessProgram(&first, FALSE);
11978     FeedMovesToProgram(&first, currentMove);
11979
11980     if (!first.sendTime) {
11981         /* can't tell gnuchess what its clock should read,
11982            so we bow to its notion. */
11983         ResetClocks();
11984         timeRemaining[0][currentMove] = whiteTimeRemaining;
11985         timeRemaining[1][currentMove] = blackTimeRemaining;
11986     }
11987
11988     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11989                 appData.icsEngineAnalyze) && first.analysisSupport) {
11990       SendToProgram("analyze\n", &first);
11991       first.analyzing = TRUE;
11992     }
11993     return 1;
11994 }
11995
11996 /*
11997  * Button procedures
11998  */
11999 void
12000 Reset (int redraw, int init)
12001 {
12002     int i;
12003
12004     if (appData.debugMode) {
12005         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12006                 redraw, init, gameMode);
12007     }
12008     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12009     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12010     CleanupTail(); // [HGM] vari: delete any stored variations
12011     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12012     pausing = pauseExamInvalid = FALSE;
12013     startedFromSetupPosition = blackPlaysFirst = FALSE;
12014     firstMove = TRUE;
12015     whiteFlag = blackFlag = FALSE;
12016     userOfferedDraw = FALSE;
12017     hintRequested = bookRequested = FALSE;
12018     first.maybeThinking = FALSE;
12019     second.maybeThinking = FALSE;
12020     first.bookSuspend = FALSE; // [HGM] book
12021     second.bookSuspend = FALSE;
12022     thinkOutput[0] = NULLCHAR;
12023     lastHint[0] = NULLCHAR;
12024     ClearGameInfo(&gameInfo);
12025     gameInfo.variant = StringToVariant(appData.variant);
12026     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12027         gameInfo.variant = VariantUnknown;
12028         strncpy(engineVariant, appData.variant, MSG_SIZ);
12029     }
12030     ics_user_moved = ics_clock_paused = FALSE;
12031     ics_getting_history = H_FALSE;
12032     ics_gamenum = -1;
12033     white_holding[0] = black_holding[0] = NULLCHAR;
12034     ClearProgramStats();
12035     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12036
12037     ResetFrontEnd();
12038     ClearHighlights();
12039     flipView = appData.flipView;
12040     ClearPremoveHighlights();
12041     gotPremove = FALSE;
12042     alarmSounded = FALSE;
12043     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12044
12045     GameEnds(EndOfFile, NULL, GE_PLAYER);
12046     if(appData.serverMovesName != NULL) {
12047         /* [HGM] prepare to make moves file for broadcasting */
12048         clock_t t = clock();
12049         if(serverMoves != NULL) fclose(serverMoves);
12050         serverMoves = fopen(appData.serverMovesName, "r");
12051         if(serverMoves != NULL) {
12052             fclose(serverMoves);
12053             /* delay 15 sec before overwriting, so all clients can see end */
12054             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12055         }
12056         serverMoves = fopen(appData.serverMovesName, "w");
12057     }
12058
12059     ExitAnalyzeMode();
12060     gameMode = BeginningOfGame;
12061     ModeHighlight();
12062     if(appData.icsActive) gameInfo.variant = VariantNormal;
12063     currentMove = forwardMostMove = backwardMostMove = 0;
12064     MarkTargetSquares(1);
12065     InitPosition(redraw);
12066     for (i = 0; i < MAX_MOVES; i++) {
12067         if (commentList[i] != NULL) {
12068             free(commentList[i]);
12069             commentList[i] = NULL;
12070         }
12071     }
12072     ResetClocks();
12073     timeRemaining[0][0] = whiteTimeRemaining;
12074     timeRemaining[1][0] = blackTimeRemaining;
12075
12076     if (first.pr == NoProc) {
12077         StartChessProgram(&first);
12078     }
12079     if (init) {
12080             InitChessProgram(&first, startedFromSetupPosition);
12081     }
12082     DisplayTitle("");
12083     DisplayMessage("", "");
12084     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12085     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12086     ClearMap();        // [HGM] exclude: invalidate map
12087 }
12088
12089 void
12090 AutoPlayGameLoop ()
12091 {
12092     for (;;) {
12093         if (!AutoPlayOneMove())
12094           return;
12095         if (matchMode || appData.timeDelay == 0)
12096           continue;
12097         if (appData.timeDelay < 0)
12098           return;
12099         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12100         break;
12101     }
12102 }
12103
12104 void
12105 AnalyzeNextGame()
12106 {
12107     ReloadGame(1); // next game
12108 }
12109
12110 int
12111 AutoPlayOneMove ()
12112 {
12113     int fromX, fromY, toX, toY;
12114
12115     if (appData.debugMode) {
12116       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12117     }
12118
12119     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12120       return FALSE;
12121
12122     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12123       pvInfoList[currentMove].depth = programStats.depth;
12124       pvInfoList[currentMove].score = programStats.score;
12125       pvInfoList[currentMove].time  = 0;
12126       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12127       else { // append analysis of final position as comment
12128         char buf[MSG_SIZ];
12129         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12130         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12131       }
12132       programStats.depth = 0;
12133     }
12134
12135     if (currentMove >= forwardMostMove) {
12136       if(gameMode == AnalyzeFile) {
12137           if(appData.loadGameIndex == -1) {
12138             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12139           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12140           } else {
12141           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12142         }
12143       }
12144 //      gameMode = EndOfGame;
12145 //      ModeHighlight();
12146
12147       /* [AS] Clear current move marker at the end of a game */
12148       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12149
12150       return FALSE;
12151     }
12152
12153     toX = moveList[currentMove][2] - AAA;
12154     toY = moveList[currentMove][3] - ONE;
12155
12156     if (moveList[currentMove][1] == '@') {
12157         if (appData.highlightLastMove) {
12158             SetHighlights(-1, -1, toX, toY);
12159         }
12160     } else {
12161         fromX = moveList[currentMove][0] - AAA;
12162         fromY = moveList[currentMove][1] - ONE;
12163
12164         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12165
12166         if(moveList[currentMove][4] == ';') { // multi-leg
12167             killX = moveList[currentMove][5] - AAA;
12168             killY = moveList[currentMove][6] - ONE;
12169         }
12170         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12171         killX = killY = -1;
12172
12173         if (appData.highlightLastMove) {
12174             SetHighlights(fromX, fromY, toX, toY);
12175         }
12176     }
12177     DisplayMove(currentMove);
12178     SendMoveToProgram(currentMove++, &first);
12179     DisplayBothClocks();
12180     DrawPosition(FALSE, boards[currentMove]);
12181     // [HGM] PV info: always display, routine tests if empty
12182     DisplayComment(currentMove - 1, commentList[currentMove]);
12183     return TRUE;
12184 }
12185
12186
12187 int
12188 LoadGameOneMove (ChessMove readAhead)
12189 {
12190     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12191     char promoChar = NULLCHAR;
12192     ChessMove moveType;
12193     char move[MSG_SIZ];
12194     char *p, *q;
12195
12196     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12197         gameMode != AnalyzeMode && gameMode != Training) {
12198         gameFileFP = NULL;
12199         return FALSE;
12200     }
12201
12202     yyboardindex = forwardMostMove;
12203     if (readAhead != EndOfFile) {
12204       moveType = readAhead;
12205     } else {
12206       if (gameFileFP == NULL)
12207           return FALSE;
12208       moveType = (ChessMove) Myylex();
12209     }
12210
12211     done = FALSE;
12212     switch (moveType) {
12213       case Comment:
12214         if (appData.debugMode)
12215           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12216         p = yy_text;
12217
12218         /* append the comment but don't display it */
12219         AppendComment(currentMove, p, FALSE);
12220         return TRUE;
12221
12222       case WhiteCapturesEnPassant:
12223       case BlackCapturesEnPassant:
12224       case WhitePromotion:
12225       case BlackPromotion:
12226       case WhiteNonPromotion:
12227       case BlackNonPromotion:
12228       case NormalMove:
12229       case FirstLeg:
12230       case WhiteKingSideCastle:
12231       case WhiteQueenSideCastle:
12232       case BlackKingSideCastle:
12233       case BlackQueenSideCastle:
12234       case WhiteKingSideCastleWild:
12235       case WhiteQueenSideCastleWild:
12236       case BlackKingSideCastleWild:
12237       case BlackQueenSideCastleWild:
12238       /* PUSH Fabien */
12239       case WhiteHSideCastleFR:
12240       case WhiteASideCastleFR:
12241       case BlackHSideCastleFR:
12242       case BlackASideCastleFR:
12243       /* POP Fabien */
12244         if (appData.debugMode)
12245           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12246         fromX = currentMoveString[0] - AAA;
12247         fromY = currentMoveString[1] - ONE;
12248         toX = currentMoveString[2] - AAA;
12249         toY = currentMoveString[3] - ONE;
12250         promoChar = currentMoveString[4];
12251         if(promoChar == ';') promoChar = currentMoveString[7];
12252         break;
12253
12254       case WhiteDrop:
12255       case BlackDrop:
12256         if (appData.debugMode)
12257           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12258         fromX = moveType == WhiteDrop ?
12259           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12260         (int) CharToPiece(ToLower(currentMoveString[0]));
12261         fromY = DROP_RANK;
12262         toX = currentMoveString[2] - AAA;
12263         toY = currentMoveString[3] - ONE;
12264         break;
12265
12266       case WhiteWins:
12267       case BlackWins:
12268       case GameIsDrawn:
12269       case GameUnfinished:
12270         if (appData.debugMode)
12271           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12272         p = strchr(yy_text, '{');
12273         if (p == NULL) p = strchr(yy_text, '(');
12274         if (p == NULL) {
12275             p = yy_text;
12276             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12277         } else {
12278             q = strchr(p, *p == '{' ? '}' : ')');
12279             if (q != NULL) *q = NULLCHAR;
12280             p++;
12281         }
12282         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12283         GameEnds(moveType, p, GE_FILE);
12284         done = TRUE;
12285         if (cmailMsgLoaded) {
12286             ClearHighlights();
12287             flipView = WhiteOnMove(currentMove);
12288             if (moveType == GameUnfinished) flipView = !flipView;
12289             if (appData.debugMode)
12290               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12291         }
12292         break;
12293
12294       case EndOfFile:
12295         if (appData.debugMode)
12296           fprintf(debugFP, "Parser hit end of file\n");
12297         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12298           case MT_NONE:
12299           case MT_CHECK:
12300             break;
12301           case MT_CHECKMATE:
12302           case MT_STAINMATE:
12303             if (WhiteOnMove(currentMove)) {
12304                 GameEnds(BlackWins, "Black mates", GE_FILE);
12305             } else {
12306                 GameEnds(WhiteWins, "White mates", GE_FILE);
12307             }
12308             break;
12309           case MT_STALEMATE:
12310             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12311             break;
12312         }
12313         done = TRUE;
12314         break;
12315
12316       case MoveNumberOne:
12317         if (lastLoadGameStart == GNUChessGame) {
12318             /* GNUChessGames have numbers, but they aren't move numbers */
12319             if (appData.debugMode)
12320               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12321                       yy_text, (int) moveType);
12322             return LoadGameOneMove(EndOfFile); /* tail recursion */
12323         }
12324         /* else fall thru */
12325
12326       case XBoardGame:
12327       case GNUChessGame:
12328       case PGNTag:
12329         /* Reached start of next game in file */
12330         if (appData.debugMode)
12331           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12332         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12333           case MT_NONE:
12334           case MT_CHECK:
12335             break;
12336           case MT_CHECKMATE:
12337           case MT_STAINMATE:
12338             if (WhiteOnMove(currentMove)) {
12339                 GameEnds(BlackWins, "Black mates", GE_FILE);
12340             } else {
12341                 GameEnds(WhiteWins, "White mates", GE_FILE);
12342             }
12343             break;
12344           case MT_STALEMATE:
12345             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12346             break;
12347         }
12348         done = TRUE;
12349         break;
12350
12351       case PositionDiagram:     /* should not happen; ignore */
12352       case ElapsedTime:         /* ignore */
12353       case NAG:                 /* ignore */
12354         if (appData.debugMode)
12355           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12356                   yy_text, (int) moveType);
12357         return LoadGameOneMove(EndOfFile); /* tail recursion */
12358
12359       case IllegalMove:
12360         if (appData.testLegality) {
12361             if (appData.debugMode)
12362               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12363             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12364                     (forwardMostMove / 2) + 1,
12365                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12366             DisplayError(move, 0);
12367             done = TRUE;
12368         } else {
12369             if (appData.debugMode)
12370               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12371                       yy_text, currentMoveString);
12372             if(currentMoveString[1] == '@') {
12373                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12374                 fromY = DROP_RANK;
12375             } else {
12376                 fromX = currentMoveString[0] - AAA;
12377                 fromY = currentMoveString[1] - ONE;
12378             }
12379             toX = currentMoveString[2] - AAA;
12380             toY = currentMoveString[3] - ONE;
12381             promoChar = currentMoveString[4];
12382         }
12383         break;
12384
12385       case AmbiguousMove:
12386         if (appData.debugMode)
12387           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12388         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12389                 (forwardMostMove / 2) + 1,
12390                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12391         DisplayError(move, 0);
12392         done = TRUE;
12393         break;
12394
12395       default:
12396       case ImpossibleMove:
12397         if (appData.debugMode)
12398           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12399         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12400                 (forwardMostMove / 2) + 1,
12401                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12402         DisplayError(move, 0);
12403         done = TRUE;
12404         break;
12405     }
12406
12407     if (done) {
12408         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12409             DrawPosition(FALSE, boards[currentMove]);
12410             DisplayBothClocks();
12411             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12412               DisplayComment(currentMove - 1, commentList[currentMove]);
12413         }
12414         (void) StopLoadGameTimer();
12415         gameFileFP = NULL;
12416         cmailOldMove = forwardMostMove;
12417         return FALSE;
12418     } else {
12419         /* currentMoveString is set as a side-effect of yylex */
12420
12421         thinkOutput[0] = NULLCHAR;
12422         MakeMove(fromX, fromY, toX, toY, promoChar);
12423         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12424         currentMove = forwardMostMove;
12425         return TRUE;
12426     }
12427 }
12428
12429 /* Load the nth game from the given file */
12430 int
12431 LoadGameFromFile (char *filename, int n, char *title, int useList)
12432 {
12433     FILE *f;
12434     char buf[MSG_SIZ];
12435
12436     if (strcmp(filename, "-") == 0) {
12437         f = stdin;
12438         title = "stdin";
12439     } else {
12440         f = fopen(filename, "rb");
12441         if (f == NULL) {
12442           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12443             DisplayError(buf, errno);
12444             return FALSE;
12445         }
12446     }
12447     if (fseek(f, 0, 0) == -1) {
12448         /* f is not seekable; probably a pipe */
12449         useList = FALSE;
12450     }
12451     if (useList && n == 0) {
12452         int error = GameListBuild(f);
12453         if (error) {
12454             DisplayError(_("Cannot build game list"), error);
12455         } else if (!ListEmpty(&gameList) &&
12456                    ((ListGame *) gameList.tailPred)->number > 1) {
12457             GameListPopUp(f, title);
12458             return TRUE;
12459         }
12460         GameListDestroy();
12461         n = 1;
12462     }
12463     if (n == 0) n = 1;
12464     return LoadGame(f, n, title, FALSE);
12465 }
12466
12467
12468 void
12469 MakeRegisteredMove ()
12470 {
12471     int fromX, fromY, toX, toY;
12472     char promoChar;
12473     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12474         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12475           case CMAIL_MOVE:
12476           case CMAIL_DRAW:
12477             if (appData.debugMode)
12478               fprintf(debugFP, "Restoring %s for game %d\n",
12479                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12480
12481             thinkOutput[0] = NULLCHAR;
12482             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12483             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12484             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12485             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12486             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12487             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12488             MakeMove(fromX, fromY, toX, toY, promoChar);
12489             ShowMove(fromX, fromY, toX, toY);
12490
12491             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12492               case MT_NONE:
12493               case MT_CHECK:
12494                 break;
12495
12496               case MT_CHECKMATE:
12497               case MT_STAINMATE:
12498                 if (WhiteOnMove(currentMove)) {
12499                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12500                 } else {
12501                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12502                 }
12503                 break;
12504
12505               case MT_STALEMATE:
12506                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12507                 break;
12508             }
12509
12510             break;
12511
12512           case CMAIL_RESIGN:
12513             if (WhiteOnMove(currentMove)) {
12514                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12515             } else {
12516                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12517             }
12518             break;
12519
12520           case CMAIL_ACCEPT:
12521             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12522             break;
12523
12524           default:
12525             break;
12526         }
12527     }
12528
12529     return;
12530 }
12531
12532 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12533 int
12534 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12535 {
12536     int retVal;
12537
12538     if (gameNumber > nCmailGames) {
12539         DisplayError(_("No more games in this message"), 0);
12540         return FALSE;
12541     }
12542     if (f == lastLoadGameFP) {
12543         int offset = gameNumber - lastLoadGameNumber;
12544         if (offset == 0) {
12545             cmailMsg[0] = NULLCHAR;
12546             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12547                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12548                 nCmailMovesRegistered--;
12549             }
12550             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12551             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12552                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12553             }
12554         } else {
12555             if (! RegisterMove()) return FALSE;
12556         }
12557     }
12558
12559     retVal = LoadGame(f, gameNumber, title, useList);
12560
12561     /* Make move registered during previous look at this game, if any */
12562     MakeRegisteredMove();
12563
12564     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12565         commentList[currentMove]
12566           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12567         DisplayComment(currentMove - 1, commentList[currentMove]);
12568     }
12569
12570     return retVal;
12571 }
12572
12573 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12574 int
12575 ReloadGame (int offset)
12576 {
12577     int gameNumber = lastLoadGameNumber + offset;
12578     if (lastLoadGameFP == NULL) {
12579         DisplayError(_("No game has been loaded yet"), 0);
12580         return FALSE;
12581     }
12582     if (gameNumber <= 0) {
12583         DisplayError(_("Can't back up any further"), 0);
12584         return FALSE;
12585     }
12586     if (cmailMsgLoaded) {
12587         return CmailLoadGame(lastLoadGameFP, gameNumber,
12588                              lastLoadGameTitle, lastLoadGameUseList);
12589     } else {
12590         return LoadGame(lastLoadGameFP, gameNumber,
12591                         lastLoadGameTitle, lastLoadGameUseList);
12592     }
12593 }
12594
12595 int keys[EmptySquare+1];
12596
12597 int
12598 PositionMatches (Board b1, Board b2)
12599 {
12600     int r, f, sum=0;
12601     switch(appData.searchMode) {
12602         case 1: return CompareWithRights(b1, b2);
12603         case 2:
12604             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12605                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12606             }
12607             return TRUE;
12608         case 3:
12609             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12610               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12611                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12612             }
12613             return sum==0;
12614         case 4:
12615             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12616                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12617             }
12618             return sum==0;
12619     }
12620     return TRUE;
12621 }
12622
12623 #define Q_PROMO  4
12624 #define Q_EP     3
12625 #define Q_BCASTL 2
12626 #define Q_WCASTL 1
12627
12628 int pieceList[256], quickBoard[256];
12629 ChessSquare pieceType[256] = { EmptySquare };
12630 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12631 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12632 int soughtTotal, turn;
12633 Boolean epOK, flipSearch;
12634
12635 typedef struct {
12636     unsigned char piece, to;
12637 } Move;
12638
12639 #define DSIZE (250000)
12640
12641 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12642 Move *moveDatabase = initialSpace;
12643 unsigned int movePtr, dataSize = DSIZE;
12644
12645 int
12646 MakePieceList (Board board, int *counts)
12647 {
12648     int r, f, n=Q_PROMO, total=0;
12649     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12650     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12651         int sq = f + (r<<4);
12652         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12653             quickBoard[sq] = ++n;
12654             pieceList[n] = sq;
12655             pieceType[n] = board[r][f];
12656             counts[board[r][f]]++;
12657             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12658             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12659             total++;
12660         }
12661     }
12662     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12663     return total;
12664 }
12665
12666 void
12667 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12668 {
12669     int sq = fromX + (fromY<<4);
12670     int piece = quickBoard[sq], rook;
12671     quickBoard[sq] = 0;
12672     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12673     if(piece == pieceList[1] && fromY == toY) {
12674       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12675         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12676         moveDatabase[movePtr++].piece = Q_WCASTL;
12677         quickBoard[sq] = piece;
12678         piece = quickBoard[from]; quickBoard[from] = 0;
12679         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12680       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12681         quickBoard[sq] = 0; // remove Rook
12682         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12683         moveDatabase[movePtr++].piece = Q_WCASTL;
12684         quickBoard[sq] = pieceList[1]; // put King
12685         piece = rook;
12686         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12687       }
12688     } else
12689     if(piece == pieceList[2] && fromY == toY) {
12690       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12691         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12692         moveDatabase[movePtr++].piece = Q_BCASTL;
12693         quickBoard[sq] = piece;
12694         piece = quickBoard[from]; quickBoard[from] = 0;
12695         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12696       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12697         quickBoard[sq] = 0; // remove Rook
12698         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12699         moveDatabase[movePtr++].piece = Q_BCASTL;
12700         quickBoard[sq] = pieceList[2]; // put King
12701         piece = rook;
12702         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12703       }
12704     } else
12705     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12706         quickBoard[(fromY<<4)+toX] = 0;
12707         moveDatabase[movePtr].piece = Q_EP;
12708         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12709         moveDatabase[movePtr].to = sq;
12710     } else
12711     if(promoPiece != pieceType[piece]) {
12712         moveDatabase[movePtr++].piece = Q_PROMO;
12713         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12714     }
12715     moveDatabase[movePtr].piece = piece;
12716     quickBoard[sq] = piece;
12717     movePtr++;
12718 }
12719
12720 int
12721 PackGame (Board board)
12722 {
12723     Move *newSpace = NULL;
12724     moveDatabase[movePtr].piece = 0; // terminate previous game
12725     if(movePtr > dataSize) {
12726         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12727         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12728         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12729         if(newSpace) {
12730             int i;
12731             Move *p = moveDatabase, *q = newSpace;
12732             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12733             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12734             moveDatabase = newSpace;
12735         } else { // calloc failed, we must be out of memory. Too bad...
12736             dataSize = 0; // prevent calloc events for all subsequent games
12737             return 0;     // and signal this one isn't cached
12738         }
12739     }
12740     movePtr++;
12741     MakePieceList(board, counts);
12742     return movePtr;
12743 }
12744
12745 int
12746 QuickCompare (Board board, int *minCounts, int *maxCounts)
12747 {   // compare according to search mode
12748     int r, f;
12749     switch(appData.searchMode)
12750     {
12751       case 1: // exact position match
12752         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12753         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12754             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12755         }
12756         break;
12757       case 2: // can have extra material on empty squares
12758         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12759             if(board[r][f] == EmptySquare) continue;
12760             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12761         }
12762         break;
12763       case 3: // material with exact Pawn structure
12764         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12765             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12766             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12767         } // fall through to material comparison
12768       case 4: // exact material
12769         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12770         break;
12771       case 6: // material range with given imbalance
12772         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12773         // fall through to range comparison
12774       case 5: // material range
12775         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12776     }
12777     return TRUE;
12778 }
12779
12780 int
12781 QuickScan (Board board, Move *move)
12782 {   // reconstruct game,and compare all positions in it
12783     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12784     do {
12785         int piece = move->piece;
12786         int to = move->to, from = pieceList[piece];
12787         if(found < 0) { // if already found just scan to game end for final piece count
12788           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12789            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12790            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12791                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12792             ) {
12793             static int lastCounts[EmptySquare+1];
12794             int i;
12795             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12796             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12797           } else stretch = 0;
12798           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12799           if(found >= 0 && !appData.minPieces) return found;
12800         }
12801         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12802           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12803           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12804             piece = (++move)->piece;
12805             from = pieceList[piece];
12806             counts[pieceType[piece]]--;
12807             pieceType[piece] = (ChessSquare) move->to;
12808             counts[move->to]++;
12809           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12810             counts[pieceType[quickBoard[to]]]--;
12811             quickBoard[to] = 0; total--;
12812             move++;
12813             continue;
12814           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12815             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12816             from  = pieceList[piece]; // so this must be King
12817             quickBoard[from] = 0;
12818             pieceList[piece] = to;
12819             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12820             quickBoard[from] = 0; // rook
12821             quickBoard[to] = piece;
12822             to = move->to; piece = move->piece;
12823             goto aftercastle;
12824           }
12825         }
12826         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12827         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12828         quickBoard[from] = 0;
12829       aftercastle:
12830         quickBoard[to] = piece;
12831         pieceList[piece] = to;
12832         cnt++; turn ^= 3;
12833         move++;
12834     } while(1);
12835 }
12836
12837 void
12838 InitSearch ()
12839 {
12840     int r, f;
12841     flipSearch = FALSE;
12842     CopyBoard(soughtBoard, boards[currentMove]);
12843     soughtTotal = MakePieceList(soughtBoard, maxSought);
12844     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12845     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12846     CopyBoard(reverseBoard, boards[currentMove]);
12847     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12848         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12849         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12850         reverseBoard[r][f] = piece;
12851     }
12852     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12853     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12854     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12855                  || (boards[currentMove][CASTLING][2] == NoRights ||
12856                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12857                  && (boards[currentMove][CASTLING][5] == NoRights ||
12858                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12859       ) {
12860         flipSearch = TRUE;
12861         CopyBoard(flipBoard, soughtBoard);
12862         CopyBoard(rotateBoard, reverseBoard);
12863         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12864             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12865             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12866         }
12867     }
12868     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12869     if(appData.searchMode >= 5) {
12870         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12871         MakePieceList(soughtBoard, minSought);
12872         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12873     }
12874     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12875         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12876 }
12877
12878 GameInfo dummyInfo;
12879 static int creatingBook;
12880
12881 int
12882 GameContainsPosition (FILE *f, ListGame *lg)
12883 {
12884     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12885     int fromX, fromY, toX, toY;
12886     char promoChar;
12887     static int initDone=FALSE;
12888
12889     // weed out games based on numerical tag comparison
12890     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12891     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12892     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12893     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12894     if(!initDone) {
12895         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12896         initDone = TRUE;
12897     }
12898     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12899     else CopyBoard(boards[scratch], initialPosition); // default start position
12900     if(lg->moves) {
12901         turn = btm + 1;
12902         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12903         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12904     }
12905     if(btm) plyNr++;
12906     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12907     fseek(f, lg->offset, 0);
12908     yynewfile(f);
12909     while(1) {
12910         yyboardindex = scratch;
12911         quickFlag = plyNr+1;
12912         next = Myylex();
12913         quickFlag = 0;
12914         switch(next) {
12915             case PGNTag:
12916                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12917             default:
12918                 continue;
12919
12920             case XBoardGame:
12921             case GNUChessGame:
12922                 if(plyNr) return -1; // after we have seen moves, this is for new game
12923               continue;
12924
12925             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12926             case ImpossibleMove:
12927             case WhiteWins: // game ends here with these four
12928             case BlackWins:
12929             case GameIsDrawn:
12930             case GameUnfinished:
12931                 return -1;
12932
12933             case IllegalMove:
12934                 if(appData.testLegality) return -1;
12935             case WhiteCapturesEnPassant:
12936             case BlackCapturesEnPassant:
12937             case WhitePromotion:
12938             case BlackPromotion:
12939             case WhiteNonPromotion:
12940             case BlackNonPromotion:
12941             case NormalMove:
12942             case FirstLeg:
12943             case WhiteKingSideCastle:
12944             case WhiteQueenSideCastle:
12945             case BlackKingSideCastle:
12946             case BlackQueenSideCastle:
12947             case WhiteKingSideCastleWild:
12948             case WhiteQueenSideCastleWild:
12949             case BlackKingSideCastleWild:
12950             case BlackQueenSideCastleWild:
12951             case WhiteHSideCastleFR:
12952             case WhiteASideCastleFR:
12953             case BlackHSideCastleFR:
12954             case BlackASideCastleFR:
12955                 fromX = currentMoveString[0] - AAA;
12956                 fromY = currentMoveString[1] - ONE;
12957                 toX = currentMoveString[2] - AAA;
12958                 toY = currentMoveString[3] - ONE;
12959                 promoChar = currentMoveString[4];
12960                 break;
12961             case WhiteDrop:
12962             case BlackDrop:
12963                 fromX = next == WhiteDrop ?
12964                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12965                   (int) CharToPiece(ToLower(currentMoveString[0]));
12966                 fromY = DROP_RANK;
12967                 toX = currentMoveString[2] - AAA;
12968                 toY = currentMoveString[3] - ONE;
12969                 promoChar = 0;
12970                 break;
12971         }
12972         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12973         plyNr++;
12974         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12975         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12976         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12977         if(appData.findMirror) {
12978             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12979             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12980         }
12981     }
12982 }
12983
12984 /* Load the nth game from open file f */
12985 int
12986 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12987 {
12988     ChessMove cm;
12989     char buf[MSG_SIZ];
12990     int gn = gameNumber;
12991     ListGame *lg = NULL;
12992     int numPGNTags = 0, i;
12993     int err, pos = -1;
12994     GameMode oldGameMode;
12995     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12996     char oldName[MSG_SIZ];
12997
12998     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12999
13000     if (appData.debugMode)
13001         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13002
13003     if (gameMode == Training )
13004         SetTrainingModeOff();
13005
13006     oldGameMode = gameMode;
13007     if (gameMode != BeginningOfGame) {
13008       Reset(FALSE, TRUE);
13009     }
13010     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13011
13012     gameFileFP = f;
13013     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13014         fclose(lastLoadGameFP);
13015     }
13016
13017     if (useList) {
13018         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13019
13020         if (lg) {
13021             fseek(f, lg->offset, 0);
13022             GameListHighlight(gameNumber);
13023             pos = lg->position;
13024             gn = 1;
13025         }
13026         else {
13027             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13028               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13029             else
13030             DisplayError(_("Game number out of range"), 0);
13031             return FALSE;
13032         }
13033     } else {
13034         GameListDestroy();
13035         if (fseek(f, 0, 0) == -1) {
13036             if (f == lastLoadGameFP ?
13037                 gameNumber == lastLoadGameNumber + 1 :
13038                 gameNumber == 1) {
13039                 gn = 1;
13040             } else {
13041                 DisplayError(_("Can't seek on game file"), 0);
13042                 return FALSE;
13043             }
13044         }
13045     }
13046     lastLoadGameFP = f;
13047     lastLoadGameNumber = gameNumber;
13048     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13049     lastLoadGameUseList = useList;
13050
13051     yynewfile(f);
13052
13053     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13054       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13055                 lg->gameInfo.black);
13056             DisplayTitle(buf);
13057     } else if (*title != NULLCHAR) {
13058         if (gameNumber > 1) {
13059           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13060             DisplayTitle(buf);
13061         } else {
13062             DisplayTitle(title);
13063         }
13064     }
13065
13066     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13067         gameMode = PlayFromGameFile;
13068         ModeHighlight();
13069     }
13070
13071     currentMove = forwardMostMove = backwardMostMove = 0;
13072     CopyBoard(boards[0], initialPosition);
13073     StopClocks();
13074
13075     /*
13076      * Skip the first gn-1 games in the file.
13077      * Also skip over anything that precedes an identifiable
13078      * start of game marker, to avoid being confused by
13079      * garbage at the start of the file.  Currently
13080      * recognized start of game markers are the move number "1",
13081      * the pattern "gnuchess .* game", the pattern
13082      * "^[#;%] [^ ]* game file", and a PGN tag block.
13083      * A game that starts with one of the latter two patterns
13084      * will also have a move number 1, possibly
13085      * following a position diagram.
13086      * 5-4-02: Let's try being more lenient and allowing a game to
13087      * start with an unnumbered move.  Does that break anything?
13088      */
13089     cm = lastLoadGameStart = EndOfFile;
13090     while (gn > 0) {
13091         yyboardindex = forwardMostMove;
13092         cm = (ChessMove) Myylex();
13093         switch (cm) {
13094           case EndOfFile:
13095             if (cmailMsgLoaded) {
13096                 nCmailGames = CMAIL_MAX_GAMES - gn;
13097             } else {
13098                 Reset(TRUE, TRUE);
13099                 DisplayError(_("Game not found in file"), 0);
13100             }
13101             return FALSE;
13102
13103           case GNUChessGame:
13104           case XBoardGame:
13105             gn--;
13106             lastLoadGameStart = cm;
13107             break;
13108
13109           case MoveNumberOne:
13110             switch (lastLoadGameStart) {
13111               case GNUChessGame:
13112               case XBoardGame:
13113               case PGNTag:
13114                 break;
13115               case MoveNumberOne:
13116               case EndOfFile:
13117                 gn--;           /* count this game */
13118                 lastLoadGameStart = cm;
13119                 break;
13120               default:
13121                 /* impossible */
13122                 break;
13123             }
13124             break;
13125
13126           case PGNTag:
13127             switch (lastLoadGameStart) {
13128               case GNUChessGame:
13129               case PGNTag:
13130               case MoveNumberOne:
13131               case EndOfFile:
13132                 gn--;           /* count this game */
13133                 lastLoadGameStart = cm;
13134                 break;
13135               case XBoardGame:
13136                 lastLoadGameStart = cm; /* game counted already */
13137                 break;
13138               default:
13139                 /* impossible */
13140                 break;
13141             }
13142             if (gn > 0) {
13143                 do {
13144                     yyboardindex = forwardMostMove;
13145                     cm = (ChessMove) Myylex();
13146                 } while (cm == PGNTag || cm == Comment);
13147             }
13148             break;
13149
13150           case WhiteWins:
13151           case BlackWins:
13152           case GameIsDrawn:
13153             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13154                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13155                     != CMAIL_OLD_RESULT) {
13156                     nCmailResults ++ ;
13157                     cmailResult[  CMAIL_MAX_GAMES
13158                                 - gn - 1] = CMAIL_OLD_RESULT;
13159                 }
13160             }
13161             break;
13162
13163           case NormalMove:
13164           case FirstLeg:
13165             /* Only a NormalMove can be at the start of a game
13166              * without a position diagram. */
13167             if (lastLoadGameStart == EndOfFile ) {
13168               gn--;
13169               lastLoadGameStart = MoveNumberOne;
13170             }
13171             break;
13172
13173           default:
13174             break;
13175         }
13176     }
13177
13178     if (appData.debugMode)
13179       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13180
13181     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13182
13183     if (cm == XBoardGame) {
13184         /* Skip any header junk before position diagram and/or move 1 */
13185         for (;;) {
13186             yyboardindex = forwardMostMove;
13187             cm = (ChessMove) Myylex();
13188
13189             if (cm == EndOfFile ||
13190                 cm == GNUChessGame || cm == XBoardGame) {
13191                 /* Empty game; pretend end-of-file and handle later */
13192                 cm = EndOfFile;
13193                 break;
13194             }
13195
13196             if (cm == MoveNumberOne || cm == PositionDiagram ||
13197                 cm == PGNTag || cm == Comment)
13198               break;
13199         }
13200     } else if (cm == GNUChessGame) {
13201         if (gameInfo.event != NULL) {
13202             free(gameInfo.event);
13203         }
13204         gameInfo.event = StrSave(yy_text);
13205     }
13206
13207     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13208     while (cm == PGNTag) {
13209         if (appData.debugMode)
13210           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13211         err = ParsePGNTag(yy_text, &gameInfo);
13212         if (!err) numPGNTags++;
13213
13214         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13215         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13216             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13217             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13218             InitPosition(TRUE);
13219             oldVariant = gameInfo.variant;
13220             if (appData.debugMode)
13221               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13222         }
13223
13224
13225         if (gameInfo.fen != NULL) {
13226           Board initial_position;
13227           startedFromSetupPosition = TRUE;
13228           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13229             Reset(TRUE, TRUE);
13230             DisplayError(_("Bad FEN position in file"), 0);
13231             return FALSE;
13232           }
13233           CopyBoard(boards[0], initial_position);
13234           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13235             CopyBoard(initialPosition, initial_position);
13236           if (blackPlaysFirst) {
13237             currentMove = forwardMostMove = backwardMostMove = 1;
13238             CopyBoard(boards[1], initial_position);
13239             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13240             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13241             timeRemaining[0][1] = whiteTimeRemaining;
13242             timeRemaining[1][1] = blackTimeRemaining;
13243             if (commentList[0] != NULL) {
13244               commentList[1] = commentList[0];
13245               commentList[0] = NULL;
13246             }
13247           } else {
13248             currentMove = forwardMostMove = backwardMostMove = 0;
13249           }
13250           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13251           {   int i;
13252               initialRulePlies = FENrulePlies;
13253               for( i=0; i< nrCastlingRights; i++ )
13254                   initialRights[i] = initial_position[CASTLING][i];
13255           }
13256           yyboardindex = forwardMostMove;
13257           free(gameInfo.fen);
13258           gameInfo.fen = NULL;
13259         }
13260
13261         yyboardindex = forwardMostMove;
13262         cm = (ChessMove) Myylex();
13263
13264         /* Handle comments interspersed among the tags */
13265         while (cm == Comment) {
13266             char *p;
13267             if (appData.debugMode)
13268               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13269             p = yy_text;
13270             AppendComment(currentMove, p, FALSE);
13271             yyboardindex = forwardMostMove;
13272             cm = (ChessMove) Myylex();
13273         }
13274     }
13275
13276     /* don't rely on existence of Event tag since if game was
13277      * pasted from clipboard the Event tag may not exist
13278      */
13279     if (numPGNTags > 0){
13280         char *tags;
13281         if (gameInfo.variant == VariantNormal) {
13282           VariantClass v = StringToVariant(gameInfo.event);
13283           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13284           if(v < VariantShogi) gameInfo.variant = v;
13285         }
13286         if (!matchMode) {
13287           if( appData.autoDisplayTags ) {
13288             tags = PGNTags(&gameInfo);
13289             TagsPopUp(tags, CmailMsg());
13290             free(tags);
13291           }
13292         }
13293     } else {
13294         /* Make something up, but don't display it now */
13295         SetGameInfo();
13296         TagsPopDown();
13297     }
13298
13299     if (cm == PositionDiagram) {
13300         int i, j;
13301         char *p;
13302         Board initial_position;
13303
13304         if (appData.debugMode)
13305           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13306
13307         if (!startedFromSetupPosition) {
13308             p = yy_text;
13309             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13310               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13311                 switch (*p) {
13312                   case '{':
13313                   case '[':
13314                   case '-':
13315                   case ' ':
13316                   case '\t':
13317                   case '\n':
13318                   case '\r':
13319                     break;
13320                   default:
13321                     initial_position[i][j++] = CharToPiece(*p);
13322                     break;
13323                 }
13324             while (*p == ' ' || *p == '\t' ||
13325                    *p == '\n' || *p == '\r') p++;
13326
13327             if (strncmp(p, "black", strlen("black"))==0)
13328               blackPlaysFirst = TRUE;
13329             else
13330               blackPlaysFirst = FALSE;
13331             startedFromSetupPosition = TRUE;
13332
13333             CopyBoard(boards[0], initial_position);
13334             if (blackPlaysFirst) {
13335                 currentMove = forwardMostMove = backwardMostMove = 1;
13336                 CopyBoard(boards[1], initial_position);
13337                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13338                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13339                 timeRemaining[0][1] = whiteTimeRemaining;
13340                 timeRemaining[1][1] = blackTimeRemaining;
13341                 if (commentList[0] != NULL) {
13342                     commentList[1] = commentList[0];
13343                     commentList[0] = NULL;
13344                 }
13345             } else {
13346                 currentMove = forwardMostMove = backwardMostMove = 0;
13347             }
13348         }
13349         yyboardindex = forwardMostMove;
13350         cm = (ChessMove) Myylex();
13351     }
13352
13353   if(!creatingBook) {
13354     if (first.pr == NoProc) {
13355         StartChessProgram(&first);
13356     }
13357     InitChessProgram(&first, FALSE);
13358     if(gameInfo.variant == VariantUnknown && *oldName) {
13359         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13360         gameInfo.variant = v;
13361     }
13362     SendToProgram("force\n", &first);
13363     if (startedFromSetupPosition) {
13364         SendBoard(&first, forwardMostMove);
13365     if (appData.debugMode) {
13366         fprintf(debugFP, "Load Game\n");
13367     }
13368         DisplayBothClocks();
13369     }
13370   }
13371
13372     /* [HGM] server: flag to write setup moves in broadcast file as one */
13373     loadFlag = appData.suppressLoadMoves;
13374
13375     while (cm == Comment) {
13376         char *p;
13377         if (appData.debugMode)
13378           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13379         p = yy_text;
13380         AppendComment(currentMove, p, FALSE);
13381         yyboardindex = forwardMostMove;
13382         cm = (ChessMove) Myylex();
13383     }
13384
13385     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13386         cm == WhiteWins || cm == BlackWins ||
13387         cm == GameIsDrawn || cm == GameUnfinished) {
13388         DisplayMessage("", _("No moves in game"));
13389         if (cmailMsgLoaded) {
13390             if (appData.debugMode)
13391               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13392             ClearHighlights();
13393             flipView = FALSE;
13394         }
13395         DrawPosition(FALSE, boards[currentMove]);
13396         DisplayBothClocks();
13397         gameMode = EditGame;
13398         ModeHighlight();
13399         gameFileFP = NULL;
13400         cmailOldMove = 0;
13401         return TRUE;
13402     }
13403
13404     // [HGM] PV info: routine tests if comment empty
13405     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13406         DisplayComment(currentMove - 1, commentList[currentMove]);
13407     }
13408     if (!matchMode && appData.timeDelay != 0)
13409       DrawPosition(FALSE, boards[currentMove]);
13410
13411     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13412       programStats.ok_to_send = 1;
13413     }
13414
13415     /* if the first token after the PGN tags is a move
13416      * and not move number 1, retrieve it from the parser
13417      */
13418     if (cm != MoveNumberOne)
13419         LoadGameOneMove(cm);
13420
13421     /* load the remaining moves from the file */
13422     while (LoadGameOneMove(EndOfFile)) {
13423       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13424       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13425     }
13426
13427     /* rewind to the start of the game */
13428     currentMove = backwardMostMove;
13429
13430     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13431
13432     if (oldGameMode == AnalyzeFile) {
13433       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13434       AnalyzeFileEvent();
13435     } else
13436     if (oldGameMode == AnalyzeMode) {
13437       AnalyzeFileEvent();
13438     }
13439
13440     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13441         long int w, b; // [HGM] adjourn: restore saved clock times
13442         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13443         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13444             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13445             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13446         }
13447     }
13448
13449     if(creatingBook) return TRUE;
13450     if (!matchMode && pos > 0) {
13451         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13452     } else
13453     if (matchMode || appData.timeDelay == 0) {
13454       ToEndEvent();
13455     } else if (appData.timeDelay > 0) {
13456       AutoPlayGameLoop();
13457     }
13458
13459     if (appData.debugMode)
13460         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13461
13462     loadFlag = 0; /* [HGM] true game starts */
13463     return TRUE;
13464 }
13465
13466 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13467 int
13468 ReloadPosition (int offset)
13469 {
13470     int positionNumber = lastLoadPositionNumber + offset;
13471     if (lastLoadPositionFP == NULL) {
13472         DisplayError(_("No position has been loaded yet"), 0);
13473         return FALSE;
13474     }
13475     if (positionNumber <= 0) {
13476         DisplayError(_("Can't back up any further"), 0);
13477         return FALSE;
13478     }
13479     return LoadPosition(lastLoadPositionFP, positionNumber,
13480                         lastLoadPositionTitle);
13481 }
13482
13483 /* Load the nth position from the given file */
13484 int
13485 LoadPositionFromFile (char *filename, int n, char *title)
13486 {
13487     FILE *f;
13488     char buf[MSG_SIZ];
13489
13490     if (strcmp(filename, "-") == 0) {
13491         return LoadPosition(stdin, n, "stdin");
13492     } else {
13493         f = fopen(filename, "rb");
13494         if (f == NULL) {
13495             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13496             DisplayError(buf, errno);
13497             return FALSE;
13498         } else {
13499             return LoadPosition(f, n, title);
13500         }
13501     }
13502 }
13503
13504 /* Load the nth position from the given open file, and close it */
13505 int
13506 LoadPosition (FILE *f, int positionNumber, char *title)
13507 {
13508     char *p, line[MSG_SIZ];
13509     Board initial_position;
13510     int i, j, fenMode, pn;
13511
13512     if (gameMode == Training )
13513         SetTrainingModeOff();
13514
13515     if (gameMode != BeginningOfGame) {
13516         Reset(FALSE, TRUE);
13517     }
13518     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13519         fclose(lastLoadPositionFP);
13520     }
13521     if (positionNumber == 0) positionNumber = 1;
13522     lastLoadPositionFP = f;
13523     lastLoadPositionNumber = positionNumber;
13524     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13525     if (first.pr == NoProc && !appData.noChessProgram) {
13526       StartChessProgram(&first);
13527       InitChessProgram(&first, FALSE);
13528     }
13529     pn = positionNumber;
13530     if (positionNumber < 0) {
13531         /* Negative position number means to seek to that byte offset */
13532         if (fseek(f, -positionNumber, 0) == -1) {
13533             DisplayError(_("Can't seek on position file"), 0);
13534             return FALSE;
13535         };
13536         pn = 1;
13537     } else {
13538         if (fseek(f, 0, 0) == -1) {
13539             if (f == lastLoadPositionFP ?
13540                 positionNumber == lastLoadPositionNumber + 1 :
13541                 positionNumber == 1) {
13542                 pn = 1;
13543             } else {
13544                 DisplayError(_("Can't seek on position file"), 0);
13545                 return FALSE;
13546             }
13547         }
13548     }
13549     /* See if this file is FEN or old-style xboard */
13550     if (fgets(line, MSG_SIZ, f) == NULL) {
13551         DisplayError(_("Position not found in file"), 0);
13552         return FALSE;
13553     }
13554     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13555     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13556
13557     if (pn >= 2) {
13558         if (fenMode || line[0] == '#') pn--;
13559         while (pn > 0) {
13560             /* skip positions before number pn */
13561             if (fgets(line, MSG_SIZ, f) == NULL) {
13562                 Reset(TRUE, TRUE);
13563                 DisplayError(_("Position not found in file"), 0);
13564                 return FALSE;
13565             }
13566             if (fenMode || line[0] == '#') pn--;
13567         }
13568     }
13569
13570     if (fenMode) {
13571         char *p;
13572         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13573             DisplayError(_("Bad FEN position in file"), 0);
13574             return FALSE;
13575         }
13576         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13577             sscanf(p+4, "%[^;]", bestMove);
13578         } else *bestMove = NULLCHAR;
13579         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13580             sscanf(p+4, "%[^;]", avoidMove);
13581         } else *avoidMove = NULLCHAR;
13582     } else {
13583         (void) fgets(line, MSG_SIZ, f);
13584         (void) fgets(line, MSG_SIZ, f);
13585
13586         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13587             (void) fgets(line, MSG_SIZ, f);
13588             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13589                 if (*p == ' ')
13590                   continue;
13591                 initial_position[i][j++] = CharToPiece(*p);
13592             }
13593         }
13594
13595         blackPlaysFirst = FALSE;
13596         if (!feof(f)) {
13597             (void) fgets(line, MSG_SIZ, f);
13598             if (strncmp(line, "black", strlen("black"))==0)
13599               blackPlaysFirst = TRUE;
13600         }
13601     }
13602     startedFromSetupPosition = TRUE;
13603
13604     CopyBoard(boards[0], initial_position);
13605     if (blackPlaysFirst) {
13606         currentMove = forwardMostMove = backwardMostMove = 1;
13607         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13608         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13609         CopyBoard(boards[1], initial_position);
13610         DisplayMessage("", _("Black to play"));
13611     } else {
13612         currentMove = forwardMostMove = backwardMostMove = 0;
13613         DisplayMessage("", _("White to play"));
13614     }
13615     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13616     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13617         SendToProgram("force\n", &first);
13618         SendBoard(&first, forwardMostMove);
13619     }
13620     if (appData.debugMode) {
13621 int i, j;
13622   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13623   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13624         fprintf(debugFP, "Load Position\n");
13625     }
13626
13627     if (positionNumber > 1) {
13628       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13629         DisplayTitle(line);
13630     } else {
13631         DisplayTitle(title);
13632     }
13633     gameMode = EditGame;
13634     ModeHighlight();
13635     ResetClocks();
13636     timeRemaining[0][1] = whiteTimeRemaining;
13637     timeRemaining[1][1] = blackTimeRemaining;
13638     DrawPosition(FALSE, boards[currentMove]);
13639
13640     return TRUE;
13641 }
13642
13643
13644 void
13645 CopyPlayerNameIntoFileName (char **dest, char *src)
13646 {
13647     while (*src != NULLCHAR && *src != ',') {
13648         if (*src == ' ') {
13649             *(*dest)++ = '_';
13650             src++;
13651         } else {
13652             *(*dest)++ = *src++;
13653         }
13654     }
13655 }
13656
13657 char *
13658 DefaultFileName (char *ext)
13659 {
13660     static char def[MSG_SIZ];
13661     char *p;
13662
13663     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13664         p = def;
13665         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13666         *p++ = '-';
13667         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13668         *p++ = '.';
13669         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13670     } else {
13671         def[0] = NULLCHAR;
13672     }
13673     return def;
13674 }
13675
13676 /* Save the current game to the given file */
13677 int
13678 SaveGameToFile (char *filename, int append)
13679 {
13680     FILE *f;
13681     char buf[MSG_SIZ];
13682     int result, i, t,tot=0;
13683
13684     if (strcmp(filename, "-") == 0) {
13685         return SaveGame(stdout, 0, NULL);
13686     } else {
13687         for(i=0; i<10; i++) { // upto 10 tries
13688              f = fopen(filename, append ? "a" : "w");
13689              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13690              if(f || errno != 13) break;
13691              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13692              tot += t;
13693         }
13694         if (f == NULL) {
13695             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13696             DisplayError(buf, errno);
13697             return FALSE;
13698         } else {
13699             safeStrCpy(buf, lastMsg, MSG_SIZ);
13700             DisplayMessage(_("Waiting for access to save file"), "");
13701             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13702             DisplayMessage(_("Saving game"), "");
13703             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13704             result = SaveGame(f, 0, NULL);
13705             DisplayMessage(buf, "");
13706             return result;
13707         }
13708     }
13709 }
13710
13711 char *
13712 SavePart (char *str)
13713 {
13714     static char buf[MSG_SIZ];
13715     char *p;
13716
13717     p = strchr(str, ' ');
13718     if (p == NULL) return str;
13719     strncpy(buf, str, p - str);
13720     buf[p - str] = NULLCHAR;
13721     return buf;
13722 }
13723
13724 #define PGN_MAX_LINE 75
13725
13726 #define PGN_SIDE_WHITE  0
13727 #define PGN_SIDE_BLACK  1
13728
13729 static int
13730 FindFirstMoveOutOfBook (int side)
13731 {
13732     int result = -1;
13733
13734     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13735         int index = backwardMostMove;
13736         int has_book_hit = 0;
13737
13738         if( (index % 2) != side ) {
13739             index++;
13740         }
13741
13742         while( index < forwardMostMove ) {
13743             /* Check to see if engine is in book */
13744             int depth = pvInfoList[index].depth;
13745             int score = pvInfoList[index].score;
13746             int in_book = 0;
13747
13748             if( depth <= 2 ) {
13749                 in_book = 1;
13750             }
13751             else if( score == 0 && depth == 63 ) {
13752                 in_book = 1; /* Zappa */
13753             }
13754             else if( score == 2 && depth == 99 ) {
13755                 in_book = 1; /* Abrok */
13756             }
13757
13758             has_book_hit += in_book;
13759
13760             if( ! in_book ) {
13761                 result = index;
13762
13763                 break;
13764             }
13765
13766             index += 2;
13767         }
13768     }
13769
13770     return result;
13771 }
13772
13773 void
13774 GetOutOfBookInfo (char * buf)
13775 {
13776     int oob[2];
13777     int i;
13778     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13779
13780     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13781     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13782
13783     *buf = '\0';
13784
13785     if( oob[0] >= 0 || oob[1] >= 0 ) {
13786         for( i=0; i<2; i++ ) {
13787             int idx = oob[i];
13788
13789             if( idx >= 0 ) {
13790                 if( i > 0 && oob[0] >= 0 ) {
13791                     strcat( buf, "   " );
13792                 }
13793
13794                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13795                 sprintf( buf+strlen(buf), "%s%.2f",
13796                     pvInfoList[idx].score >= 0 ? "+" : "",
13797                     pvInfoList[idx].score / 100.0 );
13798             }
13799         }
13800     }
13801 }
13802
13803 /* Save game in PGN style */
13804 static void
13805 SaveGamePGN2 (FILE *f)
13806 {
13807     int i, offset, linelen, newblock;
13808 //    char *movetext;
13809     char numtext[32];
13810     int movelen, numlen, blank;
13811     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13812
13813     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13814
13815     PrintPGNTags(f, &gameInfo);
13816
13817     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13818
13819     if (backwardMostMove > 0 || startedFromSetupPosition) {
13820         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13821         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13822         fprintf(f, "\n{--------------\n");
13823         PrintPosition(f, backwardMostMove);
13824         fprintf(f, "--------------}\n");
13825         free(fen);
13826     }
13827     else {
13828         /* [AS] Out of book annotation */
13829         if( appData.saveOutOfBookInfo ) {
13830             char buf[64];
13831
13832             GetOutOfBookInfo( buf );
13833
13834             if( buf[0] != '\0' ) {
13835                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13836             }
13837         }
13838
13839         fprintf(f, "\n");
13840     }
13841
13842     i = backwardMostMove;
13843     linelen = 0;
13844     newblock = TRUE;
13845
13846     while (i < forwardMostMove) {
13847         /* Print comments preceding this move */
13848         if (commentList[i] != NULL) {
13849             if (linelen > 0) fprintf(f, "\n");
13850             fprintf(f, "%s", commentList[i]);
13851             linelen = 0;
13852             newblock = TRUE;
13853         }
13854
13855         /* Format move number */
13856         if ((i % 2) == 0)
13857           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13858         else
13859           if (newblock)
13860             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13861           else
13862             numtext[0] = NULLCHAR;
13863
13864         numlen = strlen(numtext);
13865         newblock = FALSE;
13866
13867         /* Print move number */
13868         blank = linelen > 0 && numlen > 0;
13869         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13870             fprintf(f, "\n");
13871             linelen = 0;
13872             blank = 0;
13873         }
13874         if (blank) {
13875             fprintf(f, " ");
13876             linelen++;
13877         }
13878         fprintf(f, "%s", numtext);
13879         linelen += numlen;
13880
13881         /* Get move */
13882         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13883         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13884
13885         /* Print move */
13886         blank = linelen > 0 && movelen > 0;
13887         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13888             fprintf(f, "\n");
13889             linelen = 0;
13890             blank = 0;
13891         }
13892         if (blank) {
13893             fprintf(f, " ");
13894             linelen++;
13895         }
13896         fprintf(f, "%s", move_buffer);
13897         linelen += movelen;
13898
13899         /* [AS] Add PV info if present */
13900         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13901             /* [HGM] add time */
13902             char buf[MSG_SIZ]; int seconds;
13903
13904             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13905
13906             if( seconds <= 0)
13907               buf[0] = 0;
13908             else
13909               if( seconds < 30 )
13910                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13911               else
13912                 {
13913                   seconds = (seconds + 4)/10; // round to full seconds
13914                   if( seconds < 60 )
13915                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13916                   else
13917                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13918                 }
13919
13920             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13921                       pvInfoList[i].score >= 0 ? "+" : "",
13922                       pvInfoList[i].score / 100.0,
13923                       pvInfoList[i].depth,
13924                       buf );
13925
13926             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13927
13928             /* Print score/depth */
13929             blank = linelen > 0 && movelen > 0;
13930             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13931                 fprintf(f, "\n");
13932                 linelen = 0;
13933                 blank = 0;
13934             }
13935             if (blank) {
13936                 fprintf(f, " ");
13937                 linelen++;
13938             }
13939             fprintf(f, "%s", move_buffer);
13940             linelen += movelen;
13941         }
13942
13943         i++;
13944     }
13945
13946     /* Start a new line */
13947     if (linelen > 0) fprintf(f, "\n");
13948
13949     /* Print comments after last move */
13950     if (commentList[i] != NULL) {
13951         fprintf(f, "%s\n", commentList[i]);
13952     }
13953
13954     /* Print result */
13955     if (gameInfo.resultDetails != NULL &&
13956         gameInfo.resultDetails[0] != NULLCHAR) {
13957         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13958         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13959            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13960             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13961         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13962     } else {
13963         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13964     }
13965 }
13966
13967 /* Save game in PGN style and close the file */
13968 int
13969 SaveGamePGN (FILE *f)
13970 {
13971     SaveGamePGN2(f);
13972     fclose(f);
13973     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13974     return TRUE;
13975 }
13976
13977 /* Save game in old style and close the file */
13978 int
13979 SaveGameOldStyle (FILE *f)
13980 {
13981     int i, offset;
13982     time_t tm;
13983
13984     tm = time((time_t *) NULL);
13985
13986     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13987     PrintOpponents(f);
13988
13989     if (backwardMostMove > 0 || startedFromSetupPosition) {
13990         fprintf(f, "\n[--------------\n");
13991         PrintPosition(f, backwardMostMove);
13992         fprintf(f, "--------------]\n");
13993     } else {
13994         fprintf(f, "\n");
13995     }
13996
13997     i = backwardMostMove;
13998     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13999
14000     while (i < forwardMostMove) {
14001         if (commentList[i] != NULL) {
14002             fprintf(f, "[%s]\n", commentList[i]);
14003         }
14004
14005         if ((i % 2) == 1) {
14006             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14007             i++;
14008         } else {
14009             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14010             i++;
14011             if (commentList[i] != NULL) {
14012                 fprintf(f, "\n");
14013                 continue;
14014             }
14015             if (i >= forwardMostMove) {
14016                 fprintf(f, "\n");
14017                 break;
14018             }
14019             fprintf(f, "%s\n", parseList[i]);
14020             i++;
14021         }
14022     }
14023
14024     if (commentList[i] != NULL) {
14025         fprintf(f, "[%s]\n", commentList[i]);
14026     }
14027
14028     /* This isn't really the old style, but it's close enough */
14029     if (gameInfo.resultDetails != NULL &&
14030         gameInfo.resultDetails[0] != NULLCHAR) {
14031         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14032                 gameInfo.resultDetails);
14033     } else {
14034         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14035     }
14036
14037     fclose(f);
14038     return TRUE;
14039 }
14040
14041 /* Save the current game to open file f and close the file */
14042 int
14043 SaveGame (FILE *f, int dummy, char *dummy2)
14044 {
14045     if (gameMode == EditPosition) EditPositionDone(TRUE);
14046     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14047     if (appData.oldSaveStyle)
14048       return SaveGameOldStyle(f);
14049     else
14050       return SaveGamePGN(f);
14051 }
14052
14053 /* Save the current position to the given file */
14054 int
14055 SavePositionToFile (char *filename)
14056 {
14057     FILE *f;
14058     char buf[MSG_SIZ];
14059
14060     if (strcmp(filename, "-") == 0) {
14061         return SavePosition(stdout, 0, NULL);
14062     } else {
14063         f = fopen(filename, "a");
14064         if (f == NULL) {
14065             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14066             DisplayError(buf, errno);
14067             return FALSE;
14068         } else {
14069             safeStrCpy(buf, lastMsg, MSG_SIZ);
14070             DisplayMessage(_("Waiting for access to save file"), "");
14071             flock(fileno(f), LOCK_EX); // [HGM] lock
14072             DisplayMessage(_("Saving position"), "");
14073             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14074             SavePosition(f, 0, NULL);
14075             DisplayMessage(buf, "");
14076             return TRUE;
14077         }
14078     }
14079 }
14080
14081 /* Save the current position to the given open file and close the file */
14082 int
14083 SavePosition (FILE *f, int dummy, char *dummy2)
14084 {
14085     time_t tm;
14086     char *fen;
14087
14088     if (gameMode == EditPosition) EditPositionDone(TRUE);
14089     if (appData.oldSaveStyle) {
14090         tm = time((time_t *) NULL);
14091
14092         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14093         PrintOpponents(f);
14094         fprintf(f, "[--------------\n");
14095         PrintPosition(f, currentMove);
14096         fprintf(f, "--------------]\n");
14097     } else {
14098         fen = PositionToFEN(currentMove, NULL, 1);
14099         fprintf(f, "%s\n", fen);
14100         free(fen);
14101     }
14102     fclose(f);
14103     return TRUE;
14104 }
14105
14106 void
14107 ReloadCmailMsgEvent (int unregister)
14108 {
14109 #if !WIN32
14110     static char *inFilename = NULL;
14111     static char *outFilename;
14112     int i;
14113     struct stat inbuf, outbuf;
14114     int status;
14115
14116     /* Any registered moves are unregistered if unregister is set, */
14117     /* i.e. invoked by the signal handler */
14118     if (unregister) {
14119         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14120             cmailMoveRegistered[i] = FALSE;
14121             if (cmailCommentList[i] != NULL) {
14122                 free(cmailCommentList[i]);
14123                 cmailCommentList[i] = NULL;
14124             }
14125         }
14126         nCmailMovesRegistered = 0;
14127     }
14128
14129     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14130         cmailResult[i] = CMAIL_NOT_RESULT;
14131     }
14132     nCmailResults = 0;
14133
14134     if (inFilename == NULL) {
14135         /* Because the filenames are static they only get malloced once  */
14136         /* and they never get freed                                      */
14137         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14138         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14139
14140         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14141         sprintf(outFilename, "%s.out", appData.cmailGameName);
14142     }
14143
14144     status = stat(outFilename, &outbuf);
14145     if (status < 0) {
14146         cmailMailedMove = FALSE;
14147     } else {
14148         status = stat(inFilename, &inbuf);
14149         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14150     }
14151
14152     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14153        counts the games, notes how each one terminated, etc.
14154
14155        It would be nice to remove this kludge and instead gather all
14156        the information while building the game list.  (And to keep it
14157        in the game list nodes instead of having a bunch of fixed-size
14158        parallel arrays.)  Note this will require getting each game's
14159        termination from the PGN tags, as the game list builder does
14160        not process the game moves.  --mann
14161        */
14162     cmailMsgLoaded = TRUE;
14163     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14164
14165     /* Load first game in the file or popup game menu */
14166     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14167
14168 #endif /* !WIN32 */
14169     return;
14170 }
14171
14172 int
14173 RegisterMove ()
14174 {
14175     FILE *f;
14176     char string[MSG_SIZ];
14177
14178     if (   cmailMailedMove
14179         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14180         return TRUE;            /* Allow free viewing  */
14181     }
14182
14183     /* Unregister move to ensure that we don't leave RegisterMove        */
14184     /* with the move registered when the conditions for registering no   */
14185     /* longer hold                                                       */
14186     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14187         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14188         nCmailMovesRegistered --;
14189
14190         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14191           {
14192               free(cmailCommentList[lastLoadGameNumber - 1]);
14193               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14194           }
14195     }
14196
14197     if (cmailOldMove == -1) {
14198         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14199         return FALSE;
14200     }
14201
14202     if (currentMove > cmailOldMove + 1) {
14203         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14204         return FALSE;
14205     }
14206
14207     if (currentMove < cmailOldMove) {
14208         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14209         return FALSE;
14210     }
14211
14212     if (forwardMostMove > currentMove) {
14213         /* Silently truncate extra moves */
14214         TruncateGame();
14215     }
14216
14217     if (   (currentMove == cmailOldMove + 1)
14218         || (   (currentMove == cmailOldMove)
14219             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14220                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14221         if (gameInfo.result != GameUnfinished) {
14222             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14223         }
14224
14225         if (commentList[currentMove] != NULL) {
14226             cmailCommentList[lastLoadGameNumber - 1]
14227               = StrSave(commentList[currentMove]);
14228         }
14229         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14230
14231         if (appData.debugMode)
14232           fprintf(debugFP, "Saving %s for game %d\n",
14233                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14234
14235         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14236
14237         f = fopen(string, "w");
14238         if (appData.oldSaveStyle) {
14239             SaveGameOldStyle(f); /* also closes the file */
14240
14241             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14242             f = fopen(string, "w");
14243             SavePosition(f, 0, NULL); /* also closes the file */
14244         } else {
14245             fprintf(f, "{--------------\n");
14246             PrintPosition(f, currentMove);
14247             fprintf(f, "--------------}\n\n");
14248
14249             SaveGame(f, 0, NULL); /* also closes the file*/
14250         }
14251
14252         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14253         nCmailMovesRegistered ++;
14254     } else if (nCmailGames == 1) {
14255         DisplayError(_("You have not made a move yet"), 0);
14256         return FALSE;
14257     }
14258
14259     return TRUE;
14260 }
14261
14262 void
14263 MailMoveEvent ()
14264 {
14265 #if !WIN32
14266     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14267     FILE *commandOutput;
14268     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14269     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14270     int nBuffers;
14271     int i;
14272     int archived;
14273     char *arcDir;
14274
14275     if (! cmailMsgLoaded) {
14276         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14277         return;
14278     }
14279
14280     if (nCmailGames == nCmailResults) {
14281         DisplayError(_("No unfinished games"), 0);
14282         return;
14283     }
14284
14285 #if CMAIL_PROHIBIT_REMAIL
14286     if (cmailMailedMove) {
14287       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);
14288         DisplayError(msg, 0);
14289         return;
14290     }
14291 #endif
14292
14293     if (! (cmailMailedMove || RegisterMove())) return;
14294
14295     if (   cmailMailedMove
14296         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14297       snprintf(string, MSG_SIZ, partCommandString,
14298                appData.debugMode ? " -v" : "", appData.cmailGameName);
14299         commandOutput = popen(string, "r");
14300
14301         if (commandOutput == NULL) {
14302             DisplayError(_("Failed to invoke cmail"), 0);
14303         } else {
14304             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14305                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14306             }
14307             if (nBuffers > 1) {
14308                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14309                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14310                 nBytes = MSG_SIZ - 1;
14311             } else {
14312                 (void) memcpy(msg, buffer, nBytes);
14313             }
14314             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14315
14316             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14317                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14318
14319                 archived = TRUE;
14320                 for (i = 0; i < nCmailGames; i ++) {
14321                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14322                         archived = FALSE;
14323                     }
14324                 }
14325                 if (   archived
14326                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14327                         != NULL)) {
14328                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14329                            arcDir,
14330                            appData.cmailGameName,
14331                            gameInfo.date);
14332                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14333                     cmailMsgLoaded = FALSE;
14334                 }
14335             }
14336
14337             DisplayInformation(msg);
14338             pclose(commandOutput);
14339         }
14340     } else {
14341         if ((*cmailMsg) != '\0') {
14342             DisplayInformation(cmailMsg);
14343         }
14344     }
14345
14346     return;
14347 #endif /* !WIN32 */
14348 }
14349
14350 char *
14351 CmailMsg ()
14352 {
14353 #if WIN32
14354     return NULL;
14355 #else
14356     int  prependComma = 0;
14357     char number[5];
14358     char string[MSG_SIZ];       /* Space for game-list */
14359     int  i;
14360
14361     if (!cmailMsgLoaded) return "";
14362
14363     if (cmailMailedMove) {
14364       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14365     } else {
14366         /* Create a list of games left */
14367       snprintf(string, MSG_SIZ, "[");
14368         for (i = 0; i < nCmailGames; i ++) {
14369             if (! (   cmailMoveRegistered[i]
14370                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14371                 if (prependComma) {
14372                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14373                 } else {
14374                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14375                     prependComma = 1;
14376                 }
14377
14378                 strcat(string, number);
14379             }
14380         }
14381         strcat(string, "]");
14382
14383         if (nCmailMovesRegistered + nCmailResults == 0) {
14384             switch (nCmailGames) {
14385               case 1:
14386                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14387                 break;
14388
14389               case 2:
14390                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14391                 break;
14392
14393               default:
14394                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14395                          nCmailGames);
14396                 break;
14397             }
14398         } else {
14399             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14400               case 1:
14401                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14402                          string);
14403                 break;
14404
14405               case 0:
14406                 if (nCmailResults == nCmailGames) {
14407                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14408                 } else {
14409                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14410                 }
14411                 break;
14412
14413               default:
14414                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14415                          string);
14416             }
14417         }
14418     }
14419     return cmailMsg;
14420 #endif /* WIN32 */
14421 }
14422
14423 void
14424 ResetGameEvent ()
14425 {
14426     if (gameMode == Training)
14427       SetTrainingModeOff();
14428
14429     Reset(TRUE, TRUE);
14430     cmailMsgLoaded = FALSE;
14431     if (appData.icsActive) {
14432       SendToICS(ics_prefix);
14433       SendToICS("refresh\n");
14434     }
14435 }
14436
14437 void
14438 ExitEvent (int status)
14439 {
14440     exiting++;
14441     if (exiting > 2) {
14442       /* Give up on clean exit */
14443       exit(status);
14444     }
14445     if (exiting > 1) {
14446       /* Keep trying for clean exit */
14447       return;
14448     }
14449
14450     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14451     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14452
14453     if (telnetISR != NULL) {
14454       RemoveInputSource(telnetISR);
14455     }
14456     if (icsPR != NoProc) {
14457       DestroyChildProcess(icsPR, TRUE);
14458     }
14459
14460     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14461     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14462
14463     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14464     /* make sure this other one finishes before killing it!                  */
14465     if(endingGame) { int count = 0;
14466         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14467         while(endingGame && count++ < 10) DoSleep(1);
14468         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14469     }
14470
14471     /* Kill off chess programs */
14472     if (first.pr != NoProc) {
14473         ExitAnalyzeMode();
14474
14475         DoSleep( appData.delayBeforeQuit );
14476         SendToProgram("quit\n", &first);
14477         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14478     }
14479     if (second.pr != NoProc) {
14480         DoSleep( appData.delayBeforeQuit );
14481         SendToProgram("quit\n", &second);
14482         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14483     }
14484     if (first.isr != NULL) {
14485         RemoveInputSource(first.isr);
14486     }
14487     if (second.isr != NULL) {
14488         RemoveInputSource(second.isr);
14489     }
14490
14491     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14492     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14493
14494     ShutDownFrontEnd();
14495     exit(status);
14496 }
14497
14498 void
14499 PauseEngine (ChessProgramState *cps)
14500 {
14501     SendToProgram("pause\n", cps);
14502     cps->pause = 2;
14503 }
14504
14505 void
14506 UnPauseEngine (ChessProgramState *cps)
14507 {
14508     SendToProgram("resume\n", cps);
14509     cps->pause = 1;
14510 }
14511
14512 void
14513 PauseEvent ()
14514 {
14515     if (appData.debugMode)
14516         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14517     if (pausing) {
14518         pausing = FALSE;
14519         ModeHighlight();
14520         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14521             StartClocks();
14522             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14523                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14524                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14525             }
14526             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14527             HandleMachineMove(stashedInputMove, stalledEngine);
14528             stalledEngine = NULL;
14529             return;
14530         }
14531         if (gameMode == MachinePlaysWhite ||
14532             gameMode == TwoMachinesPlay   ||
14533             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14534             if(first.pause)  UnPauseEngine(&first);
14535             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14536             if(second.pause) UnPauseEngine(&second);
14537             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14538             StartClocks();
14539         } else {
14540             DisplayBothClocks();
14541         }
14542         if (gameMode == PlayFromGameFile) {
14543             if (appData.timeDelay >= 0)
14544                 AutoPlayGameLoop();
14545         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14546             Reset(FALSE, TRUE);
14547             SendToICS(ics_prefix);
14548             SendToICS("refresh\n");
14549         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14550             ForwardInner(forwardMostMove);
14551         }
14552         pauseExamInvalid = FALSE;
14553     } else {
14554         switch (gameMode) {
14555           default:
14556             return;
14557           case IcsExamining:
14558             pauseExamForwardMostMove = forwardMostMove;
14559             pauseExamInvalid = FALSE;
14560             /* fall through */
14561           case IcsObserving:
14562           case IcsPlayingWhite:
14563           case IcsPlayingBlack:
14564             pausing = TRUE;
14565             ModeHighlight();
14566             return;
14567           case PlayFromGameFile:
14568             (void) StopLoadGameTimer();
14569             pausing = TRUE;
14570             ModeHighlight();
14571             break;
14572           case BeginningOfGame:
14573             if (appData.icsActive) return;
14574             /* else fall through */
14575           case MachinePlaysWhite:
14576           case MachinePlaysBlack:
14577           case TwoMachinesPlay:
14578             if (forwardMostMove == 0)
14579               return;           /* don't pause if no one has moved */
14580             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14581                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14582                 if(onMove->pause) {           // thinking engine can be paused
14583                     PauseEngine(onMove);      // do it
14584                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14585                         PauseEngine(onMove->other);
14586                     else
14587                         SendToProgram("easy\n", onMove->other);
14588                     StopClocks();
14589                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14590             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14591                 if(first.pause) {
14592                     PauseEngine(&first);
14593                     StopClocks();
14594                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14595             } else { // human on move, pause pondering by either method
14596                 if(first.pause)
14597                     PauseEngine(&first);
14598                 else if(appData.ponderNextMove)
14599                     SendToProgram("easy\n", &first);
14600                 StopClocks();
14601             }
14602             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14603           case AnalyzeMode:
14604             pausing = TRUE;
14605             ModeHighlight();
14606             break;
14607         }
14608     }
14609 }
14610
14611 void
14612 EditCommentEvent ()
14613 {
14614     char title[MSG_SIZ];
14615
14616     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14617       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14618     } else {
14619       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14620                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14621                parseList[currentMove - 1]);
14622     }
14623
14624     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14625 }
14626
14627
14628 void
14629 EditTagsEvent ()
14630 {
14631     char *tags = PGNTags(&gameInfo);
14632     bookUp = FALSE;
14633     EditTagsPopUp(tags, NULL);
14634     free(tags);
14635 }
14636
14637 void
14638 ToggleSecond ()
14639 {
14640   if(second.analyzing) {
14641     SendToProgram("exit\n", &second);
14642     second.analyzing = FALSE;
14643   } else {
14644     if (second.pr == NoProc) StartChessProgram(&second);
14645     InitChessProgram(&second, FALSE);
14646     FeedMovesToProgram(&second, currentMove);
14647
14648     SendToProgram("analyze\n", &second);
14649     second.analyzing = TRUE;
14650   }
14651 }
14652
14653 /* Toggle ShowThinking */
14654 void
14655 ToggleShowThinking()
14656 {
14657   appData.showThinking = !appData.showThinking;
14658   ShowThinkingEvent();
14659 }
14660
14661 int
14662 AnalyzeModeEvent ()
14663 {
14664     char buf[MSG_SIZ];
14665
14666     if (!first.analysisSupport) {
14667       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14668       DisplayError(buf, 0);
14669       return 0;
14670     }
14671     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14672     if (appData.icsActive) {
14673         if (gameMode != IcsObserving) {
14674           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14675             DisplayError(buf, 0);
14676             /* secure check */
14677             if (appData.icsEngineAnalyze) {
14678                 if (appData.debugMode)
14679                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14680                 ExitAnalyzeMode();
14681                 ModeHighlight();
14682             }
14683             return 0;
14684         }
14685         /* if enable, user wants to disable icsEngineAnalyze */
14686         if (appData.icsEngineAnalyze) {
14687                 ExitAnalyzeMode();
14688                 ModeHighlight();
14689                 return 0;
14690         }
14691         appData.icsEngineAnalyze = TRUE;
14692         if (appData.debugMode)
14693             fprintf(debugFP, "ICS engine analyze starting... \n");
14694     }
14695
14696     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14697     if (appData.noChessProgram || gameMode == AnalyzeMode)
14698       return 0;
14699
14700     if (gameMode != AnalyzeFile) {
14701         if (!appData.icsEngineAnalyze) {
14702                EditGameEvent();
14703                if (gameMode != EditGame) return 0;
14704         }
14705         if (!appData.showThinking) ToggleShowThinking();
14706         ResurrectChessProgram();
14707         SendToProgram("analyze\n", &first);
14708         first.analyzing = TRUE;
14709         /*first.maybeThinking = TRUE;*/
14710         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14711         EngineOutputPopUp();
14712     }
14713     if (!appData.icsEngineAnalyze) {
14714         gameMode = AnalyzeMode;
14715         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14716     }
14717     pausing = FALSE;
14718     ModeHighlight();
14719     SetGameInfo();
14720
14721     StartAnalysisClock();
14722     GetTimeMark(&lastNodeCountTime);
14723     lastNodeCount = 0;
14724     return 1;
14725 }
14726
14727 void
14728 AnalyzeFileEvent ()
14729 {
14730     if (appData.noChessProgram || gameMode == AnalyzeFile)
14731       return;
14732
14733     if (!first.analysisSupport) {
14734       char buf[MSG_SIZ];
14735       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14736       DisplayError(buf, 0);
14737       return;
14738     }
14739
14740     if (gameMode != AnalyzeMode) {
14741         keepInfo = 1; // mere annotating should not alter PGN tags
14742         EditGameEvent();
14743         keepInfo = 0;
14744         if (gameMode != EditGame) return;
14745         if (!appData.showThinking) ToggleShowThinking();
14746         ResurrectChessProgram();
14747         SendToProgram("analyze\n", &first);
14748         first.analyzing = TRUE;
14749         /*first.maybeThinking = TRUE;*/
14750         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14751         EngineOutputPopUp();
14752     }
14753     gameMode = AnalyzeFile;
14754     pausing = FALSE;
14755     ModeHighlight();
14756
14757     StartAnalysisClock();
14758     GetTimeMark(&lastNodeCountTime);
14759     lastNodeCount = 0;
14760     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14761     AnalysisPeriodicEvent(1);
14762 }
14763
14764 void
14765 MachineWhiteEvent ()
14766 {
14767     char buf[MSG_SIZ];
14768     char *bookHit = NULL;
14769
14770     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14771       return;
14772
14773
14774     if (gameMode == PlayFromGameFile ||
14775         gameMode == TwoMachinesPlay  ||
14776         gameMode == Training         ||
14777         gameMode == AnalyzeMode      ||
14778         gameMode == EndOfGame)
14779         EditGameEvent();
14780
14781     if (gameMode == EditPosition)
14782         EditPositionDone(TRUE);
14783
14784     if (!WhiteOnMove(currentMove)) {
14785         DisplayError(_("It is not White's turn"), 0);
14786         return;
14787     }
14788
14789     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14790       ExitAnalyzeMode();
14791
14792     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14793         gameMode == AnalyzeFile)
14794         TruncateGame();
14795
14796     ResurrectChessProgram();    /* in case it isn't running */
14797     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14798         gameMode = MachinePlaysWhite;
14799         ResetClocks();
14800     } else
14801     gameMode = MachinePlaysWhite;
14802     pausing = FALSE;
14803     ModeHighlight();
14804     SetGameInfo();
14805     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14806     DisplayTitle(buf);
14807     if (first.sendName) {
14808       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14809       SendToProgram(buf, &first);
14810     }
14811     if (first.sendTime) {
14812       if (first.useColors) {
14813         SendToProgram("black\n", &first); /*gnu kludge*/
14814       }
14815       SendTimeRemaining(&first, TRUE);
14816     }
14817     if (first.useColors) {
14818       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14819     }
14820     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14821     SetMachineThinkingEnables();
14822     first.maybeThinking = TRUE;
14823     StartClocks();
14824     firstMove = FALSE;
14825
14826     if (appData.autoFlipView && !flipView) {
14827       flipView = !flipView;
14828       DrawPosition(FALSE, NULL);
14829       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14830     }
14831
14832     if(bookHit) { // [HGM] book: simulate book reply
14833         static char bookMove[MSG_SIZ]; // a bit generous?
14834
14835         programStats.nodes = programStats.depth = programStats.time =
14836         programStats.score = programStats.got_only_move = 0;
14837         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14838
14839         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14840         strcat(bookMove, bookHit);
14841         HandleMachineMove(bookMove, &first);
14842     }
14843 }
14844
14845 void
14846 MachineBlackEvent ()
14847 {
14848   char buf[MSG_SIZ];
14849   char *bookHit = NULL;
14850
14851     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14852         return;
14853
14854
14855     if (gameMode == PlayFromGameFile ||
14856         gameMode == TwoMachinesPlay  ||
14857         gameMode == Training         ||
14858         gameMode == AnalyzeMode      ||
14859         gameMode == EndOfGame)
14860         EditGameEvent();
14861
14862     if (gameMode == EditPosition)
14863         EditPositionDone(TRUE);
14864
14865     if (WhiteOnMove(currentMove)) {
14866         DisplayError(_("It is not Black's turn"), 0);
14867         return;
14868     }
14869
14870     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14871       ExitAnalyzeMode();
14872
14873     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14874         gameMode == AnalyzeFile)
14875         TruncateGame();
14876
14877     ResurrectChessProgram();    /* in case it isn't running */
14878     gameMode = MachinePlaysBlack;
14879     pausing = FALSE;
14880     ModeHighlight();
14881     SetGameInfo();
14882     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14883     DisplayTitle(buf);
14884     if (first.sendName) {
14885       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14886       SendToProgram(buf, &first);
14887     }
14888     if (first.sendTime) {
14889       if (first.useColors) {
14890         SendToProgram("white\n", &first); /*gnu kludge*/
14891       }
14892       SendTimeRemaining(&first, FALSE);
14893     }
14894     if (first.useColors) {
14895       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14896     }
14897     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14898     SetMachineThinkingEnables();
14899     first.maybeThinking = TRUE;
14900     StartClocks();
14901
14902     if (appData.autoFlipView && flipView) {
14903       flipView = !flipView;
14904       DrawPosition(FALSE, NULL);
14905       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14906     }
14907     if(bookHit) { // [HGM] book: simulate book reply
14908         static char bookMove[MSG_SIZ]; // a bit generous?
14909
14910         programStats.nodes = programStats.depth = programStats.time =
14911         programStats.score = programStats.got_only_move = 0;
14912         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14913
14914         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14915         strcat(bookMove, bookHit);
14916         HandleMachineMove(bookMove, &first);
14917     }
14918 }
14919
14920
14921 void
14922 DisplayTwoMachinesTitle ()
14923 {
14924     char buf[MSG_SIZ];
14925     if (appData.matchGames > 0) {
14926         if(appData.tourneyFile[0]) {
14927           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14928                    gameInfo.white, _("vs."), gameInfo.black,
14929                    nextGame+1, appData.matchGames+1,
14930                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14931         } else
14932         if (first.twoMachinesColor[0] == 'w') {
14933           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14934                    gameInfo.white, _("vs."),  gameInfo.black,
14935                    first.matchWins, second.matchWins,
14936                    matchGame - 1 - (first.matchWins + second.matchWins));
14937         } else {
14938           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14939                    gameInfo.white, _("vs."), gameInfo.black,
14940                    second.matchWins, first.matchWins,
14941                    matchGame - 1 - (first.matchWins + second.matchWins));
14942         }
14943     } else {
14944       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14945     }
14946     DisplayTitle(buf);
14947 }
14948
14949 void
14950 SettingsMenuIfReady ()
14951 {
14952   if (second.lastPing != second.lastPong) {
14953     DisplayMessage("", _("Waiting for second chess program"));
14954     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14955     return;
14956   }
14957   ThawUI();
14958   DisplayMessage("", "");
14959   SettingsPopUp(&second);
14960 }
14961
14962 int
14963 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14964 {
14965     char buf[MSG_SIZ];
14966     if (cps->pr == NoProc) {
14967         StartChessProgram(cps);
14968         if (cps->protocolVersion == 1) {
14969           retry();
14970           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14971         } else {
14972           /* kludge: allow timeout for initial "feature" command */
14973           if(retry != TwoMachinesEventIfReady) FreezeUI();
14974           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14975           DisplayMessage("", buf);
14976           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14977         }
14978         return 1;
14979     }
14980     return 0;
14981 }
14982
14983 void
14984 TwoMachinesEvent P((void))
14985 {
14986     int i;
14987     char buf[MSG_SIZ];
14988     ChessProgramState *onmove;
14989     char *bookHit = NULL;
14990     static int stalling = 0;
14991     TimeMark now;
14992     long wait;
14993
14994     if (appData.noChessProgram) return;
14995
14996     switch (gameMode) {
14997       case TwoMachinesPlay:
14998         return;
14999       case MachinePlaysWhite:
15000       case MachinePlaysBlack:
15001         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15002             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15003             return;
15004         }
15005         /* fall through */
15006       case BeginningOfGame:
15007       case PlayFromGameFile:
15008       case EndOfGame:
15009         EditGameEvent();
15010         if (gameMode != EditGame) return;
15011         break;
15012       case EditPosition:
15013         EditPositionDone(TRUE);
15014         break;
15015       case AnalyzeMode:
15016       case AnalyzeFile:
15017         ExitAnalyzeMode();
15018         break;
15019       case EditGame:
15020       default:
15021         break;
15022     }
15023
15024 //    forwardMostMove = currentMove;
15025     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15026     startingEngine = TRUE;
15027
15028     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15029
15030     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15031     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15032       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15033       return;
15034     }
15035   if(!appData.epd) {
15036     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15037
15038     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15039                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15040         startingEngine = matchMode = FALSE;
15041         DisplayError("second engine does not play this", 0);
15042         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15043         EditGameEvent(); // switch back to EditGame mode
15044         return;
15045     }
15046
15047     if(!stalling) {
15048       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15049       SendToProgram("force\n", &second);
15050       stalling = 1;
15051       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15052       return;
15053     }
15054   }
15055     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15056     if(appData.matchPause>10000 || appData.matchPause<10)
15057                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15058     wait = SubtractTimeMarks(&now, &pauseStart);
15059     if(wait < appData.matchPause) {
15060         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15061         return;
15062     }
15063     // we are now committed to starting the game
15064     stalling = 0;
15065     DisplayMessage("", "");
15066   if(!appData.epd) {
15067     if (startedFromSetupPosition) {
15068         SendBoard(&second, backwardMostMove);
15069     if (appData.debugMode) {
15070         fprintf(debugFP, "Two Machines\n");
15071     }
15072     }
15073     for (i = backwardMostMove; i < forwardMostMove; i++) {
15074         SendMoveToProgram(i, &second);
15075     }
15076   }
15077
15078     gameMode = TwoMachinesPlay;
15079     pausing = startingEngine = FALSE;
15080     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15081     SetGameInfo();
15082     DisplayTwoMachinesTitle();
15083     firstMove = TRUE;
15084     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15085         onmove = &first;
15086     } else {
15087         onmove = &second;
15088     }
15089     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15090     SendToProgram(first.computerString, &first);
15091     if (first.sendName) {
15092       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15093       SendToProgram(buf, &first);
15094     }
15095   if(!appData.epd) {
15096     SendToProgram(second.computerString, &second);
15097     if (second.sendName) {
15098       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15099       SendToProgram(buf, &second);
15100     }
15101   }
15102
15103     ResetClocks();
15104     if (!first.sendTime || !second.sendTime) {
15105         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15106         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15107     }
15108     if (onmove->sendTime) {
15109       if (onmove->useColors) {
15110         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15111       }
15112       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15113     }
15114     if (onmove->useColors) {
15115       SendToProgram(onmove->twoMachinesColor, onmove);
15116     }
15117     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15118 //    SendToProgram("go\n", onmove);
15119     onmove->maybeThinking = TRUE;
15120     SetMachineThinkingEnables();
15121
15122     StartClocks();
15123
15124     if(bookHit) { // [HGM] book: simulate book reply
15125         static char bookMove[MSG_SIZ]; // a bit generous?
15126
15127         programStats.nodes = programStats.depth = programStats.time =
15128         programStats.score = programStats.got_only_move = 0;
15129         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15130
15131         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15132         strcat(bookMove, bookHit);
15133         savedMessage = bookMove; // args for deferred call
15134         savedState = onmove;
15135         ScheduleDelayedEvent(DeferredBookMove, 1);
15136     }
15137 }
15138
15139 void
15140 TrainingEvent ()
15141 {
15142     if (gameMode == Training) {
15143       SetTrainingModeOff();
15144       gameMode = PlayFromGameFile;
15145       DisplayMessage("", _("Training mode off"));
15146     } else {
15147       gameMode = Training;
15148       animateTraining = appData.animate;
15149
15150       /* make sure we are not already at the end of the game */
15151       if (currentMove < forwardMostMove) {
15152         SetTrainingModeOn();
15153         DisplayMessage("", _("Training mode on"));
15154       } else {
15155         gameMode = PlayFromGameFile;
15156         DisplayError(_("Already at end of game"), 0);
15157       }
15158     }
15159     ModeHighlight();
15160 }
15161
15162 void
15163 IcsClientEvent ()
15164 {
15165     if (!appData.icsActive) return;
15166     switch (gameMode) {
15167       case IcsPlayingWhite:
15168       case IcsPlayingBlack:
15169       case IcsObserving:
15170       case IcsIdle:
15171       case BeginningOfGame:
15172       case IcsExamining:
15173         return;
15174
15175       case EditGame:
15176         break;
15177
15178       case EditPosition:
15179         EditPositionDone(TRUE);
15180         break;
15181
15182       case AnalyzeMode:
15183       case AnalyzeFile:
15184         ExitAnalyzeMode();
15185         break;
15186
15187       default:
15188         EditGameEvent();
15189         break;
15190     }
15191
15192     gameMode = IcsIdle;
15193     ModeHighlight();
15194     return;
15195 }
15196
15197 void
15198 EditGameEvent ()
15199 {
15200     int i;
15201
15202     switch (gameMode) {
15203       case Training:
15204         SetTrainingModeOff();
15205         break;
15206       case MachinePlaysWhite:
15207       case MachinePlaysBlack:
15208       case BeginningOfGame:
15209         SendToProgram("force\n", &first);
15210         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15211             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15212                 char buf[MSG_SIZ];
15213                 abortEngineThink = TRUE;
15214                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15215                 SendToProgram(buf, &first);
15216                 DisplayMessage("Aborting engine think", "");
15217                 FreezeUI();
15218             }
15219         }
15220         SetUserThinkingEnables();
15221         break;
15222       case PlayFromGameFile:
15223         (void) StopLoadGameTimer();
15224         if (gameFileFP != NULL) {
15225             gameFileFP = NULL;
15226         }
15227         break;
15228       case EditPosition:
15229         EditPositionDone(TRUE);
15230         break;
15231       case AnalyzeMode:
15232       case AnalyzeFile:
15233         ExitAnalyzeMode();
15234         SendToProgram("force\n", &first);
15235         break;
15236       case TwoMachinesPlay:
15237         GameEnds(EndOfFile, NULL, GE_PLAYER);
15238         ResurrectChessProgram();
15239         SetUserThinkingEnables();
15240         break;
15241       case EndOfGame:
15242         ResurrectChessProgram();
15243         break;
15244       case IcsPlayingBlack:
15245       case IcsPlayingWhite:
15246         DisplayError(_("Warning: You are still playing a game"), 0);
15247         break;
15248       case IcsObserving:
15249         DisplayError(_("Warning: You are still observing a game"), 0);
15250         break;
15251       case IcsExamining:
15252         DisplayError(_("Warning: You are still examining a game"), 0);
15253         break;
15254       case IcsIdle:
15255         break;
15256       case EditGame:
15257       default:
15258         return;
15259     }
15260
15261     pausing = FALSE;
15262     StopClocks();
15263     first.offeredDraw = second.offeredDraw = 0;
15264
15265     if (gameMode == PlayFromGameFile) {
15266         whiteTimeRemaining = timeRemaining[0][currentMove];
15267         blackTimeRemaining = timeRemaining[1][currentMove];
15268         DisplayTitle("");
15269     }
15270
15271     if (gameMode == MachinePlaysWhite ||
15272         gameMode == MachinePlaysBlack ||
15273         gameMode == TwoMachinesPlay ||
15274         gameMode == EndOfGame) {
15275         i = forwardMostMove;
15276         while (i > currentMove) {
15277             SendToProgram("undo\n", &first);
15278             i--;
15279         }
15280         if(!adjustedClock) {
15281         whiteTimeRemaining = timeRemaining[0][currentMove];
15282         blackTimeRemaining = timeRemaining[1][currentMove];
15283         DisplayBothClocks();
15284         }
15285         if (whiteFlag || blackFlag) {
15286             whiteFlag = blackFlag = 0;
15287         }
15288         DisplayTitle("");
15289     }
15290
15291     gameMode = EditGame;
15292     ModeHighlight();
15293     SetGameInfo();
15294 }
15295
15296 void
15297 EditPositionEvent ()
15298 {
15299     int i;
15300     if (gameMode == EditPosition) {
15301         EditGameEvent();
15302         return;
15303     }
15304
15305     EditGameEvent();
15306     if (gameMode != EditGame) return;
15307
15308     gameMode = EditPosition;
15309     ModeHighlight();
15310     SetGameInfo();
15311     CopyBoard(rightsBoard, nullBoard);
15312     if (currentMove > 0)
15313       CopyBoard(boards[0], boards[currentMove]);
15314     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15315       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15316
15317     blackPlaysFirst = !WhiteOnMove(currentMove);
15318     ResetClocks();
15319     currentMove = forwardMostMove = backwardMostMove = 0;
15320     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15321     DisplayMove(-1);
15322     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15323 }
15324
15325 void
15326 ExitAnalyzeMode ()
15327 {
15328     /* [DM] icsEngineAnalyze - possible call from other functions */
15329     if (appData.icsEngineAnalyze) {
15330         appData.icsEngineAnalyze = FALSE;
15331
15332         DisplayMessage("",_("Close ICS engine analyze..."));
15333     }
15334     if (first.analysisSupport && first.analyzing) {
15335       SendToBoth("exit\n");
15336       first.analyzing = second.analyzing = FALSE;
15337     }
15338     thinkOutput[0] = NULLCHAR;
15339 }
15340
15341 void
15342 EditPositionDone (Boolean fakeRights)
15343 {
15344     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15345
15346     startedFromSetupPosition = TRUE;
15347     InitChessProgram(&first, FALSE);
15348     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15349       boards[0][EP_STATUS] = EP_NONE;
15350       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15351       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15352         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15353         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15354       } else boards[0][CASTLING][2] = NoRights;
15355       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15356         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15357         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15358       } else boards[0][CASTLING][5] = NoRights;
15359       if(gameInfo.variant == VariantSChess) {
15360         int i;
15361         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15362           boards[0][VIRGIN][i] = 0;
15363           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15364           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15365         }
15366       }
15367     }
15368     SendToProgram("force\n", &first);
15369     if (blackPlaysFirst) {
15370         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15371         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15372         currentMove = forwardMostMove = backwardMostMove = 1;
15373         CopyBoard(boards[1], boards[0]);
15374     } else {
15375         currentMove = forwardMostMove = backwardMostMove = 0;
15376     }
15377     SendBoard(&first, forwardMostMove);
15378     if (appData.debugMode) {
15379         fprintf(debugFP, "EditPosDone\n");
15380     }
15381     DisplayTitle("");
15382     DisplayMessage("", "");
15383     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15384     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15385     gameMode = EditGame;
15386     ModeHighlight();
15387     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15388     ClearHighlights(); /* [AS] */
15389 }
15390
15391 /* Pause for `ms' milliseconds */
15392 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15393 void
15394 TimeDelay (long ms)
15395 {
15396     TimeMark m1, m2;
15397
15398     GetTimeMark(&m1);
15399     do {
15400         GetTimeMark(&m2);
15401     } while (SubtractTimeMarks(&m2, &m1) < ms);
15402 }
15403
15404 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15405 void
15406 SendMultiLineToICS (char *buf)
15407 {
15408     char temp[MSG_SIZ+1], *p;
15409     int len;
15410
15411     len = strlen(buf);
15412     if (len > MSG_SIZ)
15413       len = MSG_SIZ;
15414
15415     strncpy(temp, buf, len);
15416     temp[len] = 0;
15417
15418     p = temp;
15419     while (*p) {
15420         if (*p == '\n' || *p == '\r')
15421           *p = ' ';
15422         ++p;
15423     }
15424
15425     strcat(temp, "\n");
15426     SendToICS(temp);
15427     SendToPlayer(temp, strlen(temp));
15428 }
15429
15430 void
15431 SetWhiteToPlayEvent ()
15432 {
15433     if (gameMode == EditPosition) {
15434         blackPlaysFirst = FALSE;
15435         DisplayBothClocks();    /* works because currentMove is 0 */
15436     } else if (gameMode == IcsExamining) {
15437         SendToICS(ics_prefix);
15438         SendToICS("tomove white\n");
15439     }
15440 }
15441
15442 void
15443 SetBlackToPlayEvent ()
15444 {
15445     if (gameMode == EditPosition) {
15446         blackPlaysFirst = TRUE;
15447         currentMove = 1;        /* kludge */
15448         DisplayBothClocks();
15449         currentMove = 0;
15450     } else if (gameMode == IcsExamining) {
15451         SendToICS(ics_prefix);
15452         SendToICS("tomove black\n");
15453     }
15454 }
15455
15456 void
15457 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15458 {
15459     char buf[MSG_SIZ];
15460     ChessSquare piece = boards[0][y][x];
15461     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15462     static int lastVariant;
15463     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15464
15465     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15466
15467     switch (selection) {
15468       case ClearBoard:
15469         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15470         MarkTargetSquares(1);
15471         CopyBoard(currentBoard, boards[0]);
15472         CopyBoard(menuBoard, initialPosition);
15473         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15474             SendToICS(ics_prefix);
15475             SendToICS("bsetup clear\n");
15476         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15477             SendToICS(ics_prefix);
15478             SendToICS("clearboard\n");
15479         } else {
15480             int nonEmpty = 0;
15481             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15482                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15483                 for (y = 0; y < BOARD_HEIGHT; y++) {
15484                     if (gameMode == IcsExamining) {
15485                         if (boards[currentMove][y][x] != EmptySquare) {
15486                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15487                                     AAA + x, ONE + y);
15488                             SendToICS(buf);
15489                         }
15490                     } else if(boards[0][y][x] != DarkSquare) {
15491                         if(boards[0][y][x] != p) nonEmpty++;
15492                         boards[0][y][x] = p;
15493                     }
15494                 }
15495             }
15496             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15497                 int r;
15498                 for(r = 0; r < BOARD_HEIGHT; r++) {
15499                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15500                     ChessSquare p = menuBoard[r][x];
15501                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15502                   }
15503                 }
15504                 DisplayMessage("Clicking clock again restores position", "");
15505                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15506                 if(!nonEmpty) { // asked to clear an empty board
15507                     CopyBoard(boards[0], menuBoard);
15508                 } else
15509                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15510                     CopyBoard(boards[0], initialPosition);
15511                 } else
15512                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15513                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15514                     CopyBoard(boards[0], erasedBoard);
15515                 } else
15516                     CopyBoard(erasedBoard, currentBoard);
15517
15518             }
15519         }
15520         if (gameMode == EditPosition) {
15521             DrawPosition(FALSE, boards[0]);
15522         }
15523         break;
15524
15525       case WhitePlay:
15526         SetWhiteToPlayEvent();
15527         break;
15528
15529       case BlackPlay:
15530         SetBlackToPlayEvent();
15531         break;
15532
15533       case EmptySquare:
15534         if (gameMode == IcsExamining) {
15535             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15536             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15537             SendToICS(buf);
15538         } else {
15539             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15540                 if(x == BOARD_LEFT-2) {
15541                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15542                     boards[0][y][1] = 0;
15543                 } else
15544                 if(x == BOARD_RGHT+1) {
15545                     if(y >= gameInfo.holdingsSize) break;
15546                     boards[0][y][BOARD_WIDTH-2] = 0;
15547                 } else break;
15548             }
15549             boards[0][y][x] = EmptySquare;
15550             DrawPosition(FALSE, boards[0]);
15551         }
15552         break;
15553
15554       case PromotePiece:
15555         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15556            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15557             selection = (ChessSquare) (PROMOTED(piece));
15558         } else if(piece == EmptySquare) selection = WhiteSilver;
15559         else selection = (ChessSquare)((int)piece - 1);
15560         goto defaultlabel;
15561
15562       case DemotePiece:
15563         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15564            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15565             selection = (ChessSquare) (DEMOTED(piece));
15566         } else if(piece == EmptySquare) selection = BlackSilver;
15567         else selection = (ChessSquare)((int)piece + 1);
15568         goto defaultlabel;
15569
15570       case WhiteQueen:
15571       case BlackQueen:
15572         if(gameInfo.variant == VariantShatranj ||
15573            gameInfo.variant == VariantXiangqi  ||
15574            gameInfo.variant == VariantCourier  ||
15575            gameInfo.variant == VariantASEAN    ||
15576            gameInfo.variant == VariantMakruk     )
15577             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15578         goto defaultlabel;
15579
15580       case WhiteRook:
15581         baseRank = 0;
15582       case BlackRook:
15583         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15584         goto defaultlabel;
15585
15586       case WhiteKing:
15587         baseRank = 0;
15588       case BlackKing:
15589         if(gameInfo.variant == VariantXiangqi)
15590             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15591         if(gameInfo.variant == VariantKnightmate)
15592             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15593         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15594       default:
15595         defaultlabel:
15596         if (gameMode == IcsExamining) {
15597             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15598             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15599                      PieceToChar(selection), AAA + x, ONE + y);
15600             SendToICS(buf);
15601         } else {
15602             rightsBoard[y][x] = hasRights;
15603             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15604                 int n;
15605                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15606                     n = PieceToNumber(selection - BlackPawn);
15607                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15608                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15609                     boards[0][BOARD_HEIGHT-1-n][1]++;
15610                 } else
15611                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15612                     n = PieceToNumber(selection);
15613                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15614                     boards[0][n][BOARD_WIDTH-1] = selection;
15615                     boards[0][n][BOARD_WIDTH-2]++;
15616                 }
15617             } else
15618             boards[0][y][x] = selection;
15619             DrawPosition(TRUE, boards[0]);
15620             ClearHighlights();
15621             fromX = fromY = -1;
15622         }
15623         break;
15624     }
15625 }
15626
15627
15628 void
15629 DropMenuEvent (ChessSquare selection, int x, int y)
15630 {
15631     ChessMove moveType;
15632
15633     switch (gameMode) {
15634       case IcsPlayingWhite:
15635       case MachinePlaysBlack:
15636         if (!WhiteOnMove(currentMove)) {
15637             DisplayMoveError(_("It is Black's turn"));
15638             return;
15639         }
15640         moveType = WhiteDrop;
15641         break;
15642       case IcsPlayingBlack:
15643       case MachinePlaysWhite:
15644         if (WhiteOnMove(currentMove)) {
15645             DisplayMoveError(_("It is White's turn"));
15646             return;
15647         }
15648         moveType = BlackDrop;
15649         break;
15650       case EditGame:
15651         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15652         break;
15653       default:
15654         return;
15655     }
15656
15657     if (moveType == BlackDrop && selection < BlackPawn) {
15658       selection = (ChessSquare) ((int) selection
15659                                  + (int) BlackPawn - (int) WhitePawn);
15660     }
15661     if (boards[currentMove][y][x] != EmptySquare) {
15662         DisplayMoveError(_("That square is occupied"));
15663         return;
15664     }
15665
15666     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15667 }
15668
15669 void
15670 AcceptEvent ()
15671 {
15672     /* Accept a pending offer of any kind from opponent */
15673
15674     if (appData.icsActive) {
15675         SendToICS(ics_prefix);
15676         SendToICS("accept\n");
15677     } else if (cmailMsgLoaded) {
15678         if (currentMove == cmailOldMove &&
15679             commentList[cmailOldMove] != NULL &&
15680             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15681                    "Black offers a draw" : "White offers a draw")) {
15682             TruncateGame();
15683             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15684             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15685         } else {
15686             DisplayError(_("There is no pending offer on this move"), 0);
15687             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15688         }
15689     } else {
15690         /* Not used for offers from chess program */
15691     }
15692 }
15693
15694 void
15695 DeclineEvent ()
15696 {
15697     /* Decline a pending offer of any kind from opponent */
15698
15699     if (appData.icsActive) {
15700         SendToICS(ics_prefix);
15701         SendToICS("decline\n");
15702     } else if (cmailMsgLoaded) {
15703         if (currentMove == cmailOldMove &&
15704             commentList[cmailOldMove] != NULL &&
15705             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15706                    "Black offers a draw" : "White offers a draw")) {
15707 #ifdef NOTDEF
15708             AppendComment(cmailOldMove, "Draw declined", TRUE);
15709             DisplayComment(cmailOldMove - 1, "Draw declined");
15710 #endif /*NOTDEF*/
15711         } else {
15712             DisplayError(_("There is no pending offer on this move"), 0);
15713         }
15714     } else {
15715         /* Not used for offers from chess program */
15716     }
15717 }
15718
15719 void
15720 RematchEvent ()
15721 {
15722     /* Issue ICS rematch command */
15723     if (appData.icsActive) {
15724         SendToICS(ics_prefix);
15725         SendToICS("rematch\n");
15726     }
15727 }
15728
15729 void
15730 CallFlagEvent ()
15731 {
15732     /* Call your opponent's flag (claim a win on time) */
15733     if (appData.icsActive) {
15734         SendToICS(ics_prefix);
15735         SendToICS("flag\n");
15736     } else {
15737         switch (gameMode) {
15738           default:
15739             return;
15740           case MachinePlaysWhite:
15741             if (whiteFlag) {
15742                 if (blackFlag)
15743                   GameEnds(GameIsDrawn, "Both players ran out of time",
15744                            GE_PLAYER);
15745                 else
15746                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15747             } else {
15748                 DisplayError(_("Your opponent is not out of time"), 0);
15749             }
15750             break;
15751           case MachinePlaysBlack:
15752             if (blackFlag) {
15753                 if (whiteFlag)
15754                   GameEnds(GameIsDrawn, "Both players ran out of time",
15755                            GE_PLAYER);
15756                 else
15757                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15758             } else {
15759                 DisplayError(_("Your opponent is not out of time"), 0);
15760             }
15761             break;
15762         }
15763     }
15764 }
15765
15766 void
15767 ClockClick (int which)
15768 {       // [HGM] code moved to back-end from winboard.c
15769         if(which) { // black clock
15770           if (gameMode == EditPosition || gameMode == IcsExamining) {
15771             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15772             SetBlackToPlayEvent();
15773           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15774                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15775           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15776           } else if (shiftKey) {
15777             AdjustClock(which, -1);
15778           } else if (gameMode == IcsPlayingWhite ||
15779                      gameMode == MachinePlaysBlack) {
15780             CallFlagEvent();
15781           }
15782         } else { // white clock
15783           if (gameMode == EditPosition || gameMode == IcsExamining) {
15784             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15785             SetWhiteToPlayEvent();
15786           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15787                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15788           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15789           } else if (shiftKey) {
15790             AdjustClock(which, -1);
15791           } else if (gameMode == IcsPlayingBlack ||
15792                    gameMode == MachinePlaysWhite) {
15793             CallFlagEvent();
15794           }
15795         }
15796 }
15797
15798 void
15799 DrawEvent ()
15800 {
15801     /* Offer draw or accept pending draw offer from opponent */
15802
15803     if (appData.icsActive) {
15804         /* Note: tournament rules require draw offers to be
15805            made after you make your move but before you punch
15806            your clock.  Currently ICS doesn't let you do that;
15807            instead, you immediately punch your clock after making
15808            a move, but you can offer a draw at any time. */
15809
15810         SendToICS(ics_prefix);
15811         SendToICS("draw\n");
15812         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15813     } else if (cmailMsgLoaded) {
15814         if (currentMove == cmailOldMove &&
15815             commentList[cmailOldMove] != NULL &&
15816             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15817                    "Black offers a draw" : "White offers a draw")) {
15818             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15819             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15820         } else if (currentMove == cmailOldMove + 1) {
15821             char *offer = WhiteOnMove(cmailOldMove) ?
15822               "White offers a draw" : "Black offers a draw";
15823             AppendComment(currentMove, offer, TRUE);
15824             DisplayComment(currentMove - 1, offer);
15825             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15826         } else {
15827             DisplayError(_("You must make your move before offering a draw"), 0);
15828             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15829         }
15830     } else if (first.offeredDraw) {
15831         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15832     } else {
15833         if (first.sendDrawOffers) {
15834             SendToProgram("draw\n", &first);
15835             userOfferedDraw = TRUE;
15836         }
15837     }
15838 }
15839
15840 void
15841 AdjournEvent ()
15842 {
15843     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15844
15845     if (appData.icsActive) {
15846         SendToICS(ics_prefix);
15847         SendToICS("adjourn\n");
15848     } else {
15849         /* Currently GNU Chess doesn't offer or accept Adjourns */
15850     }
15851 }
15852
15853
15854 void
15855 AbortEvent ()
15856 {
15857     /* Offer Abort or accept pending Abort offer from opponent */
15858
15859     if (appData.icsActive) {
15860         SendToICS(ics_prefix);
15861         SendToICS("abort\n");
15862     } else {
15863         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15864     }
15865 }
15866
15867 void
15868 ResignEvent ()
15869 {
15870     /* Resign.  You can do this even if it's not your turn. */
15871
15872     if (appData.icsActive) {
15873         SendToICS(ics_prefix);
15874         SendToICS("resign\n");
15875     } else {
15876         switch (gameMode) {
15877           case MachinePlaysWhite:
15878             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15879             break;
15880           case MachinePlaysBlack:
15881             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15882             break;
15883           case EditGame:
15884             if (cmailMsgLoaded) {
15885                 TruncateGame();
15886                 if (WhiteOnMove(cmailOldMove)) {
15887                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15888                 } else {
15889                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15890                 }
15891                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15892             }
15893             break;
15894           default:
15895             break;
15896         }
15897     }
15898 }
15899
15900
15901 void
15902 StopObservingEvent ()
15903 {
15904     /* Stop observing current games */
15905     SendToICS(ics_prefix);
15906     SendToICS("unobserve\n");
15907 }
15908
15909 void
15910 StopExaminingEvent ()
15911 {
15912     /* Stop observing current game */
15913     SendToICS(ics_prefix);
15914     SendToICS("unexamine\n");
15915 }
15916
15917 void
15918 ForwardInner (int target)
15919 {
15920     int limit; int oldSeekGraphUp = seekGraphUp;
15921
15922     if (appData.debugMode)
15923         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15924                 target, currentMove, forwardMostMove);
15925
15926     if (gameMode == EditPosition)
15927       return;
15928
15929     seekGraphUp = FALSE;
15930     MarkTargetSquares(1);
15931     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15932
15933     if (gameMode == PlayFromGameFile && !pausing)
15934       PauseEvent();
15935
15936     if (gameMode == IcsExamining && pausing)
15937       limit = pauseExamForwardMostMove;
15938     else
15939       limit = forwardMostMove;
15940
15941     if (target > limit) target = limit;
15942
15943     if (target > 0 && moveList[target - 1][0]) {
15944         int fromX, fromY, toX, toY;
15945         toX = moveList[target - 1][2] - AAA;
15946         toY = moveList[target - 1][3] - ONE;
15947         if (moveList[target - 1][1] == '@') {
15948             if (appData.highlightLastMove) {
15949                 SetHighlights(-1, -1, toX, toY);
15950             }
15951         } else {
15952             fromX = moveList[target - 1][0] - AAA;
15953             fromY = moveList[target - 1][1] - ONE;
15954             if (target == currentMove + 1) {
15955                 if(moveList[target - 1][4] == ';') { // multi-leg
15956                     killX = moveList[target - 1][5] - AAA;
15957                     killY = moveList[target - 1][6] - ONE;
15958                 }
15959                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15960                 killX = killY = -1;
15961             }
15962             if (appData.highlightLastMove) {
15963                 SetHighlights(fromX, fromY, toX, toY);
15964             }
15965         }
15966     }
15967     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15968         gameMode == Training || gameMode == PlayFromGameFile ||
15969         gameMode == AnalyzeFile) {
15970         while (currentMove < target) {
15971             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15972             SendMoveToProgram(currentMove++, &first);
15973         }
15974     } else {
15975         currentMove = target;
15976     }
15977
15978     if (gameMode == EditGame || gameMode == EndOfGame) {
15979         whiteTimeRemaining = timeRemaining[0][currentMove];
15980         blackTimeRemaining = timeRemaining[1][currentMove];
15981     }
15982     DisplayBothClocks();
15983     DisplayMove(currentMove - 1);
15984     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15985     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15986     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15987         DisplayComment(currentMove - 1, commentList[currentMove]);
15988     }
15989     ClearMap(); // [HGM] exclude: invalidate map
15990 }
15991
15992
15993 void
15994 ForwardEvent ()
15995 {
15996     if (gameMode == IcsExamining && !pausing) {
15997         SendToICS(ics_prefix);
15998         SendToICS("forward\n");
15999     } else {
16000         ForwardInner(currentMove + 1);
16001     }
16002 }
16003
16004 void
16005 ToEndEvent ()
16006 {
16007     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16008         /* to optimze, we temporarily turn off analysis mode while we feed
16009          * the remaining moves to the engine. Otherwise we get analysis output
16010          * after each move.
16011          */
16012         if (first.analysisSupport) {
16013           SendToProgram("exit\nforce\n", &first);
16014           first.analyzing = FALSE;
16015         }
16016     }
16017
16018     if (gameMode == IcsExamining && !pausing) {
16019         SendToICS(ics_prefix);
16020         SendToICS("forward 999999\n");
16021     } else {
16022         ForwardInner(forwardMostMove);
16023     }
16024
16025     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16026         /* we have fed all the moves, so reactivate analysis mode */
16027         SendToProgram("analyze\n", &first);
16028         first.analyzing = TRUE;
16029         /*first.maybeThinking = TRUE;*/
16030         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16031     }
16032 }
16033
16034 void
16035 BackwardInner (int target)
16036 {
16037     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16038
16039     if (appData.debugMode)
16040         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16041                 target, currentMove, forwardMostMove);
16042
16043     if (gameMode == EditPosition) return;
16044     seekGraphUp = FALSE;
16045     MarkTargetSquares(1);
16046     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16047     if (currentMove <= backwardMostMove) {
16048         ClearHighlights();
16049         DrawPosition(full_redraw, boards[currentMove]);
16050         return;
16051     }
16052     if (gameMode == PlayFromGameFile && !pausing)
16053       PauseEvent();
16054
16055     if (moveList[target][0]) {
16056         int fromX, fromY, toX, toY;
16057         toX = moveList[target][2] - AAA;
16058         toY = moveList[target][3] - ONE;
16059         if (moveList[target][1] == '@') {
16060             if (appData.highlightLastMove) {
16061                 SetHighlights(-1, -1, toX, toY);
16062             }
16063         } else {
16064             fromX = moveList[target][0] - AAA;
16065             fromY = moveList[target][1] - ONE;
16066             if (target == currentMove - 1) {
16067                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16068             }
16069             if (appData.highlightLastMove) {
16070                 SetHighlights(fromX, fromY, toX, toY);
16071             }
16072         }
16073     }
16074     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16075         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16076         while (currentMove > target) {
16077             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16078                 // null move cannot be undone. Reload program with move history before it.
16079                 int i;
16080                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16081                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16082                 }
16083                 SendBoard(&first, i);
16084               if(second.analyzing) SendBoard(&second, i);
16085                 for(currentMove=i; currentMove<target; currentMove++) {
16086                     SendMoveToProgram(currentMove, &first);
16087                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16088                 }
16089                 break;
16090             }
16091             SendToBoth("undo\n");
16092             currentMove--;
16093         }
16094     } else {
16095         currentMove = target;
16096     }
16097
16098     if (gameMode == EditGame || gameMode == EndOfGame) {
16099         whiteTimeRemaining = timeRemaining[0][currentMove];
16100         blackTimeRemaining = timeRemaining[1][currentMove];
16101     }
16102     DisplayBothClocks();
16103     DisplayMove(currentMove - 1);
16104     DrawPosition(full_redraw, boards[currentMove]);
16105     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16106     // [HGM] PV info: routine tests if comment empty
16107     DisplayComment(currentMove - 1, commentList[currentMove]);
16108     ClearMap(); // [HGM] exclude: invalidate map
16109 }
16110
16111 void
16112 BackwardEvent ()
16113 {
16114     if (gameMode == IcsExamining && !pausing) {
16115         SendToICS(ics_prefix);
16116         SendToICS("backward\n");
16117     } else {
16118         BackwardInner(currentMove - 1);
16119     }
16120 }
16121
16122 void
16123 ToStartEvent ()
16124 {
16125     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16126         /* to optimize, we temporarily turn off analysis mode while we undo
16127          * all the moves. Otherwise we get analysis output after each undo.
16128          */
16129         if (first.analysisSupport) {
16130           SendToProgram("exit\nforce\n", &first);
16131           first.analyzing = FALSE;
16132         }
16133     }
16134
16135     if (gameMode == IcsExamining && !pausing) {
16136         SendToICS(ics_prefix);
16137         SendToICS("backward 999999\n");
16138     } else {
16139         BackwardInner(backwardMostMove);
16140     }
16141
16142     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16143         /* we have fed all the moves, so reactivate analysis mode */
16144         SendToProgram("analyze\n", &first);
16145         first.analyzing = TRUE;
16146         /*first.maybeThinking = TRUE;*/
16147         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16148     }
16149 }
16150
16151 void
16152 ToNrEvent (int to)
16153 {
16154   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16155   if (to >= forwardMostMove) to = forwardMostMove;
16156   if (to <= backwardMostMove) to = backwardMostMove;
16157   if (to < currentMove) {
16158     BackwardInner(to);
16159   } else {
16160     ForwardInner(to);
16161   }
16162 }
16163
16164 void
16165 RevertEvent (Boolean annotate)
16166 {
16167     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16168         return;
16169     }
16170     if (gameMode != IcsExamining) {
16171         DisplayError(_("You are not examining a game"), 0);
16172         return;
16173     }
16174     if (pausing) {
16175         DisplayError(_("You can't revert while pausing"), 0);
16176         return;
16177     }
16178     SendToICS(ics_prefix);
16179     SendToICS("revert\n");
16180 }
16181
16182 void
16183 RetractMoveEvent ()
16184 {
16185     switch (gameMode) {
16186       case MachinePlaysWhite:
16187       case MachinePlaysBlack:
16188         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16189             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16190             return;
16191         }
16192         if (forwardMostMove < 2) return;
16193         currentMove = forwardMostMove = forwardMostMove - 2;
16194         whiteTimeRemaining = timeRemaining[0][currentMove];
16195         blackTimeRemaining = timeRemaining[1][currentMove];
16196         DisplayBothClocks();
16197         DisplayMove(currentMove - 1);
16198         ClearHighlights();/*!! could figure this out*/
16199         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16200         SendToProgram("remove\n", &first);
16201         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16202         break;
16203
16204       case BeginningOfGame:
16205       default:
16206         break;
16207
16208       case IcsPlayingWhite:
16209       case IcsPlayingBlack:
16210         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16211             SendToICS(ics_prefix);
16212             SendToICS("takeback 2\n");
16213         } else {
16214             SendToICS(ics_prefix);
16215             SendToICS("takeback 1\n");
16216         }
16217         break;
16218     }
16219 }
16220
16221 void
16222 MoveNowEvent ()
16223 {
16224     ChessProgramState *cps;
16225
16226     switch (gameMode) {
16227       case MachinePlaysWhite:
16228         if (!WhiteOnMove(forwardMostMove)) {
16229             DisplayError(_("It is your turn"), 0);
16230             return;
16231         }
16232         cps = &first;
16233         break;
16234       case MachinePlaysBlack:
16235         if (WhiteOnMove(forwardMostMove)) {
16236             DisplayError(_("It is your turn"), 0);
16237             return;
16238         }
16239         cps = &first;
16240         break;
16241       case TwoMachinesPlay:
16242         if (WhiteOnMove(forwardMostMove) ==
16243             (first.twoMachinesColor[0] == 'w')) {
16244             cps = &first;
16245         } else {
16246             cps = &second;
16247         }
16248         break;
16249       case BeginningOfGame:
16250       default:
16251         return;
16252     }
16253     SendToProgram("?\n", cps);
16254 }
16255
16256 void
16257 TruncateGameEvent ()
16258 {
16259     EditGameEvent();
16260     if (gameMode != EditGame) return;
16261     TruncateGame();
16262 }
16263
16264 void
16265 TruncateGame ()
16266 {
16267     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16268     if (forwardMostMove > currentMove) {
16269         if (gameInfo.resultDetails != NULL) {
16270             free(gameInfo.resultDetails);
16271             gameInfo.resultDetails = NULL;
16272             gameInfo.result = GameUnfinished;
16273         }
16274         forwardMostMove = currentMove;
16275         HistorySet(parseList, backwardMostMove, forwardMostMove,
16276                    currentMove-1);
16277     }
16278 }
16279
16280 void
16281 HintEvent ()
16282 {
16283     if (appData.noChessProgram) return;
16284     switch (gameMode) {
16285       case MachinePlaysWhite:
16286         if (WhiteOnMove(forwardMostMove)) {
16287             DisplayError(_("Wait until your turn."), 0);
16288             return;
16289         }
16290         break;
16291       case BeginningOfGame:
16292       case MachinePlaysBlack:
16293         if (!WhiteOnMove(forwardMostMove)) {
16294             DisplayError(_("Wait until your turn."), 0);
16295             return;
16296         }
16297         break;
16298       default:
16299         DisplayError(_("No hint available"), 0);
16300         return;
16301     }
16302     SendToProgram("hint\n", &first);
16303     hintRequested = TRUE;
16304 }
16305
16306 int
16307 SaveSelected (FILE *g, int dummy, char *dummy2)
16308 {
16309     ListGame * lg = (ListGame *) gameList.head;
16310     int nItem, cnt=0;
16311     FILE *f;
16312
16313     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16314         DisplayError(_("Game list not loaded or empty"), 0);
16315         return 0;
16316     }
16317
16318     creatingBook = TRUE; // suppresses stuff during load game
16319
16320     /* Get list size */
16321     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16322         if(lg->position >= 0) { // selected?
16323             LoadGame(f, nItem, "", TRUE);
16324             SaveGamePGN2(g); // leaves g open
16325             cnt++; DoEvents();
16326         }
16327         lg = (ListGame *) lg->node.succ;
16328     }
16329
16330     fclose(g);
16331     creatingBook = FALSE;
16332
16333     return cnt;
16334 }
16335
16336 void
16337 CreateBookEvent ()
16338 {
16339     ListGame * lg = (ListGame *) gameList.head;
16340     FILE *f, *g;
16341     int nItem;
16342     static int secondTime = FALSE;
16343
16344     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16345         DisplayError(_("Game list not loaded or empty"), 0);
16346         return;
16347     }
16348
16349     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16350         fclose(g);
16351         secondTime++;
16352         DisplayNote(_("Book file exists! Try again for overwrite."));
16353         return;
16354     }
16355
16356     creatingBook = TRUE;
16357     secondTime = FALSE;
16358
16359     /* Get list size */
16360     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16361         if(lg->position >= 0) {
16362             LoadGame(f, nItem, "", TRUE);
16363             AddGameToBook(TRUE);
16364             DoEvents();
16365         }
16366         lg = (ListGame *) lg->node.succ;
16367     }
16368
16369     creatingBook = FALSE;
16370     FlushBook();
16371 }
16372
16373 void
16374 BookEvent ()
16375 {
16376     if (appData.noChessProgram) return;
16377     switch (gameMode) {
16378       case MachinePlaysWhite:
16379         if (WhiteOnMove(forwardMostMove)) {
16380             DisplayError(_("Wait until your turn."), 0);
16381             return;
16382         }
16383         break;
16384       case BeginningOfGame:
16385       case MachinePlaysBlack:
16386         if (!WhiteOnMove(forwardMostMove)) {
16387             DisplayError(_("Wait until your turn."), 0);
16388             return;
16389         }
16390         break;
16391       case EditPosition:
16392         EditPositionDone(TRUE);
16393         break;
16394       case TwoMachinesPlay:
16395         return;
16396       default:
16397         break;
16398     }
16399     SendToProgram("bk\n", &first);
16400     bookOutput[0] = NULLCHAR;
16401     bookRequested = TRUE;
16402 }
16403
16404 void
16405 AboutGameEvent ()
16406 {
16407     char *tags = PGNTags(&gameInfo);
16408     TagsPopUp(tags, CmailMsg());
16409     free(tags);
16410 }
16411
16412 /* end button procedures */
16413
16414 void
16415 PrintPosition (FILE *fp, int move)
16416 {
16417     int i, j;
16418
16419     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16420         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16421             char c = PieceToChar(boards[move][i][j]);
16422             fputc(c == '?' ? '.' : c, fp);
16423             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16424         }
16425     }
16426     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16427       fprintf(fp, "white to play\n");
16428     else
16429       fprintf(fp, "black to play\n");
16430 }
16431
16432 void
16433 PrintOpponents (FILE *fp)
16434 {
16435     if (gameInfo.white != NULL) {
16436         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16437     } else {
16438         fprintf(fp, "\n");
16439     }
16440 }
16441
16442 /* Find last component of program's own name, using some heuristics */
16443 void
16444 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16445 {
16446     char *p, *q, c;
16447     int local = (strcmp(host, "localhost") == 0);
16448     while (!local && (p = strchr(prog, ';')) != NULL) {
16449         p++;
16450         while (*p == ' ') p++;
16451         prog = p;
16452     }
16453     if (*prog == '"' || *prog == '\'') {
16454         q = strchr(prog + 1, *prog);
16455     } else {
16456         q = strchr(prog, ' ');
16457     }
16458     if (q == NULL) q = prog + strlen(prog);
16459     p = q;
16460     while (p >= prog && *p != '/' && *p != '\\') p--;
16461     p++;
16462     if(p == prog && *p == '"') p++;
16463     c = *q; *q = 0;
16464     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16465     memcpy(buf, p, q - p);
16466     buf[q - p] = NULLCHAR;
16467     if (!local) {
16468         strcat(buf, "@");
16469         strcat(buf, host);
16470     }
16471 }
16472
16473 char *
16474 TimeControlTagValue ()
16475 {
16476     char buf[MSG_SIZ];
16477     if (!appData.clockMode) {
16478       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16479     } else if (movesPerSession > 0) {
16480       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16481     } else if (timeIncrement == 0) {
16482       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16483     } else {
16484       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16485     }
16486     return StrSave(buf);
16487 }
16488
16489 void
16490 SetGameInfo ()
16491 {
16492     /* This routine is used only for certain modes */
16493     VariantClass v = gameInfo.variant;
16494     ChessMove r = GameUnfinished;
16495     char *p = NULL;
16496
16497     if(keepInfo) return;
16498
16499     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16500         r = gameInfo.result;
16501         p = gameInfo.resultDetails;
16502         gameInfo.resultDetails = NULL;
16503     }
16504     ClearGameInfo(&gameInfo);
16505     gameInfo.variant = v;
16506
16507     switch (gameMode) {
16508       case MachinePlaysWhite:
16509         gameInfo.event = StrSave( appData.pgnEventHeader );
16510         gameInfo.site = StrSave(HostName());
16511         gameInfo.date = PGNDate();
16512         gameInfo.round = StrSave("-");
16513         gameInfo.white = StrSave(first.tidy);
16514         gameInfo.black = StrSave(UserName());
16515         gameInfo.timeControl = TimeControlTagValue();
16516         break;
16517
16518       case MachinePlaysBlack:
16519         gameInfo.event = StrSave( appData.pgnEventHeader );
16520         gameInfo.site = StrSave(HostName());
16521         gameInfo.date = PGNDate();
16522         gameInfo.round = StrSave("-");
16523         gameInfo.white = StrSave(UserName());
16524         gameInfo.black = StrSave(first.tidy);
16525         gameInfo.timeControl = TimeControlTagValue();
16526         break;
16527
16528       case TwoMachinesPlay:
16529         gameInfo.event = StrSave( appData.pgnEventHeader );
16530         gameInfo.site = StrSave(HostName());
16531         gameInfo.date = PGNDate();
16532         if (roundNr > 0) {
16533             char buf[MSG_SIZ];
16534             snprintf(buf, MSG_SIZ, "%d", roundNr);
16535             gameInfo.round = StrSave(buf);
16536         } else {
16537             gameInfo.round = StrSave("-");
16538         }
16539         if (first.twoMachinesColor[0] == 'w') {
16540             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16541             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16542         } else {
16543             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16544             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16545         }
16546         gameInfo.timeControl = TimeControlTagValue();
16547         break;
16548
16549       case EditGame:
16550         gameInfo.event = StrSave("Edited game");
16551         gameInfo.site = StrSave(HostName());
16552         gameInfo.date = PGNDate();
16553         gameInfo.round = StrSave("-");
16554         gameInfo.white = StrSave("-");
16555         gameInfo.black = StrSave("-");
16556         gameInfo.result = r;
16557         gameInfo.resultDetails = p;
16558         break;
16559
16560       case EditPosition:
16561         gameInfo.event = StrSave("Edited position");
16562         gameInfo.site = StrSave(HostName());
16563         gameInfo.date = PGNDate();
16564         gameInfo.round = StrSave("-");
16565         gameInfo.white = StrSave("-");
16566         gameInfo.black = StrSave("-");
16567         break;
16568
16569       case IcsPlayingWhite:
16570       case IcsPlayingBlack:
16571       case IcsObserving:
16572       case IcsExamining:
16573         break;
16574
16575       case PlayFromGameFile:
16576         gameInfo.event = StrSave("Game from non-PGN file");
16577         gameInfo.site = StrSave(HostName());
16578         gameInfo.date = PGNDate();
16579         gameInfo.round = StrSave("-");
16580         gameInfo.white = StrSave("?");
16581         gameInfo.black = StrSave("?");
16582         break;
16583
16584       default:
16585         break;
16586     }
16587 }
16588
16589 void
16590 ReplaceComment (int index, char *text)
16591 {
16592     int len;
16593     char *p;
16594     float score;
16595
16596     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16597        pvInfoList[index-1].depth == len &&
16598        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16599        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16600     while (*text == '\n') text++;
16601     len = strlen(text);
16602     while (len > 0 && text[len - 1] == '\n') len--;
16603
16604     if (commentList[index] != NULL)
16605       free(commentList[index]);
16606
16607     if (len == 0) {
16608         commentList[index] = NULL;
16609         return;
16610     }
16611   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16612       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16613       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16614     commentList[index] = (char *) malloc(len + 2);
16615     strncpy(commentList[index], text, len);
16616     commentList[index][len] = '\n';
16617     commentList[index][len + 1] = NULLCHAR;
16618   } else {
16619     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16620     char *p;
16621     commentList[index] = (char *) malloc(len + 7);
16622     safeStrCpy(commentList[index], "{\n", 3);
16623     safeStrCpy(commentList[index]+2, text, len+1);
16624     commentList[index][len+2] = NULLCHAR;
16625     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16626     strcat(commentList[index], "\n}\n");
16627   }
16628 }
16629
16630 void
16631 CrushCRs (char *text)
16632 {
16633   char *p = text;
16634   char *q = text;
16635   char ch;
16636
16637   do {
16638     ch = *p++;
16639     if (ch == '\r') continue;
16640     *q++ = ch;
16641   } while (ch != '\0');
16642 }
16643
16644 void
16645 AppendComment (int index, char *text, Boolean addBraces)
16646 /* addBraces  tells if we should add {} */
16647 {
16648     int oldlen, len;
16649     char *old;
16650
16651 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16652     if(addBraces == 3) addBraces = 0; else // force appending literally
16653     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16654
16655     CrushCRs(text);
16656     while (*text == '\n') text++;
16657     len = strlen(text);
16658     while (len > 0 && text[len - 1] == '\n') len--;
16659     text[len] = NULLCHAR;
16660
16661     if (len == 0) return;
16662
16663     if (commentList[index] != NULL) {
16664       Boolean addClosingBrace = addBraces;
16665         old = commentList[index];
16666         oldlen = strlen(old);
16667         while(commentList[index][oldlen-1] ==  '\n')
16668           commentList[index][--oldlen] = NULLCHAR;
16669         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16670         safeStrCpy(commentList[index], old, oldlen + len + 6);
16671         free(old);
16672         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16673         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16674           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16675           while (*text == '\n') { text++; len--; }
16676           commentList[index][--oldlen] = NULLCHAR;
16677       }
16678         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16679         else          strcat(commentList[index], "\n");
16680         strcat(commentList[index], text);
16681         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16682         else          strcat(commentList[index], "\n");
16683     } else {
16684         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16685         if(addBraces)
16686           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16687         else commentList[index][0] = NULLCHAR;
16688         strcat(commentList[index], text);
16689         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16690         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16691     }
16692 }
16693
16694 static char *
16695 FindStr (char * text, char * sub_text)
16696 {
16697     char * result = strstr( text, sub_text );
16698
16699     if( result != NULL ) {
16700         result += strlen( sub_text );
16701     }
16702
16703     return result;
16704 }
16705
16706 /* [AS] Try to extract PV info from PGN comment */
16707 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16708 char *
16709 GetInfoFromComment (int index, char * text)
16710 {
16711     char * sep = text, *p;
16712
16713     if( text != NULL && index > 0 ) {
16714         int score = 0;
16715         int depth = 0;
16716         int time = -1, sec = 0, deci;
16717         char * s_eval = FindStr( text, "[%eval " );
16718         char * s_emt = FindStr( text, "[%emt " );
16719 #if 0
16720         if( s_eval != NULL || s_emt != NULL ) {
16721 #else
16722         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16723 #endif
16724             /* New style */
16725             char delim;
16726
16727             if( s_eval != NULL ) {
16728                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16729                     return text;
16730                 }
16731
16732                 if( delim != ']' ) {
16733                     return text;
16734                 }
16735             }
16736
16737             if( s_emt != NULL ) {
16738             }
16739                 return text;
16740         }
16741         else {
16742             /* We expect something like: [+|-]nnn.nn/dd */
16743             int score_lo = 0;
16744
16745             if(*text != '{') return text; // [HGM] braces: must be normal comment
16746
16747             sep = strchr( text, '/' );
16748             if( sep == NULL || sep < (text+4) ) {
16749                 return text;
16750             }
16751
16752             p = text;
16753             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16754             if(p[1] == '(') { // comment starts with PV
16755                p = strchr(p, ')'); // locate end of PV
16756                if(p == NULL || sep < p+5) return text;
16757                // at this point we have something like "{(.*) +0.23/6 ..."
16758                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16759                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16760                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16761             }
16762             time = -1; sec = -1; deci = -1;
16763             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16764                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16765                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16766                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16767                 return text;
16768             }
16769
16770             if( score_lo < 0 || score_lo >= 100 ) {
16771                 return text;
16772             }
16773
16774             if(sec >= 0) time = 600*time + 10*sec; else
16775             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16776
16777             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16778
16779             /* [HGM] PV time: now locate end of PV info */
16780             while( *++sep >= '0' && *sep <= '9'); // strip depth
16781             if(time >= 0)
16782             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16783             if(sec >= 0)
16784             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16785             if(deci >= 0)
16786             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16787             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16788         }
16789
16790         if( depth <= 0 ) {
16791             return text;
16792         }
16793
16794         if( time < 0 ) {
16795             time = -1;
16796         }
16797
16798         pvInfoList[index-1].depth = depth;
16799         pvInfoList[index-1].score = score;
16800         pvInfoList[index-1].time  = 10*time; // centi-sec
16801         if(*sep == '}') *sep = 0; else *--sep = '{';
16802         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16803     }
16804     return sep;
16805 }
16806
16807 void
16808 SendToProgram (char *message, ChessProgramState *cps)
16809 {
16810     int count, outCount, error;
16811     char buf[MSG_SIZ];
16812
16813     if (cps->pr == NoProc) return;
16814     Attention(cps);
16815
16816     if (appData.debugMode) {
16817         TimeMark now;
16818         GetTimeMark(&now);
16819         fprintf(debugFP, "%ld >%-6s: %s",
16820                 SubtractTimeMarks(&now, &programStartTime),
16821                 cps->which, message);
16822         if(serverFP)
16823             fprintf(serverFP, "%ld >%-6s: %s",
16824                 SubtractTimeMarks(&now, &programStartTime),
16825                 cps->which, message), fflush(serverFP);
16826     }
16827
16828     count = strlen(message);
16829     outCount = OutputToProcess(cps->pr, message, count, &error);
16830     if (outCount < count && !exiting
16831                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16832       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16833       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16834         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16835             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16836                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16837                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16838                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16839             } else {
16840                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16841                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16842                 gameInfo.result = res;
16843             }
16844             gameInfo.resultDetails = StrSave(buf);
16845         }
16846         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16847         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16848     }
16849 }
16850
16851 void
16852 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16853 {
16854     char *end_str;
16855     char buf[MSG_SIZ];
16856     ChessProgramState *cps = (ChessProgramState *)closure;
16857
16858     if (isr != cps->isr) return; /* Killed intentionally */
16859     if (count <= 0) {
16860         if (count == 0) {
16861             RemoveInputSource(cps->isr);
16862             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16863                     _(cps->which), cps->program);
16864             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16865             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16866                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16867                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16868                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16869                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16870                 } else {
16871                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16872                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16873                     gameInfo.result = res;
16874                 }
16875                 gameInfo.resultDetails = StrSave(buf);
16876             }
16877             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16878             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16879         } else {
16880             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16881                     _(cps->which), cps->program);
16882             RemoveInputSource(cps->isr);
16883
16884             /* [AS] Program is misbehaving badly... kill it */
16885             if( count == -2 ) {
16886                 DestroyChildProcess( cps->pr, 9 );
16887                 cps->pr = NoProc;
16888             }
16889
16890             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16891         }
16892         return;
16893     }
16894
16895     if ((end_str = strchr(message, '\r')) != NULL)
16896       *end_str = NULLCHAR;
16897     if ((end_str = strchr(message, '\n')) != NULL)
16898       *end_str = NULLCHAR;
16899
16900     if (appData.debugMode) {
16901         TimeMark now; int print = 1;
16902         char *quote = ""; char c; int i;
16903
16904         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16905                 char start = message[0];
16906                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16907                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16908                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16909                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16910                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16911                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16912                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16913                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16914                    sscanf(message, "hint: %c", &c)!=1 &&
16915                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16916                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16917                     print = (appData.engineComments >= 2);
16918                 }
16919                 message[0] = start; // restore original message
16920         }
16921         if(print) {
16922                 GetTimeMark(&now);
16923                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16924                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16925                         quote,
16926                         message);
16927                 if(serverFP)
16928                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16929                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16930                         quote,
16931                         message), fflush(serverFP);
16932         }
16933     }
16934
16935     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16936     if (appData.icsEngineAnalyze) {
16937         if (strstr(message, "whisper") != NULL ||
16938              strstr(message, "kibitz") != NULL ||
16939             strstr(message, "tellics") != NULL) return;
16940     }
16941
16942     HandleMachineMove(message, cps);
16943 }
16944
16945
16946 void
16947 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16948 {
16949     char buf[MSG_SIZ];
16950     int seconds;
16951
16952     if( timeControl_2 > 0 ) {
16953         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16954             tc = timeControl_2;
16955         }
16956     }
16957     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16958     inc /= cps->timeOdds;
16959     st  /= cps->timeOdds;
16960
16961     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16962
16963     if (st > 0) {
16964       /* Set exact time per move, normally using st command */
16965       if (cps->stKludge) {
16966         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16967         seconds = st % 60;
16968         if (seconds == 0) {
16969           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16970         } else {
16971           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16972         }
16973       } else {
16974         snprintf(buf, MSG_SIZ, "st %d\n", st);
16975       }
16976     } else {
16977       /* Set conventional or incremental time control, using level command */
16978       if (seconds == 0) {
16979         /* Note old gnuchess bug -- minutes:seconds used to not work.
16980            Fixed in later versions, but still avoid :seconds
16981            when seconds is 0. */
16982         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16983       } else {
16984         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16985                  seconds, inc/1000.);
16986       }
16987     }
16988     SendToProgram(buf, cps);
16989
16990     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16991     /* Orthogonally, limit search to given depth */
16992     if (sd > 0) {
16993       if (cps->sdKludge) {
16994         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16995       } else {
16996         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16997       }
16998       SendToProgram(buf, cps);
16999     }
17000
17001     if(cps->nps >= 0) { /* [HGM] nps */
17002         if(cps->supportsNPS == FALSE)
17003           cps->nps = -1; // don't use if engine explicitly says not supported!
17004         else {
17005           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17006           SendToProgram(buf, cps);
17007         }
17008     }
17009 }
17010
17011 ChessProgramState *
17012 WhitePlayer ()
17013 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17014 {
17015     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17016        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17017         return &second;
17018     return &first;
17019 }
17020
17021 void
17022 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17023 {
17024     char message[MSG_SIZ];
17025     long time, otime;
17026
17027     /* Note: this routine must be called when the clocks are stopped
17028        or when they have *just* been set or switched; otherwise
17029        it will be off by the time since the current tick started.
17030     */
17031     if (machineWhite) {
17032         time = whiteTimeRemaining / 10;
17033         otime = blackTimeRemaining / 10;
17034     } else {
17035         time = blackTimeRemaining / 10;
17036         otime = whiteTimeRemaining / 10;
17037     }
17038     /* [HGM] translate opponent's time by time-odds factor */
17039     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17040
17041     if (time <= 0) time = 1;
17042     if (otime <= 0) otime = 1;
17043
17044     snprintf(message, MSG_SIZ, "time %ld\n", time);
17045     SendToProgram(message, cps);
17046
17047     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17048     SendToProgram(message, cps);
17049 }
17050
17051 char *
17052 EngineDefinedVariant (ChessProgramState *cps, int n)
17053 {   // return name of n-th unknown variant that engine supports
17054     static char buf[MSG_SIZ];
17055     char *p, *s = cps->variants;
17056     if(!s) return NULL;
17057     do { // parse string from variants feature
17058       VariantClass v;
17059         p = strchr(s, ',');
17060         if(p) *p = NULLCHAR;
17061       v = StringToVariant(s);
17062       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17063         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17064             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17065                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17066                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17067                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17068             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17069         }
17070         if(p) *p++ = ',';
17071         if(n < 0) return buf;
17072     } while(s = p);
17073     return NULL;
17074 }
17075
17076 int
17077 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17078 {
17079   char buf[MSG_SIZ];
17080   int len = strlen(name);
17081   int val;
17082
17083   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17084     (*p) += len + 1;
17085     sscanf(*p, "%d", &val);
17086     *loc = (val != 0);
17087     while (**p && **p != ' ')
17088       (*p)++;
17089     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17090     SendToProgram(buf, cps);
17091     return TRUE;
17092   }
17093   return FALSE;
17094 }
17095
17096 int
17097 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17098 {
17099   char buf[MSG_SIZ];
17100   int len = strlen(name);
17101   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17102     (*p) += len + 1;
17103     sscanf(*p, "%d", loc);
17104     while (**p && **p != ' ') (*p)++;
17105     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17106     SendToProgram(buf, cps);
17107     return TRUE;
17108   }
17109   return FALSE;
17110 }
17111
17112 int
17113 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17114 {
17115   char buf[MSG_SIZ];
17116   int len = strlen(name);
17117   if (strncmp((*p), name, len) == 0
17118       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17119     (*p) += len + 2;
17120     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17121     sscanf(*p, "%[^\"]", *loc);
17122     while (**p && **p != '\"') (*p)++;
17123     if (**p == '\"') (*p)++;
17124     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17125     SendToProgram(buf, cps);
17126     return TRUE;
17127   }
17128   return FALSE;
17129 }
17130
17131 int
17132 ParseOption (Option *opt, ChessProgramState *cps)
17133 // [HGM] options: process the string that defines an engine option, and determine
17134 // name, type, default value, and allowed value range
17135 {
17136         char *p, *q, buf[MSG_SIZ];
17137         int n, min = (-1)<<31, max = 1<<31, def;
17138
17139         opt->target = &opt->value;   // OK for spin/slider and checkbox
17140         if(p = strstr(opt->name, " -spin ")) {
17141             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17142             if(max < min) max = min; // enforce consistency
17143             if(def < min) def = min;
17144             if(def > max) def = max;
17145             opt->value = def;
17146             opt->min = min;
17147             opt->max = max;
17148             opt->type = Spin;
17149         } else if((p = strstr(opt->name, " -slider "))) {
17150             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17151             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17152             if(max < min) max = min; // enforce consistency
17153             if(def < min) def = min;
17154             if(def > max) def = max;
17155             opt->value = def;
17156             opt->min = min;
17157             opt->max = max;
17158             opt->type = Spin; // Slider;
17159         } else if((p = strstr(opt->name, " -string "))) {
17160             opt->textValue = p+9;
17161             opt->type = TextBox;
17162             opt->target = &opt->textValue;
17163         } else if((p = strstr(opt->name, " -file "))) {
17164             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17165             opt->target = opt->textValue = p+7;
17166             opt->type = FileName; // FileName;
17167             opt->target = &opt->textValue;
17168         } else if((p = strstr(opt->name, " -path "))) {
17169             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17170             opt->target = opt->textValue = p+7;
17171             opt->type = PathName; // PathName;
17172             opt->target = &opt->textValue;
17173         } else if(p = strstr(opt->name, " -check ")) {
17174             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17175             opt->value = (def != 0);
17176             opt->type = CheckBox;
17177         } else if(p = strstr(opt->name, " -combo ")) {
17178             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17179             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17180             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17181             opt->value = n = 0;
17182             while(q = StrStr(q, " /// ")) {
17183                 n++; *q = 0;    // count choices, and null-terminate each of them
17184                 q += 5;
17185                 if(*q == '*') { // remember default, which is marked with * prefix
17186                     q++;
17187                     opt->value = n;
17188                 }
17189                 cps->comboList[cps->comboCnt++] = q;
17190             }
17191             cps->comboList[cps->comboCnt++] = NULL;
17192             opt->max = n + 1;
17193             opt->type = ComboBox;
17194         } else if(p = strstr(opt->name, " -button")) {
17195             opt->type = Button;
17196         } else if(p = strstr(opt->name, " -save")) {
17197             opt->type = SaveButton;
17198         } else return FALSE;
17199         *p = 0; // terminate option name
17200         // now look if the command-line options define a setting for this engine option.
17201         if(cps->optionSettings && cps->optionSettings[0])
17202             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17203         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17204           snprintf(buf, MSG_SIZ, "option %s", p);
17205                 if(p = strstr(buf, ",")) *p = 0;
17206                 if(q = strchr(buf, '=')) switch(opt->type) {
17207                     case ComboBox:
17208                         for(n=0; n<opt->max; n++)
17209                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17210                         break;
17211                     case TextBox:
17212                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17213                         break;
17214                     case Spin:
17215                     case CheckBox:
17216                         opt->value = atoi(q+1);
17217                     default:
17218                         break;
17219                 }
17220                 strcat(buf, "\n");
17221                 SendToProgram(buf, cps);
17222         }
17223         return TRUE;
17224 }
17225
17226 void
17227 FeatureDone (ChessProgramState *cps, int val)
17228 {
17229   DelayedEventCallback cb = GetDelayedEvent();
17230   if ((cb == InitBackEnd3 && cps == &first) ||
17231       (cb == SettingsMenuIfReady && cps == &second) ||
17232       (cb == LoadEngine) ||
17233       (cb == TwoMachinesEventIfReady)) {
17234     CancelDelayedEvent();
17235     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17236   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17237   cps->initDone = val;
17238   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17239 }
17240
17241 /* Parse feature command from engine */
17242 void
17243 ParseFeatures (char *args, ChessProgramState *cps)
17244 {
17245   char *p = args;
17246   char *q = NULL;
17247   int val;
17248   char buf[MSG_SIZ];
17249
17250   for (;;) {
17251     while (*p == ' ') p++;
17252     if (*p == NULLCHAR) return;
17253
17254     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17255     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17256     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17257     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17258     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17259     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17260     if (BoolFeature(&p, "reuse", &val, cps)) {
17261       /* Engine can disable reuse, but can't enable it if user said no */
17262       if (!val) cps->reuse = FALSE;
17263       continue;
17264     }
17265     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17266     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17267       if (gameMode == TwoMachinesPlay) {
17268         DisplayTwoMachinesTitle();
17269       } else {
17270         DisplayTitle("");
17271       }
17272       continue;
17273     }
17274     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17275     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17276     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17277     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17278     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17279     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17280     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17281     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17282     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17283     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17284     if (IntFeature(&p, "done", &val, cps)) {
17285       FeatureDone(cps, val);
17286       continue;
17287     }
17288     /* Added by Tord: */
17289     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17290     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17291     /* End of additions by Tord */
17292
17293     /* [HGM] added features: */
17294     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17295     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17296     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17297     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17298     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17299     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17300     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17301     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17302         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17303         FREE(cps->option[cps->nrOptions].name);
17304         cps->option[cps->nrOptions].name = q; q = NULL;
17305         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17306           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17307             SendToProgram(buf, cps);
17308             continue;
17309         }
17310         if(cps->nrOptions >= MAX_OPTIONS) {
17311             cps->nrOptions--;
17312             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17313             DisplayError(buf, 0);
17314         }
17315         continue;
17316     }
17317     /* End of additions by HGM */
17318
17319     /* unknown feature: complain and skip */
17320     q = p;
17321     while (*q && *q != '=') q++;
17322     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17323     SendToProgram(buf, cps);
17324     p = q;
17325     if (*p == '=') {
17326       p++;
17327       if (*p == '\"') {
17328         p++;
17329         while (*p && *p != '\"') p++;
17330         if (*p == '\"') p++;
17331       } else {
17332         while (*p && *p != ' ') p++;
17333       }
17334     }
17335   }
17336
17337 }
17338
17339 void
17340 PeriodicUpdatesEvent (int newState)
17341 {
17342     if (newState == appData.periodicUpdates)
17343       return;
17344
17345     appData.periodicUpdates=newState;
17346
17347     /* Display type changes, so update it now */
17348 //    DisplayAnalysis();
17349
17350     /* Get the ball rolling again... */
17351     if (newState) {
17352         AnalysisPeriodicEvent(1);
17353         StartAnalysisClock();
17354     }
17355 }
17356
17357 void
17358 PonderNextMoveEvent (int newState)
17359 {
17360     if (newState == appData.ponderNextMove) return;
17361     if (gameMode == EditPosition) EditPositionDone(TRUE);
17362     if (newState) {
17363         SendToProgram("hard\n", &first);
17364         if (gameMode == TwoMachinesPlay) {
17365             SendToProgram("hard\n", &second);
17366         }
17367     } else {
17368         SendToProgram("easy\n", &first);
17369         thinkOutput[0] = NULLCHAR;
17370         if (gameMode == TwoMachinesPlay) {
17371             SendToProgram("easy\n", &second);
17372         }
17373     }
17374     appData.ponderNextMove = newState;
17375 }
17376
17377 void
17378 NewSettingEvent (int option, int *feature, char *command, int value)
17379 {
17380     char buf[MSG_SIZ];
17381
17382     if (gameMode == EditPosition) EditPositionDone(TRUE);
17383     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17384     if(feature == NULL || *feature) SendToProgram(buf, &first);
17385     if (gameMode == TwoMachinesPlay) {
17386         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17387     }
17388 }
17389
17390 void
17391 ShowThinkingEvent ()
17392 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17393 {
17394     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17395     int newState = appData.showThinking
17396         // [HGM] thinking: other features now need thinking output as well
17397         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17398
17399     if (oldState == newState) return;
17400     oldState = newState;
17401     if (gameMode == EditPosition) EditPositionDone(TRUE);
17402     if (oldState) {
17403         SendToProgram("post\n", &first);
17404         if (gameMode == TwoMachinesPlay) {
17405             SendToProgram("post\n", &second);
17406         }
17407     } else {
17408         SendToProgram("nopost\n", &first);
17409         thinkOutput[0] = NULLCHAR;
17410         if (gameMode == TwoMachinesPlay) {
17411             SendToProgram("nopost\n", &second);
17412         }
17413     }
17414 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17415 }
17416
17417 void
17418 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17419 {
17420   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17421   if (pr == NoProc) return;
17422   AskQuestion(title, question, replyPrefix, pr);
17423 }
17424
17425 void
17426 TypeInEvent (char firstChar)
17427 {
17428     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17429         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17430         gameMode == AnalyzeMode || gameMode == EditGame ||
17431         gameMode == EditPosition || gameMode == IcsExamining ||
17432         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17433         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17434                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17435                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17436         gameMode == Training) PopUpMoveDialog(firstChar);
17437 }
17438
17439 void
17440 TypeInDoneEvent (char *move)
17441 {
17442         Board board;
17443         int n, fromX, fromY, toX, toY;
17444         char promoChar;
17445         ChessMove moveType;
17446
17447         // [HGM] FENedit
17448         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17449                 EditPositionPasteFEN(move);
17450                 return;
17451         }
17452         // [HGM] movenum: allow move number to be typed in any mode
17453         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17454           ToNrEvent(2*n-1);
17455           return;
17456         }
17457         // undocumented kludge: allow command-line option to be typed in!
17458         // (potentially fatal, and does not implement the effect of the option.)
17459         // should only be used for options that are values on which future decisions will be made,
17460         // and definitely not on options that would be used during initialization.
17461         if(strstr(move, "!!! -") == move) {
17462             ParseArgsFromString(move+4);
17463             return;
17464         }
17465
17466       if (gameMode != EditGame && currentMove != forwardMostMove &&
17467         gameMode != Training) {
17468         DisplayMoveError(_("Displayed move is not current"));
17469       } else {
17470         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17471           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17472         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17473         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17474           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17475           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17476         } else {
17477           DisplayMoveError(_("Could not parse move"));
17478         }
17479       }
17480 }
17481
17482 void
17483 DisplayMove (int moveNumber)
17484 {
17485     char message[MSG_SIZ];
17486     char res[MSG_SIZ];
17487     char cpThinkOutput[MSG_SIZ];
17488
17489     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17490
17491     if (moveNumber == forwardMostMove - 1 ||
17492         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17493
17494         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17495
17496         if (strchr(cpThinkOutput, '\n')) {
17497             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17498         }
17499     } else {
17500         *cpThinkOutput = NULLCHAR;
17501     }
17502
17503     /* [AS] Hide thinking from human user */
17504     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17505         *cpThinkOutput = NULLCHAR;
17506         if( thinkOutput[0] != NULLCHAR ) {
17507             int i;
17508
17509             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17510                 cpThinkOutput[i] = '.';
17511             }
17512             cpThinkOutput[i] = NULLCHAR;
17513             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17514         }
17515     }
17516
17517     if (moveNumber == forwardMostMove - 1 &&
17518         gameInfo.resultDetails != NULL) {
17519         if (gameInfo.resultDetails[0] == NULLCHAR) {
17520           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17521         } else {
17522           snprintf(res, MSG_SIZ, " {%s} %s",
17523                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17524         }
17525     } else {
17526         res[0] = NULLCHAR;
17527     }
17528
17529     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17530         DisplayMessage(res, cpThinkOutput);
17531     } else {
17532       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17533                 WhiteOnMove(moveNumber) ? " " : ".. ",
17534                 parseList[moveNumber], res);
17535         DisplayMessage(message, cpThinkOutput);
17536     }
17537 }
17538
17539 void
17540 DisplayComment (int moveNumber, char *text)
17541 {
17542     char title[MSG_SIZ];
17543
17544     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17545       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17546     } else {
17547       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17548               WhiteOnMove(moveNumber) ? " " : ".. ",
17549               parseList[moveNumber]);
17550     }
17551     if (text != NULL && (appData.autoDisplayComment || commentUp))
17552         CommentPopUp(title, text);
17553 }
17554
17555 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17556  * might be busy thinking or pondering.  It can be omitted if your
17557  * gnuchess is configured to stop thinking immediately on any user
17558  * input.  However, that gnuchess feature depends on the FIONREAD
17559  * ioctl, which does not work properly on some flavors of Unix.
17560  */
17561 void
17562 Attention (ChessProgramState *cps)
17563 {
17564 #if ATTENTION
17565     if (!cps->useSigint) return;
17566     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17567     switch (gameMode) {
17568       case MachinePlaysWhite:
17569       case MachinePlaysBlack:
17570       case TwoMachinesPlay:
17571       case IcsPlayingWhite:
17572       case IcsPlayingBlack:
17573       case AnalyzeMode:
17574       case AnalyzeFile:
17575         /* Skip if we know it isn't thinking */
17576         if (!cps->maybeThinking) return;
17577         if (appData.debugMode)
17578           fprintf(debugFP, "Interrupting %s\n", cps->which);
17579         InterruptChildProcess(cps->pr);
17580         cps->maybeThinking = FALSE;
17581         break;
17582       default:
17583         break;
17584     }
17585 #endif /*ATTENTION*/
17586 }
17587
17588 int
17589 CheckFlags ()
17590 {
17591     if (whiteTimeRemaining <= 0) {
17592         if (!whiteFlag) {
17593             whiteFlag = TRUE;
17594             if (appData.icsActive) {
17595                 if (appData.autoCallFlag &&
17596                     gameMode == IcsPlayingBlack && !blackFlag) {
17597                   SendToICS(ics_prefix);
17598                   SendToICS("flag\n");
17599                 }
17600             } else {
17601                 if (blackFlag) {
17602                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17603                 } else {
17604                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17605                     if (appData.autoCallFlag) {
17606                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17607                         return TRUE;
17608                     }
17609                 }
17610             }
17611         }
17612     }
17613     if (blackTimeRemaining <= 0) {
17614         if (!blackFlag) {
17615             blackFlag = TRUE;
17616             if (appData.icsActive) {
17617                 if (appData.autoCallFlag &&
17618                     gameMode == IcsPlayingWhite && !whiteFlag) {
17619                   SendToICS(ics_prefix);
17620                   SendToICS("flag\n");
17621                 }
17622             } else {
17623                 if (whiteFlag) {
17624                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17625                 } else {
17626                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17627                     if (appData.autoCallFlag) {
17628                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17629                         return TRUE;
17630                     }
17631                 }
17632             }
17633         }
17634     }
17635     return FALSE;
17636 }
17637
17638 void
17639 CheckTimeControl ()
17640 {
17641     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17642         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17643
17644     /*
17645      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17646      */
17647     if ( !WhiteOnMove(forwardMostMove) ) {
17648         /* White made time control */
17649         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17650         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17651         /* [HGM] time odds: correct new time quota for time odds! */
17652                                             / WhitePlayer()->timeOdds;
17653         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17654     } else {
17655         lastBlack -= blackTimeRemaining;
17656         /* Black made time control */
17657         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17658                                             / WhitePlayer()->other->timeOdds;
17659         lastWhite = whiteTimeRemaining;
17660     }
17661 }
17662
17663 void
17664 DisplayBothClocks ()
17665 {
17666     int wom = gameMode == EditPosition ?
17667       !blackPlaysFirst : WhiteOnMove(currentMove);
17668     DisplayWhiteClock(whiteTimeRemaining, wom);
17669     DisplayBlackClock(blackTimeRemaining, !wom);
17670 }
17671
17672
17673 /* Timekeeping seems to be a portability nightmare.  I think everyone
17674    has ftime(), but I'm really not sure, so I'm including some ifdefs
17675    to use other calls if you don't.  Clocks will be less accurate if
17676    you have neither ftime nor gettimeofday.
17677 */
17678
17679 /* VS 2008 requires the #include outside of the function */
17680 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17681 #include <sys/timeb.h>
17682 #endif
17683
17684 /* Get the current time as a TimeMark */
17685 void
17686 GetTimeMark (TimeMark *tm)
17687 {
17688 #if HAVE_GETTIMEOFDAY
17689
17690     struct timeval timeVal;
17691     struct timezone timeZone;
17692
17693     gettimeofday(&timeVal, &timeZone);
17694     tm->sec = (long) timeVal.tv_sec;
17695     tm->ms = (int) (timeVal.tv_usec / 1000L);
17696
17697 #else /*!HAVE_GETTIMEOFDAY*/
17698 #if HAVE_FTIME
17699
17700 // include <sys/timeb.h> / moved to just above start of function
17701     struct timeb timeB;
17702
17703     ftime(&timeB);
17704     tm->sec = (long) timeB.time;
17705     tm->ms = (int) timeB.millitm;
17706
17707 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17708     tm->sec = (long) time(NULL);
17709     tm->ms = 0;
17710 #endif
17711 #endif
17712 }
17713
17714 /* Return the difference in milliseconds between two
17715    time marks.  We assume the difference will fit in a long!
17716 */
17717 long
17718 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17719 {
17720     return 1000L*(tm2->sec - tm1->sec) +
17721            (long) (tm2->ms - tm1->ms);
17722 }
17723
17724
17725 /*
17726  * Code to manage the game clocks.
17727  *
17728  * In tournament play, black starts the clock and then white makes a move.
17729  * We give the human user a slight advantage if he is playing white---the
17730  * clocks don't run until he makes his first move, so it takes zero time.
17731  * Also, we don't account for network lag, so we could get out of sync
17732  * with GNU Chess's clock -- but then, referees are always right.
17733  */
17734
17735 static TimeMark tickStartTM;
17736 static long intendedTickLength;
17737
17738 long
17739 NextTickLength (long timeRemaining)
17740 {
17741     long nominalTickLength, nextTickLength;
17742
17743     if (timeRemaining > 0L && timeRemaining <= 10000L)
17744       nominalTickLength = 100L;
17745     else
17746       nominalTickLength = 1000L;
17747     nextTickLength = timeRemaining % nominalTickLength;
17748     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17749
17750     return nextTickLength;
17751 }
17752
17753 /* Adjust clock one minute up or down */
17754 void
17755 AdjustClock (Boolean which, int dir)
17756 {
17757     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17758     if(which) blackTimeRemaining += 60000*dir;
17759     else      whiteTimeRemaining += 60000*dir;
17760     DisplayBothClocks();
17761     adjustedClock = TRUE;
17762 }
17763
17764 /* Stop clocks and reset to a fresh time control */
17765 void
17766 ResetClocks ()
17767 {
17768     (void) StopClockTimer();
17769     if (appData.icsActive) {
17770         whiteTimeRemaining = blackTimeRemaining = 0;
17771     } else if (searchTime) {
17772         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17773         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17774     } else { /* [HGM] correct new time quote for time odds */
17775         whiteTC = blackTC = fullTimeControlString;
17776         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17777         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17778     }
17779     if (whiteFlag || blackFlag) {
17780         DisplayTitle("");
17781         whiteFlag = blackFlag = FALSE;
17782     }
17783     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17784     DisplayBothClocks();
17785     adjustedClock = FALSE;
17786 }
17787
17788 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17789
17790 /* Decrement running clock by amount of time that has passed */
17791 void
17792 DecrementClocks ()
17793 {
17794     long timeRemaining;
17795     long lastTickLength, fudge;
17796     TimeMark now;
17797
17798     if (!appData.clockMode) return;
17799     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17800
17801     GetTimeMark(&now);
17802
17803     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17804
17805     /* Fudge if we woke up a little too soon */
17806     fudge = intendedTickLength - lastTickLength;
17807     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17808
17809     if (WhiteOnMove(forwardMostMove)) {
17810         if(whiteNPS >= 0) lastTickLength = 0;
17811         timeRemaining = whiteTimeRemaining -= lastTickLength;
17812         if(timeRemaining < 0 && !appData.icsActive) {
17813             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17814             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17815                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17816                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17817             }
17818         }
17819         DisplayWhiteClock(whiteTimeRemaining - fudge,
17820                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17821     } else {
17822         if(blackNPS >= 0) lastTickLength = 0;
17823         timeRemaining = blackTimeRemaining -= lastTickLength;
17824         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17825             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17826             if(suddenDeath) {
17827                 blackStartMove = forwardMostMove;
17828                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17829             }
17830         }
17831         DisplayBlackClock(blackTimeRemaining - fudge,
17832                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17833     }
17834     if (CheckFlags()) return;
17835
17836     if(twoBoards) { // count down secondary board's clocks as well
17837         activePartnerTime -= lastTickLength;
17838         partnerUp = 1;
17839         if(activePartner == 'W')
17840             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17841         else
17842             DisplayBlackClock(activePartnerTime, TRUE);
17843         partnerUp = 0;
17844     }
17845
17846     tickStartTM = now;
17847     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17848     StartClockTimer(intendedTickLength);
17849
17850     /* if the time remaining has fallen below the alarm threshold, sound the
17851      * alarm. if the alarm has sounded and (due to a takeback or time control
17852      * with increment) the time remaining has increased to a level above the
17853      * threshold, reset the alarm so it can sound again.
17854      */
17855
17856     if (appData.icsActive && appData.icsAlarm) {
17857
17858         /* make sure we are dealing with the user's clock */
17859         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17860                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17861            )) return;
17862
17863         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17864             alarmSounded = FALSE;
17865         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17866             PlayAlarmSound();
17867             alarmSounded = TRUE;
17868         }
17869     }
17870 }
17871
17872
17873 /* A player has just moved, so stop the previously running
17874    clock and (if in clock mode) start the other one.
17875    We redisplay both clocks in case we're in ICS mode, because
17876    ICS gives us an update to both clocks after every move.
17877    Note that this routine is called *after* forwardMostMove
17878    is updated, so the last fractional tick must be subtracted
17879    from the color that is *not* on move now.
17880 */
17881 void
17882 SwitchClocks (int newMoveNr)
17883 {
17884     long lastTickLength;
17885     TimeMark now;
17886     int flagged = FALSE;
17887
17888     GetTimeMark(&now);
17889
17890     if (StopClockTimer() && appData.clockMode) {
17891         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17892         if (!WhiteOnMove(forwardMostMove)) {
17893             if(blackNPS >= 0) lastTickLength = 0;
17894             blackTimeRemaining -= lastTickLength;
17895            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17896 //         if(pvInfoList[forwardMostMove].time == -1)
17897                  pvInfoList[forwardMostMove].time =               // use GUI time
17898                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17899         } else {
17900            if(whiteNPS >= 0) lastTickLength = 0;
17901            whiteTimeRemaining -= lastTickLength;
17902            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17903 //         if(pvInfoList[forwardMostMove].time == -1)
17904                  pvInfoList[forwardMostMove].time =
17905                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17906         }
17907         flagged = CheckFlags();
17908     }
17909     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17910     CheckTimeControl();
17911
17912     if (flagged || !appData.clockMode) return;
17913
17914     switch (gameMode) {
17915       case MachinePlaysBlack:
17916       case MachinePlaysWhite:
17917       case BeginningOfGame:
17918         if (pausing) return;
17919         break;
17920
17921       case EditGame:
17922       case PlayFromGameFile:
17923       case IcsExamining:
17924         return;
17925
17926       default:
17927         break;
17928     }
17929
17930     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17931         if(WhiteOnMove(forwardMostMove))
17932              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17933         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17934     }
17935
17936     tickStartTM = now;
17937     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17938       whiteTimeRemaining : blackTimeRemaining);
17939     StartClockTimer(intendedTickLength);
17940 }
17941
17942
17943 /* Stop both clocks */
17944 void
17945 StopClocks ()
17946 {
17947     long lastTickLength;
17948     TimeMark now;
17949
17950     if (!StopClockTimer()) return;
17951     if (!appData.clockMode) return;
17952
17953     GetTimeMark(&now);
17954
17955     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17956     if (WhiteOnMove(forwardMostMove)) {
17957         if(whiteNPS >= 0) lastTickLength = 0;
17958         whiteTimeRemaining -= lastTickLength;
17959         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17960     } else {
17961         if(blackNPS >= 0) lastTickLength = 0;
17962         blackTimeRemaining -= lastTickLength;
17963         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17964     }
17965     CheckFlags();
17966 }
17967
17968 /* Start clock of player on move.  Time may have been reset, so
17969    if clock is already running, stop and restart it. */
17970 void
17971 StartClocks ()
17972 {
17973     (void) StopClockTimer(); /* in case it was running already */
17974     DisplayBothClocks();
17975     if (CheckFlags()) return;
17976
17977     if (!appData.clockMode) return;
17978     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17979
17980     GetTimeMark(&tickStartTM);
17981     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17982       whiteTimeRemaining : blackTimeRemaining);
17983
17984    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17985     whiteNPS = blackNPS = -1;
17986     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17987        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17988         whiteNPS = first.nps;
17989     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17990        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17991         blackNPS = first.nps;
17992     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17993         whiteNPS = second.nps;
17994     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17995         blackNPS = second.nps;
17996     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17997
17998     StartClockTimer(intendedTickLength);
17999 }
18000
18001 char *
18002 TimeString (long ms)
18003 {
18004     long second, minute, hour, day;
18005     char *sign = "";
18006     static char buf[32];
18007
18008     if (ms > 0 && ms <= 9900) {
18009       /* convert milliseconds to tenths, rounding up */
18010       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18011
18012       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18013       return buf;
18014     }
18015
18016     /* convert milliseconds to seconds, rounding up */
18017     /* use floating point to avoid strangeness of integer division
18018        with negative dividends on many machines */
18019     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18020
18021     if (second < 0) {
18022         sign = "-";
18023         second = -second;
18024     }
18025
18026     day = second / (60 * 60 * 24);
18027     second = second % (60 * 60 * 24);
18028     hour = second / (60 * 60);
18029     second = second % (60 * 60);
18030     minute = second / 60;
18031     second = second % 60;
18032
18033     if (day > 0)
18034       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
18035               sign, day, hour, minute, second);
18036     else if (hour > 0)
18037       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
18038     else
18039       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
18040
18041     return buf;
18042 }
18043
18044
18045 /*
18046  * This is necessary because some C libraries aren't ANSI C compliant yet.
18047  */
18048 char *
18049 StrStr (char *string, char *match)
18050 {
18051     int i, length;
18052
18053     length = strlen(match);
18054
18055     for (i = strlen(string) - length; i >= 0; i--, string++)
18056       if (!strncmp(match, string, length))
18057         return string;
18058
18059     return NULL;
18060 }
18061
18062 char *
18063 StrCaseStr (char *string, char *match)
18064 {
18065     int i, j, length;
18066
18067     length = strlen(match);
18068
18069     for (i = strlen(string) - length; i >= 0; i--, string++) {
18070         for (j = 0; j < length; j++) {
18071             if (ToLower(match[j]) != ToLower(string[j]))
18072               break;
18073         }
18074         if (j == length) return string;
18075     }
18076
18077     return NULL;
18078 }
18079
18080 #ifndef _amigados
18081 int
18082 StrCaseCmp (char *s1, char *s2)
18083 {
18084     char c1, c2;
18085
18086     for (;;) {
18087         c1 = ToLower(*s1++);
18088         c2 = ToLower(*s2++);
18089         if (c1 > c2) return 1;
18090         if (c1 < c2) return -1;
18091         if (c1 == NULLCHAR) return 0;
18092     }
18093 }
18094
18095
18096 int
18097 ToLower (int c)
18098 {
18099     return isupper(c) ? tolower(c) : c;
18100 }
18101
18102
18103 int
18104 ToUpper (int c)
18105 {
18106     return islower(c) ? toupper(c) : c;
18107 }
18108 #endif /* !_amigados    */
18109
18110 char *
18111 StrSave (char *s)
18112 {
18113   char *ret;
18114
18115   if ((ret = (char *) malloc(strlen(s) + 1)))
18116     {
18117       safeStrCpy(ret, s, strlen(s)+1);
18118     }
18119   return ret;
18120 }
18121
18122 char *
18123 StrSavePtr (char *s, char **savePtr)
18124 {
18125     if (*savePtr) {
18126         free(*savePtr);
18127     }
18128     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18129       safeStrCpy(*savePtr, s, strlen(s)+1);
18130     }
18131     return(*savePtr);
18132 }
18133
18134 char *
18135 PGNDate ()
18136 {
18137     time_t clock;
18138     struct tm *tm;
18139     char buf[MSG_SIZ];
18140
18141     clock = time((time_t *)NULL);
18142     tm = localtime(&clock);
18143     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18144             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18145     return StrSave(buf);
18146 }
18147
18148
18149 char *
18150 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18151 {
18152     int i, j, fromX, fromY, toX, toY;
18153     int whiteToPlay, haveRights = nrCastlingRights;
18154     char buf[MSG_SIZ];
18155     char *p, *q;
18156     int emptycount;
18157     ChessSquare piece;
18158
18159     whiteToPlay = (gameMode == EditPosition) ?
18160       !blackPlaysFirst : (move % 2 == 0);
18161     p = buf;
18162
18163     /* Piece placement data */
18164     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18165         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18166         emptycount = 0;
18167         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18168             if (boards[move][i][j] == EmptySquare) {
18169                 emptycount++;
18170             } else { ChessSquare piece = boards[move][i][j];
18171                 if (emptycount > 0) {
18172                     if(emptycount<10) /* [HGM] can be >= 10 */
18173                         *p++ = '0' + emptycount;
18174                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18175                     emptycount = 0;
18176                 }
18177                 if(PieceToChar(piece) == '+') {
18178                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18179                     *p++ = '+';
18180                     piece = (ChessSquare)(CHUDEMOTED(piece));
18181                 }
18182                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18183                 if(*p = PieceSuffix(piece)) p++;
18184                 if(p[-1] == '~') {
18185                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18186                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18187                     *p++ = '~';
18188                 }
18189             }
18190         }
18191         if (emptycount > 0) {
18192             if(emptycount<10) /* [HGM] can be >= 10 */
18193                 *p++ = '0' + emptycount;
18194             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18195             emptycount = 0;
18196         }
18197         *p++ = '/';
18198     }
18199     *(p - 1) = ' ';
18200
18201     /* [HGM] print Crazyhouse or Shogi holdings */
18202     if( gameInfo.holdingsWidth ) {
18203         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18204         q = p;
18205         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18206             piece = boards[move][i][BOARD_WIDTH-1];
18207             if( piece != EmptySquare )
18208               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18209                   *p++ = PieceToChar(piece);
18210         }
18211         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18212             piece = boards[move][BOARD_HEIGHT-i-1][0];
18213             if( piece != EmptySquare )
18214               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18215                   *p++ = PieceToChar(piece);
18216         }
18217
18218         if( q == p ) *p++ = '-';
18219         *p++ = ']';
18220         *p++ = ' ';
18221     }
18222
18223     /* Active color */
18224     *p++ = whiteToPlay ? 'w' : 'b';
18225     *p++ = ' ';
18226
18227   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18228     haveRights = 0; q = p;
18229     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18230       piece = boards[move][0][i];
18231       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18232         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18233       }
18234     }
18235     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18236       piece = boards[move][BOARD_HEIGHT-1][i];
18237       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18238         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18239       }
18240     }
18241     if(p == q) *p++ = '-';
18242     *p++ = ' ';
18243   }
18244
18245   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18246     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18247   } else {
18248   if(haveRights) {
18249      int handW=0, handB=0;
18250      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18251         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18252         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18253      }
18254      q = p;
18255      if(appData.fischerCastling) {
18256         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18257            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18258                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18259         } else {
18260        /* [HGM] write directly from rights */
18261            if(boards[move][CASTLING][2] != NoRights &&
18262               boards[move][CASTLING][0] != NoRights   )
18263                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18264            if(boards[move][CASTLING][2] != NoRights &&
18265               boards[move][CASTLING][1] != NoRights   )
18266                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18267         }
18268         if(handB) {
18269            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18270                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18271         } else {
18272            if(boards[move][CASTLING][5] != NoRights &&
18273               boards[move][CASTLING][3] != NoRights   )
18274                 *p++ = boards[move][CASTLING][3] + AAA;
18275            if(boards[move][CASTLING][5] != NoRights &&
18276               boards[move][CASTLING][4] != NoRights   )
18277                 *p++ = boards[move][CASTLING][4] + AAA;
18278         }
18279      } else {
18280
18281         /* [HGM] write true castling rights */
18282         if( nrCastlingRights == 6 ) {
18283             int q, k=0;
18284             if(boards[move][CASTLING][0] != NoRights &&
18285                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18286             q = (boards[move][CASTLING][1] != NoRights &&
18287                  boards[move][CASTLING][2] != NoRights  );
18288             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18289                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18290                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18291                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18292             }
18293             if(q) *p++ = 'Q';
18294             k = 0;
18295             if(boards[move][CASTLING][3] != NoRights &&
18296                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18297             q = (boards[move][CASTLING][4] != NoRights &&
18298                  boards[move][CASTLING][5] != NoRights  );
18299             if(handB) {
18300                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18301                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18302                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18303             }
18304             if(q) *p++ = 'q';
18305         }
18306      }
18307      if (q == p) *p++ = '-'; /* No castling rights */
18308      *p++ = ' ';
18309   }
18310
18311   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18312      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18313      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18314     /* En passant target square */
18315     if (move > backwardMostMove) {
18316         fromX = moveList[move - 1][0] - AAA;
18317         fromY = moveList[move - 1][1] - ONE;
18318         toX = moveList[move - 1][2] - AAA;
18319         toY = moveList[move - 1][3] - ONE;
18320         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18321             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18322             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18323             fromX == toX) {
18324             /* 2-square pawn move just happened */
18325             *p++ = toX + AAA;
18326             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18327         } else {
18328             *p++ = '-';
18329         }
18330     } else if(move == backwardMostMove) {
18331         // [HGM] perhaps we should always do it like this, and forget the above?
18332         if((signed char)boards[move][EP_STATUS] >= 0) {
18333             *p++ = boards[move][EP_STATUS] + AAA;
18334             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18335         } else {
18336             *p++ = '-';
18337         }
18338     } else {
18339         *p++ = '-';
18340     }
18341     *p++ = ' ';
18342   }
18343   }
18344
18345     if(moveCounts)
18346     {   int i = 0, j=move;
18347
18348         /* [HGM] find reversible plies */
18349         if (appData.debugMode) { int k;
18350             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18351             for(k=backwardMostMove; k<=forwardMostMove; k++)
18352                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18353
18354         }
18355
18356         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18357         if( j == backwardMostMove ) i += initialRulePlies;
18358         sprintf(p, "%d ", i);
18359         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18360
18361         /* Fullmove number */
18362         sprintf(p, "%d", (move / 2) + 1);
18363     } else *--p = NULLCHAR;
18364
18365     return StrSave(buf);
18366 }
18367
18368 Boolean
18369 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18370 {
18371     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18372     char *p, c;
18373     int emptycount, virgin[BOARD_FILES];
18374     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18375
18376     p = fen;
18377
18378     /* Piece placement data */
18379     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18380         j = 0;
18381         for (;;) {
18382             if (*p == '/' || *p == ' ' || *p == '[' ) {
18383                 if(j > w) w = j;
18384                 emptycount = gameInfo.boardWidth - j;
18385                 while (emptycount--)
18386                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18387                 if (*p == '/') p++;
18388                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18389                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18390                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18391                     }
18392                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18393                 }
18394                 break;
18395 #if(BOARD_FILES >= 10)*0
18396             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18397                 p++; emptycount=10;
18398                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18399                 while (emptycount--)
18400                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18401 #endif
18402             } else if (*p == '*') {
18403                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18404             } else if (isdigit(*p)) {
18405                 emptycount = *p++ - '0';
18406                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18407                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18408                 while (emptycount--)
18409                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18410             } else if (*p == '<') {
18411                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18412                 else if (i != 0 || !shuffle) return FALSE;
18413                 p++;
18414             } else if (shuffle && *p == '>') {
18415                 p++; // for now ignore closing shuffle range, and assume rank-end
18416             } else if (*p == '?') {
18417                 if (j >= gameInfo.boardWidth) return FALSE;
18418                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18419                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18420             } else if (*p == '+' || isalpha(*p)) {
18421                 char *q, *s = SUFFIXES;
18422                 if (j >= gameInfo.boardWidth) return FALSE;
18423                 if(*p=='+') {
18424                     char c = *++p;
18425                     if(q = strchr(s, p[1])) p++;
18426                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18427                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18428                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18429                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18430                 } else {
18431                     char c = *p++;
18432                     if(q = strchr(s, *p)) p++;
18433                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18434                 }
18435
18436                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18437                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18438                     piece = (ChessSquare) (PROMOTED(piece));
18439                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18440                     p++;
18441                 }
18442                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18443                 if(piece == king) wKingRank = i;
18444                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18445             } else {
18446                 return FALSE;
18447             }
18448         }
18449     }
18450     while (*p == '/' || *p == ' ') p++;
18451
18452     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18453
18454     /* [HGM] by default clear Crazyhouse holdings, if present */
18455     if(gameInfo.holdingsWidth) {
18456        for(i=0; i<BOARD_HEIGHT; i++) {
18457            board[i][0]             = EmptySquare; /* black holdings */
18458            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18459            board[i][1]             = (ChessSquare) 0; /* black counts */
18460            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18461        }
18462     }
18463
18464     /* [HGM] look for Crazyhouse holdings here */
18465     while(*p==' ') p++;
18466     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18467         int swap=0, wcnt=0, bcnt=0;
18468         if(*p == '[') p++;
18469         if(*p == '<') swap++, p++;
18470         if(*p == '-' ) p++; /* empty holdings */ else {
18471             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18472             /* if we would allow FEN reading to set board size, we would   */
18473             /* have to add holdings and shift the board read so far here   */
18474             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18475                 p++;
18476                 if((int) piece >= (int) BlackPawn ) {
18477                     i = (int)piece - (int)BlackPawn;
18478                     i = PieceToNumber((ChessSquare)i);
18479                     if( i >= gameInfo.holdingsSize ) return FALSE;
18480                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18481                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18482                     bcnt++;
18483                 } else {
18484                     i = (int)piece - (int)WhitePawn;
18485                     i = PieceToNumber((ChessSquare)i);
18486                     if( i >= gameInfo.holdingsSize ) return FALSE;
18487                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18488                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18489                     wcnt++;
18490                 }
18491             }
18492             if(subst) { // substitute back-rank question marks by holdings pieces
18493                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18494                     int k, m, n = bcnt + 1;
18495                     if(board[0][j] == ClearBoard) {
18496                         if(!wcnt) return FALSE;
18497                         n = rand() % wcnt;
18498                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18499                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18500                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18501                             break;
18502                         }
18503                     }
18504                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18505                         if(!bcnt) return FALSE;
18506                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18507                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18508                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18509                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18510                             break;
18511                         }
18512                     }
18513                 }
18514                 subst = 0;
18515             }
18516         }
18517         if(*p == ']') p++;
18518     }
18519
18520     if(subst) return FALSE; // substitution requested, but no holdings
18521
18522     while(*p == ' ') p++;
18523
18524     /* Active color */
18525     c = *p++;
18526     if(appData.colorNickNames) {
18527       if( c == appData.colorNickNames[0] ) c = 'w'; else
18528       if( c == appData.colorNickNames[1] ) c = 'b';
18529     }
18530     switch (c) {
18531       case 'w':
18532         *blackPlaysFirst = FALSE;
18533         break;
18534       case 'b':
18535         *blackPlaysFirst = TRUE;
18536         break;
18537       default:
18538         return FALSE;
18539     }
18540
18541     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18542     /* return the extra info in global variiables             */
18543
18544     while(*p==' ') p++;
18545
18546     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18547         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18548         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18549     }
18550
18551     /* set defaults in case FEN is incomplete */
18552     board[EP_STATUS] = EP_UNKNOWN;
18553     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18554     for(i=0; i<nrCastlingRights; i++ ) {
18555         board[CASTLING][i] =
18556             appData.fischerCastling ? NoRights : initialRights[i];
18557     }   /* assume possible unless obviously impossible */
18558     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18559     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18560     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18561                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18562     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18563     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18564     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18565                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18566     FENrulePlies = 0;
18567
18568     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18569       char *q = p;
18570       int w=0, b=0;
18571       while(isalpha(*p)) {
18572         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18573         if(islower(*p)) b |= 1 << (*p++ - 'a');
18574       }
18575       if(*p == '-') p++;
18576       if(p != q) {
18577         board[TOUCHED_W] = ~w;
18578         board[TOUCHED_B] = ~b;
18579         while(*p == ' ') p++;
18580       }
18581     } else
18582
18583     if(nrCastlingRights) {
18584       int fischer = 0;
18585       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18586       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18587           /* castling indicator present, so default becomes no castlings */
18588           for(i=0; i<nrCastlingRights; i++ ) {
18589                  board[CASTLING][i] = NoRights;
18590           }
18591       }
18592       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18593              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18594              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18595              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18596         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18597
18598         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18599             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18600             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18601         }
18602         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18603             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18604         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18605                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18606         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18607                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18608         switch(c) {
18609           case'K':
18610               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18611               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18612               board[CASTLING][2] = whiteKingFile;
18613               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18614               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18615               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18616               break;
18617           case'Q':
18618               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18619               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18620               board[CASTLING][2] = whiteKingFile;
18621               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18622               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18623               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18624               break;
18625           case'k':
18626               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18627               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18628               board[CASTLING][5] = blackKingFile;
18629               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18630               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18631               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18632               break;
18633           case'q':
18634               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18635               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18636               board[CASTLING][5] = blackKingFile;
18637               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18638               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18639               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18640           case '-':
18641               break;
18642           default: /* FRC castlings */
18643               if(c >= 'a') { /* black rights */
18644                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18645                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18646                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18647                   if(i == BOARD_RGHT) break;
18648                   board[CASTLING][5] = i;
18649                   c -= AAA;
18650                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18651                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18652                   if(c > i)
18653                       board[CASTLING][3] = c;
18654                   else
18655                       board[CASTLING][4] = c;
18656               } else { /* white rights */
18657                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18658                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18659                     if(board[0][i] == WhiteKing) break;
18660                   if(i == BOARD_RGHT) break;
18661                   board[CASTLING][2] = i;
18662                   c -= AAA - 'a' + 'A';
18663                   if(board[0][c] >= WhiteKing) break;
18664                   if(c > i)
18665                       board[CASTLING][0] = c;
18666                   else
18667                       board[CASTLING][1] = c;
18668               }
18669         }
18670       }
18671       for(i=0; i<nrCastlingRights; i++)
18672         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18673       if(gameInfo.variant == VariantSChess)
18674         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18675       if(fischer && shuffle) appData.fischerCastling = TRUE;
18676     if (appData.debugMode) {
18677         fprintf(debugFP, "FEN castling rights:");
18678         for(i=0; i<nrCastlingRights; i++)
18679         fprintf(debugFP, " %d", board[CASTLING][i]);
18680         fprintf(debugFP, "\n");
18681     }
18682
18683       while(*p==' ') p++;
18684     }
18685
18686     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18687
18688     /* read e.p. field in games that know e.p. capture */
18689     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18690        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18691        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18692       if(*p=='-') {
18693         p++; board[EP_STATUS] = EP_NONE;
18694       } else {
18695          char c = *p++ - AAA;
18696
18697          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18698          if(*p >= '0' && *p <='9') p++;
18699          board[EP_STATUS] = c;
18700       }
18701     }
18702
18703
18704     if(sscanf(p, "%d", &i) == 1) {
18705         FENrulePlies = i; /* 50-move ply counter */
18706         /* (The move number is still ignored)    */
18707     }
18708
18709     return TRUE;
18710 }
18711
18712 void
18713 EditPositionPasteFEN (char *fen)
18714 {
18715   if (fen != NULL) {
18716     Board initial_position;
18717
18718     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18719       DisplayError(_("Bad FEN position in clipboard"), 0);
18720       return ;
18721     } else {
18722       int savedBlackPlaysFirst = blackPlaysFirst;
18723       EditPositionEvent();
18724       blackPlaysFirst = savedBlackPlaysFirst;
18725       CopyBoard(boards[0], initial_position);
18726       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18727       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18728       DisplayBothClocks();
18729       DrawPosition(FALSE, boards[currentMove]);
18730     }
18731   }
18732 }
18733
18734 static char cseq[12] = "\\   ";
18735
18736 Boolean
18737 set_cont_sequence (char *new_seq)
18738 {
18739     int len;
18740     Boolean ret;
18741
18742     // handle bad attempts to set the sequence
18743         if (!new_seq)
18744                 return 0; // acceptable error - no debug
18745
18746     len = strlen(new_seq);
18747     ret = (len > 0) && (len < sizeof(cseq));
18748     if (ret)
18749       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18750     else if (appData.debugMode)
18751       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18752     return ret;
18753 }
18754
18755 /*
18756     reformat a source message so words don't cross the width boundary.  internal
18757     newlines are not removed.  returns the wrapped size (no null character unless
18758     included in source message).  If dest is NULL, only calculate the size required
18759     for the dest buffer.  lp argument indicats line position upon entry, and it's
18760     passed back upon exit.
18761 */
18762 int
18763 wrap (char *dest, char *src, int count, int width, int *lp)
18764 {
18765     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18766
18767     cseq_len = strlen(cseq);
18768     old_line = line = *lp;
18769     ansi = len = clen = 0;
18770
18771     for (i=0; i < count; i++)
18772     {
18773         if (src[i] == '\033')
18774             ansi = 1;
18775
18776         // if we hit the width, back up
18777         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18778         {
18779             // store i & len in case the word is too long
18780             old_i = i, old_len = len;
18781
18782             // find the end of the last word
18783             while (i && src[i] != ' ' && src[i] != '\n')
18784             {
18785                 i--;
18786                 len--;
18787             }
18788
18789             // word too long?  restore i & len before splitting it
18790             if ((old_i-i+clen) >= width)
18791             {
18792                 i = old_i;
18793                 len = old_len;
18794             }
18795
18796             // extra space?
18797             if (i && src[i-1] == ' ')
18798                 len--;
18799
18800             if (src[i] != ' ' && src[i] != '\n')
18801             {
18802                 i--;
18803                 if (len)
18804                     len--;
18805             }
18806
18807             // now append the newline and continuation sequence
18808             if (dest)
18809                 dest[len] = '\n';
18810             len++;
18811             if (dest)
18812                 strncpy(dest+len, cseq, cseq_len);
18813             len += cseq_len;
18814             line = cseq_len;
18815             clen = cseq_len;
18816             continue;
18817         }
18818
18819         if (dest)
18820             dest[len] = src[i];
18821         len++;
18822         if (!ansi)
18823             line++;
18824         if (src[i] == '\n')
18825             line = 0;
18826         if (src[i] == 'm')
18827             ansi = 0;
18828     }
18829     if (dest && appData.debugMode)
18830     {
18831         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18832             count, width, line, len, *lp);
18833         show_bytes(debugFP, src, count);
18834         fprintf(debugFP, "\ndest: ");
18835         show_bytes(debugFP, dest, len);
18836         fprintf(debugFP, "\n");
18837     }
18838     *lp = dest ? line : old_line;
18839
18840     return len;
18841 }
18842
18843 // [HGM] vari: routines for shelving variations
18844 Boolean modeRestore = FALSE;
18845
18846 void
18847 PushInner (int firstMove, int lastMove)
18848 {
18849         int i, j, nrMoves = lastMove - firstMove;
18850
18851         // push current tail of game on stack
18852         savedResult[storedGames] = gameInfo.result;
18853         savedDetails[storedGames] = gameInfo.resultDetails;
18854         gameInfo.resultDetails = NULL;
18855         savedFirst[storedGames] = firstMove;
18856         savedLast [storedGames] = lastMove;
18857         savedFramePtr[storedGames] = framePtr;
18858         framePtr -= nrMoves; // reserve space for the boards
18859         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18860             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18861             for(j=0; j<MOVE_LEN; j++)
18862                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18863             for(j=0; j<2*MOVE_LEN; j++)
18864                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18865             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18866             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18867             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18868             pvInfoList[firstMove+i-1].depth = 0;
18869             commentList[framePtr+i] = commentList[firstMove+i];
18870             commentList[firstMove+i] = NULL;
18871         }
18872
18873         storedGames++;
18874         forwardMostMove = firstMove; // truncate game so we can start variation
18875 }
18876
18877 void
18878 PushTail (int firstMove, int lastMove)
18879 {
18880         if(appData.icsActive) { // only in local mode
18881                 forwardMostMove = currentMove; // mimic old ICS behavior
18882                 return;
18883         }
18884         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18885
18886         PushInner(firstMove, lastMove);
18887         if(storedGames == 1) GreyRevert(FALSE);
18888         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18889 }
18890
18891 void
18892 PopInner (Boolean annotate)
18893 {
18894         int i, j, nrMoves;
18895         char buf[8000], moveBuf[20];
18896
18897         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18898         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18899         nrMoves = savedLast[storedGames] - currentMove;
18900         if(annotate) {
18901                 int cnt = 10;
18902                 if(!WhiteOnMove(currentMove))
18903                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18904                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18905                 for(i=currentMove; i<forwardMostMove; i++) {
18906                         if(WhiteOnMove(i))
18907                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18908                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18909                         strcat(buf, moveBuf);
18910                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18911                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18912                 }
18913                 strcat(buf, ")");
18914         }
18915         for(i=1; i<=nrMoves; i++) { // copy last variation back
18916             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18917             for(j=0; j<MOVE_LEN; j++)
18918                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18919             for(j=0; j<2*MOVE_LEN; j++)
18920                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18921             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18922             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18923             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18924             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18925             commentList[currentMove+i] = commentList[framePtr+i];
18926             commentList[framePtr+i] = NULL;
18927         }
18928         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18929         framePtr = savedFramePtr[storedGames];
18930         gameInfo.result = savedResult[storedGames];
18931         if(gameInfo.resultDetails != NULL) {
18932             free(gameInfo.resultDetails);
18933       }
18934         gameInfo.resultDetails = savedDetails[storedGames];
18935         forwardMostMove = currentMove + nrMoves;
18936 }
18937
18938 Boolean
18939 PopTail (Boolean annotate)
18940 {
18941         if(appData.icsActive) return FALSE; // only in local mode
18942         if(!storedGames) return FALSE; // sanity
18943         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18944
18945         PopInner(annotate);
18946         if(currentMove < forwardMostMove) ForwardEvent(); else
18947         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18948
18949         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18950         return TRUE;
18951 }
18952
18953 void
18954 CleanupTail ()
18955 {       // remove all shelved variations
18956         int i;
18957         for(i=0; i<storedGames; i++) {
18958             if(savedDetails[i])
18959                 free(savedDetails[i]);
18960             savedDetails[i] = NULL;
18961         }
18962         for(i=framePtr; i<MAX_MOVES; i++) {
18963                 if(commentList[i]) free(commentList[i]);
18964                 commentList[i] = NULL;
18965         }
18966         framePtr = MAX_MOVES-1;
18967         storedGames = 0;
18968 }
18969
18970 void
18971 LoadVariation (int index, char *text)
18972 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18973         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18974         int level = 0, move;
18975
18976         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18977         // first find outermost bracketing variation
18978         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18979             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18980                 if(*p == '{') wait = '}'; else
18981                 if(*p == '[') wait = ']'; else
18982                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18983                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18984             }
18985             if(*p == wait) wait = NULLCHAR; // closing ]} found
18986             p++;
18987         }
18988         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18989         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18990         end[1] = NULLCHAR; // clip off comment beyond variation
18991         ToNrEvent(currentMove-1);
18992         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18993         // kludge: use ParsePV() to append variation to game
18994         move = currentMove;
18995         ParsePV(start, TRUE, TRUE);
18996         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18997         ClearPremoveHighlights();
18998         CommentPopDown();
18999         ToNrEvent(currentMove+1);
19000 }
19001
19002 void
19003 LoadTheme ()
19004 {
19005     char *p, *q, buf[MSG_SIZ];
19006     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19007         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19008         ParseArgsFromString(buf);
19009         ActivateTheme(TRUE); // also redo colors
19010         return;
19011     }
19012     p = nickName;
19013     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19014     {
19015         int len;
19016         q = appData.themeNames;
19017         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19018       if(appData.useBitmaps) {
19019         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19020                 appData.liteBackTextureFile, appData.darkBackTextureFile,
19021                 appData.liteBackTextureMode,
19022                 appData.darkBackTextureMode );
19023       } else {
19024         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
19025                 Col2Text(2),   // lightSquareColor
19026                 Col2Text(3) ); // darkSquareColor
19027       }
19028       if(appData.useBorder) {
19029         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19030                 appData.border);
19031       } else {
19032         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19033       }
19034       if(appData.useFont) {
19035         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19036                 appData.renderPiecesWithFont,
19037                 appData.fontToPieceTable,
19038                 Col2Text(9),    // appData.fontBackColorWhite
19039                 Col2Text(10) ); // appData.fontForeColorBlack
19040       } else {
19041         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
19042                 appData.pieceDirectory);
19043         if(!appData.pieceDirectory[0])
19044           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19045                 Col2Text(0),   // whitePieceColor
19046                 Col2Text(1) ); // blackPieceColor
19047       }
19048       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19049                 Col2Text(4),   // highlightSquareColor
19050                 Col2Text(5) ); // premoveHighlightColor
19051         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19052         if(insert != q) insert[-1] = NULLCHAR;
19053         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19054         if(q)   free(q);
19055     }
19056     ActivateTheme(FALSE);
19057 }