4f6262ad3ff5091ae4beb2fd828dc566c9bc0eb6
[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, BlackDragon, BlackKing, BlackTower,
568         BlackTower, 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, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
696     { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
698     { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
699       WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
700     { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
701       BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
702     { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
704     { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
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                        ;
3400 #endif
3401             } // [DM] 'else { ' deleted
3402                 if (
3403                     /* Regular tells and says */
3404                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3405                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3406                     looking_at(buf, &i, "* says: ") ||
3407                     /* Don't color "message" or "messages" output */
3408                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3409                     looking_at(buf, &i, "*. * at *:*: ") ||
3410                     looking_at(buf, &i, "--* (*:*): ") ||
3411                     /* Message notifications (same color as tells) */
3412                     looking_at(buf, &i, "* has left a message ") ||
3413                     looking_at(buf, &i, "* just sent you a message:\n") ||
3414                     /* Whispers and kibitzes */
3415                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3416                     looking_at(buf, &i, "* kibitzes: ") ||
3417                     /* Channel tells */
3418                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3419
3420                   if (tkind == 1 && strchr(star_match[0], ':')) {
3421                       /* Avoid "tells you:" spoofs in channels */
3422                      tkind = 3;
3423                   }
3424                   if (star_match[0][0] == NULLCHAR ||
3425                       strchr(star_match[0], ' ') ||
3426                       (tkind == 3 && strchr(star_match[1], ' '))) {
3427                     /* Reject bogus matches */
3428                     i = oldi;
3429                   } else {
3430                     if (appData.colorize) {
3431                       if (oldi > next_out) {
3432                         SendToPlayer(&buf[next_out], oldi - next_out);
3433                         next_out = oldi;
3434                       }
3435                       switch (tkind) {
3436                       case 1:
3437                         Colorize(ColorTell, FALSE);
3438                         curColor = ColorTell;
3439                         break;
3440                       case 2:
3441                         Colorize(ColorKibitz, FALSE);
3442                         curColor = ColorKibitz;
3443                         break;
3444                       case 3:
3445                         p = strrchr(star_match[1], '(');
3446                         if (p == NULL) {
3447                           p = star_match[1];
3448                         } else {
3449                           p++;
3450                         }
3451                         if (atoi(p) == 1) {
3452                           Colorize(ColorChannel1, FALSE);
3453                           curColor = ColorChannel1;
3454                         } else {
3455                           Colorize(ColorChannel, FALSE);
3456                           curColor = ColorChannel;
3457                         }
3458                         break;
3459                       case 5:
3460                         curColor = ColorNormal;
3461                         break;
3462                       }
3463                     }
3464                     if (started == STARTED_NONE && appData.autoComment &&
3465                         (gameMode == IcsObserving ||
3466                          gameMode == IcsPlayingWhite ||
3467                          gameMode == IcsPlayingBlack)) {
3468                       parse_pos = i - oldi;
3469                       memcpy(parse, &buf[oldi], parse_pos);
3470                       parse[parse_pos] = NULLCHAR;
3471                       started = STARTED_COMMENT;
3472                       savingComment = TRUE;
3473                     } else if(collective != 3) {
3474                       started = STARTED_CHATTER;
3475                       savingComment = FALSE;
3476                     }
3477                     loggedOn = TRUE;
3478                     continue;
3479                   }
3480                 }
3481
3482                 if (looking_at(buf, &i, "* s-shouts: ") ||
3483                     looking_at(buf, &i, "* c-shouts: ")) {
3484                     if (appData.colorize) {
3485                         if (oldi > next_out) {
3486                             SendToPlayer(&buf[next_out], oldi - next_out);
3487                             next_out = oldi;
3488                         }
3489                         Colorize(ColorSShout, FALSE);
3490                         curColor = ColorSShout;
3491                     }
3492                     loggedOn = TRUE;
3493                     started = STARTED_CHATTER;
3494                     continue;
3495                 }
3496
3497                 if (looking_at(buf, &i, "--->")) {
3498                     loggedOn = TRUE;
3499                     continue;
3500                 }
3501
3502                 if (looking_at(buf, &i, "* shouts: ") ||
3503                     looking_at(buf, &i, "--> ")) {
3504                     if (appData.colorize) {
3505                         if (oldi > next_out) {
3506                             SendToPlayer(&buf[next_out], oldi - next_out);
3507                             next_out = oldi;
3508                         }
3509                         Colorize(ColorShout, FALSE);
3510                         curColor = ColorShout;
3511                     }
3512                     loggedOn = TRUE;
3513                     started = STARTED_CHATTER;
3514                     continue;
3515                 }
3516
3517                 if (looking_at( buf, &i, "Challenge:")) {
3518                     if (appData.colorize) {
3519                         if (oldi > next_out) {
3520                             SendToPlayer(&buf[next_out], oldi - next_out);
3521                             next_out = oldi;
3522                         }
3523                         Colorize(ColorChallenge, FALSE);
3524                         curColor = ColorChallenge;
3525                     }
3526                     loggedOn = TRUE;
3527                     continue;
3528                 }
3529
3530                 if (looking_at(buf, &i, "* offers you") ||
3531                     looking_at(buf, &i, "* offers to be") ||
3532                     looking_at(buf, &i, "* would like to") ||
3533                     looking_at(buf, &i, "* requests to") ||
3534                     looking_at(buf, &i, "Your opponent offers") ||
3535                     looking_at(buf, &i, "Your opponent requests")) {
3536
3537                     if (appData.colorize) {
3538                         if (oldi > next_out) {
3539                             SendToPlayer(&buf[next_out], oldi - next_out);
3540                             next_out = oldi;
3541                         }
3542                         Colorize(ColorRequest, FALSE);
3543                         curColor = ColorRequest;
3544                     }
3545                     continue;
3546                 }
3547
3548                 if (looking_at(buf, &i, "* (*) seeking")) {
3549                     if (appData.colorize) {
3550                         if (oldi > next_out) {
3551                             SendToPlayer(&buf[next_out], oldi - next_out);
3552                             next_out = oldi;
3553                         }
3554                         Colorize(ColorSeek, FALSE);
3555                         curColor = ColorSeek;
3556                     }
3557                     continue;
3558             }
3559
3560           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3561
3562             if (looking_at(buf, &i, "\\   ")) {
3563                 if (prevColor != ColorNormal) {
3564                     if (oldi > next_out) {
3565                         SendToPlayer(&buf[next_out], oldi - next_out);
3566                         next_out = oldi;
3567                     }
3568                     Colorize(prevColor, TRUE);
3569                     curColor = prevColor;
3570                 }
3571                 if (savingComment) {
3572                     parse_pos = i - oldi;
3573                     memcpy(parse, &buf[oldi], parse_pos);
3574                     parse[parse_pos] = NULLCHAR;
3575                     started = STARTED_COMMENT;
3576                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3577                         chattingPartner = savingComment - 3; // kludge to remember the box
3578                 } else {
3579                     started = STARTED_CHATTER;
3580                 }
3581                 continue;
3582             }
3583
3584             if (looking_at(buf, &i, "Black Strength :") ||
3585                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3586                 looking_at(buf, &i, "<10>") ||
3587                 looking_at(buf, &i, "#@#")) {
3588                 /* Wrong board style */
3589                 loggedOn = TRUE;
3590                 SendToICS(ics_prefix);
3591                 SendToICS("set style 12\n");
3592                 SendToICS(ics_prefix);
3593                 SendToICS("refresh\n");
3594                 continue;
3595             }
3596
3597             if (looking_at(buf, &i, "login:")) {
3598               if (!have_sent_ICS_logon) {
3599                 if(ICSInitScript())
3600                   have_sent_ICS_logon = 1;
3601                 else // no init script was found
3602                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3603               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3604                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3605               }
3606                 continue;
3607             }
3608
3609             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3610                 (looking_at(buf, &i, "\n<12> ") ||
3611                  looking_at(buf, &i, "<12> "))) {
3612                 loggedOn = TRUE;
3613                 if (oldi > next_out) {
3614                     SendToPlayer(&buf[next_out], oldi - next_out);
3615                 }
3616                 next_out = i;
3617                 started = STARTED_BOARD;
3618                 parse_pos = 0;
3619                 continue;
3620             }
3621
3622             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3623                 looking_at(buf, &i, "<b1> ")) {
3624                 if (oldi > next_out) {
3625                     SendToPlayer(&buf[next_out], oldi - next_out);
3626                 }
3627                 next_out = i;
3628                 started = STARTED_HOLDINGS;
3629                 parse_pos = 0;
3630                 continue;
3631             }
3632
3633             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3634                 loggedOn = TRUE;
3635                 /* Header for a move list -- first line */
3636
3637                 switch (ics_getting_history) {
3638                   case H_FALSE:
3639                     switch (gameMode) {
3640                       case IcsIdle:
3641                       case BeginningOfGame:
3642                         /* User typed "moves" or "oldmoves" while we
3643                            were idle.  Pretend we asked for these
3644                            moves and soak them up so user can step
3645                            through them and/or save them.
3646                            */
3647                         Reset(FALSE, TRUE);
3648                         gameMode = IcsObserving;
3649                         ModeHighlight();
3650                         ics_gamenum = -1;
3651                         ics_getting_history = H_GOT_UNREQ_HEADER;
3652                         break;
3653                       case EditGame: /*?*/
3654                       case EditPosition: /*?*/
3655                         /* Should above feature work in these modes too? */
3656                         /* For now it doesn't */
3657                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3658                         break;
3659                       default:
3660                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3661                         break;
3662                     }
3663                     break;
3664                   case H_REQUESTED:
3665                     /* Is this the right one? */
3666                     if (gameInfo.white && gameInfo.black &&
3667                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3668                         strcmp(gameInfo.black, star_match[2]) == 0) {
3669                         /* All is well */
3670                         ics_getting_history = H_GOT_REQ_HEADER;
3671                     }
3672                     break;
3673                   case H_GOT_REQ_HEADER:
3674                   case H_GOT_UNREQ_HEADER:
3675                   case H_GOT_UNWANTED_HEADER:
3676                   case H_GETTING_MOVES:
3677                     /* Should not happen */
3678                     DisplayError(_("Error gathering move list: two headers"), 0);
3679                     ics_getting_history = H_FALSE;
3680                     break;
3681                 }
3682
3683                 /* Save player ratings into gameInfo if needed */
3684                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3685                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3686                     (gameInfo.whiteRating == -1 ||
3687                      gameInfo.blackRating == -1)) {
3688
3689                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3690                     gameInfo.blackRating = string_to_rating(star_match[3]);
3691                     if (appData.debugMode)
3692                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3693                               gameInfo.whiteRating, gameInfo.blackRating);
3694                 }
3695                 continue;
3696             }
3697
3698             if (looking_at(buf, &i,
3699               "* * match, initial time: * minute*, increment: * second")) {
3700                 /* Header for a move list -- second line */
3701                 /* Initial board will follow if this is a wild game */
3702                 if (gameInfo.event != NULL) free(gameInfo.event);
3703                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3704                 gameInfo.event = StrSave(str);
3705                 /* [HGM] we switched variant. Translate boards if needed. */
3706                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3707                 continue;
3708             }
3709
3710             if (looking_at(buf, &i, "Move  ")) {
3711                 /* Beginning of a move list */
3712                 switch (ics_getting_history) {
3713                   case H_FALSE:
3714                     /* Normally should not happen */
3715                     /* Maybe user hit reset while we were parsing */
3716                     break;
3717                   case H_REQUESTED:
3718                     /* Happens if we are ignoring a move list that is not
3719                      * the one we just requested.  Common if the user
3720                      * tries to observe two games without turning off
3721                      * getMoveList */
3722                     break;
3723                   case H_GETTING_MOVES:
3724                     /* Should not happen */
3725                     DisplayError(_("Error gathering move list: nested"), 0);
3726                     ics_getting_history = H_FALSE;
3727                     break;
3728                   case H_GOT_REQ_HEADER:
3729                     ics_getting_history = H_GETTING_MOVES;
3730                     started = STARTED_MOVES;
3731                     parse_pos = 0;
3732                     if (oldi > next_out) {
3733                         SendToPlayer(&buf[next_out], oldi - next_out);
3734                     }
3735                     break;
3736                   case H_GOT_UNREQ_HEADER:
3737                     ics_getting_history = H_GETTING_MOVES;
3738                     started = STARTED_MOVES_NOHIDE;
3739                     parse_pos = 0;
3740                     break;
3741                   case H_GOT_UNWANTED_HEADER:
3742                     ics_getting_history = H_FALSE;
3743                     break;
3744                 }
3745                 continue;
3746             }
3747
3748             if (looking_at(buf, &i, "% ") ||
3749                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3750                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3751                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3752                     soughtPending = FALSE;
3753                     seekGraphUp = TRUE;
3754                     DrawSeekGraph();
3755                 }
3756                 if(suppressKibitz) next_out = i;
3757                 savingComment = FALSE;
3758                 suppressKibitz = 0;
3759                 switch (started) {
3760                   case STARTED_MOVES:
3761                   case STARTED_MOVES_NOHIDE:
3762                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3763                     parse[parse_pos + i - oldi] = NULLCHAR;
3764                     ParseGameHistory(parse);
3765 #if ZIPPY
3766                     if (appData.zippyPlay && first.initDone) {
3767                         FeedMovesToProgram(&first, forwardMostMove);
3768                         if (gameMode == IcsPlayingWhite) {
3769                             if (WhiteOnMove(forwardMostMove)) {
3770                                 if (first.sendTime) {
3771                                   if (first.useColors) {
3772                                     SendToProgram("black\n", &first);
3773                                   }
3774                                   SendTimeRemaining(&first, TRUE);
3775                                 }
3776                                 if (first.useColors) {
3777                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3778                                 }
3779                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3780                                 first.maybeThinking = TRUE;
3781                             } else {
3782                                 if (first.usePlayother) {
3783                                   if (first.sendTime) {
3784                                     SendTimeRemaining(&first, TRUE);
3785                                   }
3786                                   SendToProgram("playother\n", &first);
3787                                   firstMove = FALSE;
3788                                 } else {
3789                                   firstMove = TRUE;
3790                                 }
3791                             }
3792                         } else if (gameMode == IcsPlayingBlack) {
3793                             if (!WhiteOnMove(forwardMostMove)) {
3794                                 if (first.sendTime) {
3795                                   if (first.useColors) {
3796                                     SendToProgram("white\n", &first);
3797                                   }
3798                                   SendTimeRemaining(&first, FALSE);
3799                                 }
3800                                 if (first.useColors) {
3801                                   SendToProgram("black\n", &first);
3802                                 }
3803                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3804                                 first.maybeThinking = TRUE;
3805                             } else {
3806                                 if (first.usePlayother) {
3807                                   if (first.sendTime) {
3808                                     SendTimeRemaining(&first, FALSE);
3809                                   }
3810                                   SendToProgram("playother\n", &first);
3811                                   firstMove = FALSE;
3812                                 } else {
3813                                   firstMove = TRUE;
3814                                 }
3815                             }
3816                         }
3817                     }
3818 #endif
3819                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3820                         /* Moves came from oldmoves or moves command
3821                            while we weren't doing anything else.
3822                            */
3823                         currentMove = forwardMostMove;
3824                         ClearHighlights();/*!!could figure this out*/
3825                         flipView = appData.flipView;
3826                         DrawPosition(TRUE, boards[currentMove]);
3827                         DisplayBothClocks();
3828                         snprintf(str, MSG_SIZ, "%s %s %s",
3829                                 gameInfo.white, _("vs."),  gameInfo.black);
3830                         DisplayTitle(str);
3831                         gameMode = IcsIdle;
3832                     } else {
3833                         /* Moves were history of an active game */
3834                         if (gameInfo.resultDetails != NULL) {
3835                             free(gameInfo.resultDetails);
3836                             gameInfo.resultDetails = NULL;
3837                         }
3838                     }
3839                     HistorySet(parseList, backwardMostMove,
3840                                forwardMostMove, currentMove-1);
3841                     DisplayMove(currentMove - 1);
3842                     if (started == STARTED_MOVES) next_out = i;
3843                     started = STARTED_NONE;
3844                     ics_getting_history = H_FALSE;
3845                     break;
3846
3847                   case STARTED_OBSERVE:
3848                     started = STARTED_NONE;
3849                     SendToICS(ics_prefix);
3850                     SendToICS("refresh\n");
3851                     break;
3852
3853                   default:
3854                     break;
3855                 }
3856                 if(bookHit) { // [HGM] book: simulate book reply
3857                     static char bookMove[MSG_SIZ]; // a bit generous?
3858
3859                     programStats.nodes = programStats.depth = programStats.time =
3860                     programStats.score = programStats.got_only_move = 0;
3861                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3862
3863                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3864                     strcat(bookMove, bookHit);
3865                     HandleMachineMove(bookMove, &first);
3866                 }
3867                 continue;
3868             }
3869
3870             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3871                  started == STARTED_HOLDINGS ||
3872                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3873                 /* Accumulate characters in move list or board */
3874                 parse[parse_pos++] = buf[i];
3875             }
3876
3877             /* Start of game messages.  Mostly we detect start of game
3878                when the first board image arrives.  On some versions
3879                of the ICS, though, we need to do a "refresh" after starting
3880                to observe in order to get the current board right away. */
3881             if (looking_at(buf, &i, "Adding game * to observation list")) {
3882                 started = STARTED_OBSERVE;
3883                 continue;
3884             }
3885
3886             /* Handle auto-observe */
3887             if (appData.autoObserve &&
3888                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3889                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3890                 char *player;
3891                 /* Choose the player that was highlighted, if any. */
3892                 if (star_match[0][0] == '\033' ||
3893                     star_match[1][0] != '\033') {
3894                     player = star_match[0];
3895                 } else {
3896                     player = star_match[2];
3897                 }
3898                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3899                         ics_prefix, StripHighlightAndTitle(player));
3900                 SendToICS(str);
3901
3902                 /* Save ratings from notify string */
3903                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3904                 player1Rating = string_to_rating(star_match[1]);
3905                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3906                 player2Rating = string_to_rating(star_match[3]);
3907
3908                 if (appData.debugMode)
3909                   fprintf(debugFP,
3910                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3911                           player1Name, player1Rating,
3912                           player2Name, player2Rating);
3913
3914                 continue;
3915             }
3916
3917             /* Deal with automatic examine mode after a game,
3918                and with IcsObserving -> IcsExamining transition */
3919             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3920                 looking_at(buf, &i, "has made you an examiner of game *")) {
3921
3922                 int gamenum = atoi(star_match[0]);
3923                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3924                     gamenum == ics_gamenum) {
3925                     /* We were already playing or observing this game;
3926                        no need to refetch history */
3927                     gameMode = IcsExamining;
3928                     if (pausing) {
3929                         pauseExamForwardMostMove = forwardMostMove;
3930                     } else if (currentMove < forwardMostMove) {
3931                         ForwardInner(forwardMostMove);
3932                     }
3933                 } else {
3934                     /* I don't think this case really can happen */
3935                     SendToICS(ics_prefix);
3936                     SendToICS("refresh\n");
3937                 }
3938                 continue;
3939             }
3940
3941             /* Error messages */
3942 //          if (ics_user_moved) {
3943             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3944                 if (looking_at(buf, &i, "Illegal move") ||
3945                     looking_at(buf, &i, "Not a legal move") ||
3946                     looking_at(buf, &i, "Your king is in check") ||
3947                     looking_at(buf, &i, "It isn't your turn") ||
3948                     looking_at(buf, &i, "It is not your move")) {
3949                     /* Illegal move */
3950                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3951                         currentMove = forwardMostMove-1;
3952                         DisplayMove(currentMove - 1); /* before DMError */
3953                         DrawPosition(FALSE, boards[currentMove]);
3954                         SwitchClocks(forwardMostMove-1); // [HGM] race
3955                         DisplayBothClocks();
3956                     }
3957                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3958                     ics_user_moved = 0;
3959                     continue;
3960                 }
3961             }
3962
3963             if (looking_at(buf, &i, "still have time") ||
3964                 looking_at(buf, &i, "not out of time") ||
3965                 looking_at(buf, &i, "either player is out of time") ||
3966                 looking_at(buf, &i, "has timeseal; checking")) {
3967                 /* We must have called his flag a little too soon */
3968                 whiteFlag = blackFlag = FALSE;
3969                 continue;
3970             }
3971
3972             if (looking_at(buf, &i, "added * seconds to") ||
3973                 looking_at(buf, &i, "seconds were added to")) {
3974                 /* Update the clocks */
3975                 SendToICS(ics_prefix);
3976                 SendToICS("refresh\n");
3977                 continue;
3978             }
3979
3980             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3981                 ics_clock_paused = TRUE;
3982                 StopClocks();
3983                 continue;
3984             }
3985
3986             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3987                 ics_clock_paused = FALSE;
3988                 StartClocks();
3989                 continue;
3990             }
3991
3992             /* Grab player ratings from the Creating: message.
3993                Note we have to check for the special case when
3994                the ICS inserts things like [white] or [black]. */
3995             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3996                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3997                 /* star_matches:
3998                    0    player 1 name (not necessarily white)
3999                    1    player 1 rating
4000                    2    empty, white, or black (IGNORED)
4001                    3    player 2 name (not necessarily black)
4002                    4    player 2 rating
4003
4004                    The names/ratings are sorted out when the game
4005                    actually starts (below).
4006                 */
4007                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4008                 player1Rating = string_to_rating(star_match[1]);
4009                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4010                 player2Rating = string_to_rating(star_match[4]);
4011
4012                 if (appData.debugMode)
4013                   fprintf(debugFP,
4014                           "Ratings from 'Creating:' %s %d, %s %d\n",
4015                           player1Name, player1Rating,
4016                           player2Name, player2Rating);
4017
4018                 continue;
4019             }
4020
4021             /* Improved generic start/end-of-game messages */
4022             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4023                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4024                 /* If tkind == 0: */
4025                 /* star_match[0] is the game number */
4026                 /*           [1] is the white player's name */
4027                 /*           [2] is the black player's name */
4028                 /* For end-of-game: */
4029                 /*           [3] is the reason for the game end */
4030                 /*           [4] is a PGN end game-token, preceded by " " */
4031                 /* For start-of-game: */
4032                 /*           [3] begins with "Creating" or "Continuing" */
4033                 /*           [4] is " *" or empty (don't care). */
4034                 int gamenum = atoi(star_match[0]);
4035                 char *whitename, *blackname, *why, *endtoken;
4036                 ChessMove endtype = EndOfFile;
4037
4038                 if (tkind == 0) {
4039                   whitename = star_match[1];
4040                   blackname = star_match[2];
4041                   why = star_match[3];
4042                   endtoken = star_match[4];
4043                 } else {
4044                   whitename = star_match[1];
4045                   blackname = star_match[3];
4046                   why = star_match[5];
4047                   endtoken = star_match[6];
4048                 }
4049
4050                 /* Game start messages */
4051                 if (strncmp(why, "Creating ", 9) == 0 ||
4052                     strncmp(why, "Continuing ", 11) == 0) {
4053                     gs_gamenum = gamenum;
4054                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4055                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4056                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4057 #if ZIPPY
4058                     if (appData.zippyPlay) {
4059                         ZippyGameStart(whitename, blackname);
4060                     }
4061 #endif /*ZIPPY*/
4062                     partnerBoardValid = FALSE; // [HGM] bughouse
4063                     continue;
4064                 }
4065
4066                 /* Game end messages */
4067                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4068                     ics_gamenum != gamenum) {
4069                     continue;
4070                 }
4071                 while (endtoken[0] == ' ') endtoken++;
4072                 switch (endtoken[0]) {
4073                   case '*':
4074                   default:
4075                     endtype = GameUnfinished;
4076                     break;
4077                   case '0':
4078                     endtype = BlackWins;
4079                     break;
4080                   case '1':
4081                     if (endtoken[1] == '/')
4082                       endtype = GameIsDrawn;
4083                     else
4084                       endtype = WhiteWins;
4085                     break;
4086                 }
4087                 GameEnds(endtype, why, GE_ICS);
4088 #if ZIPPY
4089                 if (appData.zippyPlay && first.initDone) {
4090                     ZippyGameEnd(endtype, why);
4091                     if (first.pr == NoProc) {
4092                       /* Start the next process early so that we'll
4093                          be ready for the next challenge */
4094                       StartChessProgram(&first);
4095                     }
4096                     /* Send "new" early, in case this command takes
4097                        a long time to finish, so that we'll be ready
4098                        for the next challenge. */
4099                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4100                     Reset(TRUE, TRUE);
4101                 }
4102 #endif /*ZIPPY*/
4103                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4104                 continue;
4105             }
4106
4107             if (looking_at(buf, &i, "Removing game * from observation") ||
4108                 looking_at(buf, &i, "no longer observing game *") ||
4109                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4110                 if (gameMode == IcsObserving &&
4111                     atoi(star_match[0]) == ics_gamenum)
4112                   {
4113                       /* icsEngineAnalyze */
4114                       if (appData.icsEngineAnalyze) {
4115                             ExitAnalyzeMode();
4116                             ModeHighlight();
4117                       }
4118                       StopClocks();
4119                       gameMode = IcsIdle;
4120                       ics_gamenum = -1;
4121                       ics_user_moved = FALSE;
4122                   }
4123                 continue;
4124             }
4125
4126             if (looking_at(buf, &i, "no longer examining game *")) {
4127                 if (gameMode == IcsExamining &&
4128                     atoi(star_match[0]) == ics_gamenum)
4129                   {
4130                       gameMode = IcsIdle;
4131                       ics_gamenum = -1;
4132                       ics_user_moved = FALSE;
4133                   }
4134                 continue;
4135             }
4136
4137             /* Advance leftover_start past any newlines we find,
4138                so only partial lines can get reparsed */
4139             if (looking_at(buf, &i, "\n")) {
4140                 prevColor = curColor;
4141                 if (curColor != ColorNormal) {
4142                     if (oldi > next_out) {
4143                         SendToPlayer(&buf[next_out], oldi - next_out);
4144                         next_out = oldi;
4145                     }
4146                     Colorize(ColorNormal, FALSE);
4147                     curColor = ColorNormal;
4148                 }
4149                 if (started == STARTED_BOARD) {
4150                     started = STARTED_NONE;
4151                     parse[parse_pos] = NULLCHAR;
4152                     ParseBoard12(parse);
4153                     ics_user_moved = 0;
4154
4155                     /* Send premove here */
4156                     if (appData.premove) {
4157                       char str[MSG_SIZ];
4158                       if (currentMove == 0 &&
4159                           gameMode == IcsPlayingWhite &&
4160                           appData.premoveWhite) {
4161                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4162                         if (appData.debugMode)
4163                           fprintf(debugFP, "Sending premove:\n");
4164                         SendToICS(str);
4165                       } else if (currentMove == 1 &&
4166                                  gameMode == IcsPlayingBlack &&
4167                                  appData.premoveBlack) {
4168                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4169                         if (appData.debugMode)
4170                           fprintf(debugFP, "Sending premove:\n");
4171                         SendToICS(str);
4172                       } else if (gotPremove) {
4173                         int oldFMM = forwardMostMove;
4174                         gotPremove = 0;
4175                         ClearPremoveHighlights();
4176                         if (appData.debugMode)
4177                           fprintf(debugFP, "Sending premove:\n");
4178                           UserMoveEvent(premoveFromX, premoveFromY,
4179                                         premoveToX, premoveToY,
4180                                         premovePromoChar);
4181                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4182                           if(moveList[oldFMM-1][1] != '@')
4183                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4184                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4185                           else // (drop)
4186                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4187                         }
4188                       }
4189                     }
4190
4191                     /* Usually suppress following prompt */
4192                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4193                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4194                         if (looking_at(buf, &i, "*% ")) {
4195                             savingComment = FALSE;
4196                             suppressKibitz = 0;
4197                         }
4198                     }
4199                     next_out = i;
4200                 } else if (started == STARTED_HOLDINGS) {
4201                     int gamenum;
4202                     char new_piece[MSG_SIZ];
4203                     started = STARTED_NONE;
4204                     parse[parse_pos] = NULLCHAR;
4205                     if (appData.debugMode)
4206                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4207                                                         parse, currentMove);
4208                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4209                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4210                         if (gameInfo.variant == VariantNormal) {
4211                           /* [HGM] We seem to switch variant during a game!
4212                            * Presumably no holdings were displayed, so we have
4213                            * to move the position two files to the right to
4214                            * create room for them!
4215                            */
4216                           VariantClass newVariant;
4217                           switch(gameInfo.boardWidth) { // base guess on board width
4218                                 case 9:  newVariant = VariantShogi; break;
4219                                 case 10: newVariant = VariantGreat; break;
4220                                 default: newVariant = VariantCrazyhouse; break;
4221                           }
4222                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4223                           /* Get a move list just to see the header, which
4224                              will tell us whether this is really bug or zh */
4225                           if (ics_getting_history == H_FALSE) {
4226                             ics_getting_history = H_REQUESTED;
4227                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4228                             SendToICS(str);
4229                           }
4230                         }
4231                         new_piece[0] = NULLCHAR;
4232                         sscanf(parse, "game %d white [%s black [%s <- %s",
4233                                &gamenum, white_holding, black_holding,
4234                                new_piece);
4235                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4236                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4237                         /* [HGM] copy holdings to board holdings area */
4238                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4239                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4240                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4241 #if ZIPPY
4242                         if (appData.zippyPlay && first.initDone) {
4243                             ZippyHoldings(white_holding, black_holding,
4244                                           new_piece);
4245                         }
4246 #endif /*ZIPPY*/
4247                         if (tinyLayout || smallLayout) {
4248                             char wh[16], bh[16];
4249                             PackHolding(wh, white_holding);
4250                             PackHolding(bh, black_holding);
4251                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4252                                     gameInfo.white, gameInfo.black);
4253                         } else {
4254                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4255                                     gameInfo.white, white_holding, _("vs."),
4256                                     gameInfo.black, black_holding);
4257                         }
4258                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4259                         DrawPosition(FALSE, boards[currentMove]);
4260                         DisplayTitle(str);
4261                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4262                         sscanf(parse, "game %d white [%s black [%s <- %s",
4263                                &gamenum, white_holding, black_holding,
4264                                new_piece);
4265                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4266                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4267                         /* [HGM] copy holdings to partner-board holdings area */
4268                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4269                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4270                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4271                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4272                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4273                       }
4274                     }
4275                     /* Suppress following prompt */
4276                     if (looking_at(buf, &i, "*% ")) {
4277                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4278                         savingComment = FALSE;
4279                         suppressKibitz = 0;
4280                     }
4281                     next_out = i;
4282                 }
4283                 continue;
4284             }
4285
4286             i++;                /* skip unparsed character and loop back */
4287         }
4288
4289         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4290 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4291 //          SendToPlayer(&buf[next_out], i - next_out);
4292             started != STARTED_HOLDINGS && leftover_start > next_out) {
4293             SendToPlayer(&buf[next_out], leftover_start - next_out);
4294             next_out = i;
4295         }
4296
4297         leftover_len = buf_len - leftover_start;
4298         /* if buffer ends with something we couldn't parse,
4299            reparse it after appending the next read */
4300
4301     } else if (count == 0) {
4302         RemoveInputSource(isr);
4303         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4304     } else {
4305         DisplayFatalError(_("Error reading from ICS"), error, 1);
4306     }
4307 }
4308
4309
4310 /* Board style 12 looks like this:
4311
4312    <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
4313
4314  * The "<12> " is stripped before it gets to this routine.  The two
4315  * trailing 0's (flip state and clock ticking) are later addition, and
4316  * some chess servers may not have them, or may have only the first.
4317  * Additional trailing fields may be added in the future.
4318  */
4319
4320 #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"
4321
4322 #define RELATION_OBSERVING_PLAYED    0
4323 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4324 #define RELATION_PLAYING_MYMOVE      1
4325 #define RELATION_PLAYING_NOTMYMOVE  -1
4326 #define RELATION_EXAMINING           2
4327 #define RELATION_ISOLATED_BOARD     -3
4328 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4329
4330 void
4331 ParseBoard12 (char *string)
4332 {
4333 #if ZIPPY
4334     int i, takeback;
4335     char *bookHit = NULL; // [HGM] book
4336 #endif
4337     GameMode newGameMode;
4338     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4339     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4340     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4341     char to_play, board_chars[200];
4342     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4343     char black[32], white[32];
4344     Board board;
4345     int prevMove = currentMove;
4346     int ticking = 2;
4347     ChessMove moveType;
4348     int fromX, fromY, toX, toY;
4349     char promoChar;
4350     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4351     Boolean weird = FALSE, reqFlag = FALSE;
4352
4353     fromX = fromY = toX = toY = -1;
4354
4355     newGame = FALSE;
4356
4357     if (appData.debugMode)
4358       fprintf(debugFP, "Parsing board: %s\n", string);
4359
4360     move_str[0] = NULLCHAR;
4361     elapsed_time[0] = NULLCHAR;
4362     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4363         int  i = 0, j;
4364         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4365             if(string[i] == ' ') { ranks++; files = 0; }
4366             else files++;
4367             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4368             i++;
4369         }
4370         for(j = 0; j <i; j++) board_chars[j] = string[j];
4371         board_chars[i] = '\0';
4372         string += i + 1;
4373     }
4374     n = sscanf(string, PATTERN, &to_play, &double_push,
4375                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4376                &gamenum, white, black, &relation, &basetime, &increment,
4377                &white_stren, &black_stren, &white_time, &black_time,
4378                &moveNum, str, elapsed_time, move_str, &ics_flip,
4379                &ticking);
4380
4381     if (n < 21) {
4382         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4383         DisplayError(str, 0);
4384         return;
4385     }
4386
4387     /* Convert the move number to internal form */
4388     moveNum = (moveNum - 1) * 2;
4389     if (to_play == 'B') moveNum++;
4390     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4391       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4392                         0, 1);
4393       return;
4394     }
4395
4396     switch (relation) {
4397       case RELATION_OBSERVING_PLAYED:
4398       case RELATION_OBSERVING_STATIC:
4399         if (gamenum == -1) {
4400             /* Old ICC buglet */
4401             relation = RELATION_OBSERVING_STATIC;
4402         }
4403         newGameMode = IcsObserving;
4404         break;
4405       case RELATION_PLAYING_MYMOVE:
4406       case RELATION_PLAYING_NOTMYMOVE:
4407         newGameMode =
4408           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4409             IcsPlayingWhite : IcsPlayingBlack;
4410         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4411         break;
4412       case RELATION_EXAMINING:
4413         newGameMode = IcsExamining;
4414         break;
4415       case RELATION_ISOLATED_BOARD:
4416       default:
4417         /* Just display this board.  If user was doing something else,
4418            we will forget about it until the next board comes. */
4419         newGameMode = IcsIdle;
4420         break;
4421       case RELATION_STARTING_POSITION:
4422         newGameMode = gameMode;
4423         break;
4424     }
4425
4426     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4427         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4428          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4429       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4430       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4431       static int lastBgGame = -1;
4432       char *toSqr;
4433       for (k = 0; k < ranks; k++) {
4434         for (j = 0; j < files; j++)
4435           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4436         if(gameInfo.holdingsWidth > 1) {
4437              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4438              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4439         }
4440       }
4441       CopyBoard(partnerBoard, board);
4442       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4443         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4444         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4445       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4446       if(toSqr = strchr(str, '-')) {
4447         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4448         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4449       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4450       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4451       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4452       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4453       if(twoBoards) {
4454           DisplayWhiteClock(white_time*fac, to_play == 'W');
4455           DisplayBlackClock(black_time*fac, to_play != 'W');
4456           activePartner = to_play;
4457           if(gamenum != lastBgGame) {
4458               char buf[MSG_SIZ];
4459               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4460               DisplayTitle(buf);
4461           }
4462           lastBgGame = gamenum;
4463           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4464                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4465       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4466                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4467       if(!twoBoards) DisplayMessage(partnerStatus, "");
4468         partnerBoardValid = TRUE;
4469       return;
4470     }
4471
4472     if(appData.dualBoard && appData.bgObserve) {
4473         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4474             SendToICS(ics_prefix), SendToICS("pobserve\n");
4475         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4476             char buf[MSG_SIZ];
4477             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4478             SendToICS(buf);
4479         }
4480     }
4481
4482     /* Modify behavior for initial board display on move listing
4483        of wild games.
4484        */
4485     switch (ics_getting_history) {
4486       case H_FALSE:
4487       case H_REQUESTED:
4488         break;
4489       case H_GOT_REQ_HEADER:
4490       case H_GOT_UNREQ_HEADER:
4491         /* This is the initial position of the current game */
4492         gamenum = ics_gamenum;
4493         moveNum = 0;            /* old ICS bug workaround */
4494         if (to_play == 'B') {
4495           startedFromSetupPosition = TRUE;
4496           blackPlaysFirst = TRUE;
4497           moveNum = 1;
4498           if (forwardMostMove == 0) forwardMostMove = 1;
4499           if (backwardMostMove == 0) backwardMostMove = 1;
4500           if (currentMove == 0) currentMove = 1;
4501         }
4502         newGameMode = gameMode;
4503         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4504         break;
4505       case H_GOT_UNWANTED_HEADER:
4506         /* This is an initial board that we don't want */
4507         return;
4508       case H_GETTING_MOVES:
4509         /* Should not happen */
4510         DisplayError(_("Error gathering move list: extra board"), 0);
4511         ics_getting_history = H_FALSE;
4512         return;
4513     }
4514
4515    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4516                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4517                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4518      /* [HGM] We seem to have switched variant unexpectedly
4519       * Try to guess new variant from board size
4520       */
4521           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4522           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4523           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4524           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4525           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4526           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4527           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4528           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4529           /* Get a move list just to see the header, which
4530              will tell us whether this is really bug or zh */
4531           if (ics_getting_history == H_FALSE) {
4532             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4533             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4534             SendToICS(str);
4535           }
4536     }
4537
4538     /* Take action if this is the first board of a new game, or of a
4539        different game than is currently being displayed.  */
4540     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4541         relation == RELATION_ISOLATED_BOARD) {
4542
4543         /* Forget the old game and get the history (if any) of the new one */
4544         if (gameMode != BeginningOfGame) {
4545           Reset(TRUE, TRUE);
4546         }
4547         newGame = TRUE;
4548         if (appData.autoRaiseBoard) BoardToTop();
4549         prevMove = -3;
4550         if (gamenum == -1) {
4551             newGameMode = IcsIdle;
4552         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4553                    appData.getMoveList && !reqFlag) {
4554             /* Need to get game history */
4555             ics_getting_history = H_REQUESTED;
4556             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4557             SendToICS(str);
4558         }
4559
4560         /* Initially flip the board to have black on the bottom if playing
4561            black or if the ICS flip flag is set, but let the user change
4562            it with the Flip View button. */
4563         flipView = appData.autoFlipView ?
4564           (newGameMode == IcsPlayingBlack) || ics_flip :
4565           appData.flipView;
4566
4567         /* Done with values from previous mode; copy in new ones */
4568         gameMode = newGameMode;
4569         ModeHighlight();
4570         ics_gamenum = gamenum;
4571         if (gamenum == gs_gamenum) {
4572             int klen = strlen(gs_kind);
4573             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4574             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4575             gameInfo.event = StrSave(str);
4576         } else {
4577             gameInfo.event = StrSave("ICS game");
4578         }
4579         gameInfo.site = StrSave(appData.icsHost);
4580         gameInfo.date = PGNDate();
4581         gameInfo.round = StrSave("-");
4582         gameInfo.white = StrSave(white);
4583         gameInfo.black = StrSave(black);
4584         timeControl = basetime * 60 * 1000;
4585         timeControl_2 = 0;
4586         timeIncrement = increment * 1000;
4587         movesPerSession = 0;
4588         gameInfo.timeControl = TimeControlTagValue();
4589         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4590   if (appData.debugMode) {
4591     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4592     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4593     setbuf(debugFP, NULL);
4594   }
4595
4596         gameInfo.outOfBook = NULL;
4597
4598         /* Do we have the ratings? */
4599         if (strcmp(player1Name, white) == 0 &&
4600             strcmp(player2Name, black) == 0) {
4601             if (appData.debugMode)
4602               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4603                       player1Rating, player2Rating);
4604             gameInfo.whiteRating = player1Rating;
4605             gameInfo.blackRating = player2Rating;
4606         } else if (strcmp(player2Name, white) == 0 &&
4607                    strcmp(player1Name, black) == 0) {
4608             if (appData.debugMode)
4609               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4610                       player2Rating, player1Rating);
4611             gameInfo.whiteRating = player2Rating;
4612             gameInfo.blackRating = player1Rating;
4613         }
4614         player1Name[0] = player2Name[0] = NULLCHAR;
4615
4616         /* Silence shouts if requested */
4617         if (appData.quietPlay &&
4618             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4619             SendToICS(ics_prefix);
4620             SendToICS("set shout 0\n");
4621         }
4622     }
4623
4624     /* Deal with midgame name changes */
4625     if (!newGame) {
4626         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4627             if (gameInfo.white) free(gameInfo.white);
4628             gameInfo.white = StrSave(white);
4629         }
4630         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4631             if (gameInfo.black) free(gameInfo.black);
4632             gameInfo.black = StrSave(black);
4633         }
4634     }
4635
4636     /* Throw away game result if anything actually changes in examine mode */
4637     if (gameMode == IcsExamining && !newGame) {
4638         gameInfo.result = GameUnfinished;
4639         if (gameInfo.resultDetails != NULL) {
4640             free(gameInfo.resultDetails);
4641             gameInfo.resultDetails = NULL;
4642         }
4643     }
4644
4645     /* In pausing && IcsExamining mode, we ignore boards coming
4646        in if they are in a different variation than we are. */
4647     if (pauseExamInvalid) return;
4648     if (pausing && gameMode == IcsExamining) {
4649         if (moveNum <= pauseExamForwardMostMove) {
4650             pauseExamInvalid = TRUE;
4651             forwardMostMove = pauseExamForwardMostMove;
4652             return;
4653         }
4654     }
4655
4656   if (appData.debugMode) {
4657     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4658   }
4659     /* Parse the board */
4660     for (k = 0; k < ranks; k++) {
4661       for (j = 0; j < files; j++)
4662         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4663       if(gameInfo.holdingsWidth > 1) {
4664            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4665            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4666       }
4667     }
4668     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4669       board[5][BOARD_RGHT+1] = WhiteAngel;
4670       board[6][BOARD_RGHT+1] = WhiteMarshall;
4671       board[1][0] = BlackMarshall;
4672       board[2][0] = BlackAngel;
4673       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4674     }
4675     CopyBoard(boards[moveNum], board);
4676     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4677     if (moveNum == 0) {
4678         startedFromSetupPosition =
4679           !CompareBoards(board, initialPosition);
4680         if(startedFromSetupPosition)
4681             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4682     }
4683
4684     /* [HGM] Set castling rights. Take the outermost Rooks,
4685        to make it also work for FRC opening positions. Note that board12
4686        is really defective for later FRC positions, as it has no way to
4687        indicate which Rook can castle if they are on the same side of King.
4688        For the initial position we grant rights to the outermost Rooks,
4689        and remember thos rights, and we then copy them on positions
4690        later in an FRC game. This means WB might not recognize castlings with
4691        Rooks that have moved back to their original position as illegal,
4692        but in ICS mode that is not its job anyway.
4693     */
4694     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4695     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4696
4697         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4698             if(board[0][i] == WhiteRook) j = i;
4699         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4700         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4701             if(board[0][i] == WhiteRook) j = i;
4702         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4703         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4704             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4705         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4706         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4707             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4708         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4709
4710         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4711         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4712         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4713             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4714         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4715             if(board[BOARD_HEIGHT-1][k] == bKing)
4716                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4717         if(gameInfo.variant == VariantTwoKings) {
4718             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4719             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4720             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4721         }
4722     } else { int r;
4723         r = boards[moveNum][CASTLING][0] = initialRights[0];
4724         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4725         r = boards[moveNum][CASTLING][1] = initialRights[1];
4726         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4727         r = boards[moveNum][CASTLING][3] = initialRights[3];
4728         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4729         r = boards[moveNum][CASTLING][4] = initialRights[4];
4730         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4731         /* wildcastle kludge: always assume King has rights */
4732         r = boards[moveNum][CASTLING][2] = initialRights[2];
4733         r = boards[moveNum][CASTLING][5] = initialRights[5];
4734     }
4735     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4736     boards[moveNum][EP_STATUS] = EP_NONE;
4737     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4738     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4739     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4740
4741
4742     if (ics_getting_history == H_GOT_REQ_HEADER ||
4743         ics_getting_history == H_GOT_UNREQ_HEADER) {
4744         /* This was an initial position from a move list, not
4745            the current position */
4746         return;
4747     }
4748
4749     /* Update currentMove and known move number limits */
4750     newMove = newGame || moveNum > forwardMostMove;
4751
4752     if (newGame) {
4753         forwardMostMove = backwardMostMove = currentMove = moveNum;
4754         if (gameMode == IcsExamining && moveNum == 0) {
4755           /* Workaround for ICS limitation: we are not told the wild
4756              type when starting to examine a game.  But if we ask for
4757              the move list, the move list header will tell us */
4758             ics_getting_history = H_REQUESTED;
4759             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4760             SendToICS(str);
4761         }
4762     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4763                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4764 #if ZIPPY
4765         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4766         /* [HGM] applied this also to an engine that is silently watching        */
4767         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4768             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4769             gameInfo.variant == currentlyInitializedVariant) {
4770           takeback = forwardMostMove - moveNum;
4771           for (i = 0; i < takeback; i++) {
4772             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4773             SendToProgram("undo\n", &first);
4774           }
4775         }
4776 #endif
4777
4778         forwardMostMove = moveNum;
4779         if (!pausing || currentMove > forwardMostMove)
4780           currentMove = forwardMostMove;
4781     } else {
4782         /* New part of history that is not contiguous with old part */
4783         if (pausing && gameMode == IcsExamining) {
4784             pauseExamInvalid = TRUE;
4785             forwardMostMove = pauseExamForwardMostMove;
4786             return;
4787         }
4788         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4789 #if ZIPPY
4790             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4791                 // [HGM] when we will receive the move list we now request, it will be
4792                 // fed to the engine from the first move on. So if the engine is not
4793                 // in the initial position now, bring it there.
4794                 InitChessProgram(&first, 0);
4795             }
4796 #endif
4797             ics_getting_history = H_REQUESTED;
4798             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4799             SendToICS(str);
4800         }
4801         forwardMostMove = backwardMostMove = currentMove = moveNum;
4802     }
4803
4804     /* Update the clocks */
4805     if (strchr(elapsed_time, '.')) {
4806       /* Time is in ms */
4807       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4808       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4809     } else {
4810       /* Time is in seconds */
4811       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4812       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4813     }
4814
4815
4816 #if ZIPPY
4817     if (appData.zippyPlay && newGame &&
4818         gameMode != IcsObserving && gameMode != IcsIdle &&
4819         gameMode != IcsExamining)
4820       ZippyFirstBoard(moveNum, basetime, increment);
4821 #endif
4822
4823     /* Put the move on the move list, first converting
4824        to canonical algebraic form. */
4825     if (moveNum > 0) {
4826   if (appData.debugMode) {
4827     int f = forwardMostMove;
4828     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4829             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4830             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4831     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4832     fprintf(debugFP, "moveNum = %d\n", moveNum);
4833     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4834     setbuf(debugFP, NULL);
4835   }
4836         if (moveNum <= backwardMostMove) {
4837             /* We don't know what the board looked like before
4838                this move.  Punt. */
4839           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4840             strcat(parseList[moveNum - 1], " ");
4841             strcat(parseList[moveNum - 1], elapsed_time);
4842             moveList[moveNum - 1][0] = NULLCHAR;
4843         } else if (strcmp(move_str, "none") == 0) {
4844             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4845             /* Again, we don't know what the board looked like;
4846                this is really the start of the game. */
4847             parseList[moveNum - 1][0] = NULLCHAR;
4848             moveList[moveNum - 1][0] = NULLCHAR;
4849             backwardMostMove = moveNum;
4850             startedFromSetupPosition = TRUE;
4851             fromX = fromY = toX = toY = -1;
4852         } else {
4853           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4854           //                 So we parse the long-algebraic move string in stead of the SAN move
4855           int valid; char buf[MSG_SIZ], *prom;
4856
4857           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4858                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4859           // str looks something like "Q/a1-a2"; kill the slash
4860           if(str[1] == '/')
4861             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4862           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4863           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4864                 strcat(buf, prom); // long move lacks promo specification!
4865           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4866                 if(appData.debugMode)
4867                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4868                 safeStrCpy(move_str, buf, MSG_SIZ);
4869           }
4870           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4871                                 &fromX, &fromY, &toX, &toY, &promoChar)
4872                || ParseOneMove(buf, moveNum - 1, &moveType,
4873                                 &fromX, &fromY, &toX, &toY, &promoChar);
4874           // end of long SAN patch
4875           if (valid) {
4876             (void) CoordsToAlgebraic(boards[moveNum - 1],
4877                                      PosFlags(moveNum - 1),
4878                                      fromY, fromX, toY, toX, promoChar,
4879                                      parseList[moveNum-1]);
4880             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4881               case MT_NONE:
4882               case MT_STALEMATE:
4883               default:
4884                 break;
4885               case MT_CHECK:
4886                 if(!IS_SHOGI(gameInfo.variant))
4887                     strcat(parseList[moveNum - 1], "+");
4888                 break;
4889               case MT_CHECKMATE:
4890               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4891                 strcat(parseList[moveNum - 1], "#");
4892                 break;
4893             }
4894             strcat(parseList[moveNum - 1], " ");
4895             strcat(parseList[moveNum - 1], elapsed_time);
4896             /* currentMoveString is set as a side-effect of ParseOneMove */
4897             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4898             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4899             strcat(moveList[moveNum - 1], "\n");
4900
4901             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4902                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4903               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4904                 ChessSquare old, new = boards[moveNum][k][j];
4905                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4906                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4907                   if(old == new) continue;
4908                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4909                   else if(new == WhiteWazir || new == BlackWazir) {
4910                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4911                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4912                       else boards[moveNum][k][j] = old; // preserve type of Gold
4913                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4914                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4915               }
4916           } else {
4917             /* Move from ICS was illegal!?  Punt. */
4918             if (appData.debugMode) {
4919               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4920               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4921             }
4922             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4923             strcat(parseList[moveNum - 1], " ");
4924             strcat(parseList[moveNum - 1], elapsed_time);
4925             moveList[moveNum - 1][0] = NULLCHAR;
4926             fromX = fromY = toX = toY = -1;
4927           }
4928         }
4929   if (appData.debugMode) {
4930     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4931     setbuf(debugFP, NULL);
4932   }
4933
4934 #if ZIPPY
4935         /* Send move to chess program (BEFORE animating it). */
4936         if (appData.zippyPlay && !newGame && newMove &&
4937            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4938
4939             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4940                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4941                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4942                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4943                             move_str);
4944                     DisplayError(str, 0);
4945                 } else {
4946                     if (first.sendTime) {
4947                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4948                     }
4949                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4950                     if (firstMove && !bookHit) {
4951                         firstMove = FALSE;
4952                         if (first.useColors) {
4953                           SendToProgram(gameMode == IcsPlayingWhite ?
4954                                         "white\ngo\n" :
4955                                         "black\ngo\n", &first);
4956                         } else {
4957                           SendToProgram("go\n", &first);
4958                         }
4959                         first.maybeThinking = TRUE;
4960                     }
4961                 }
4962             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4963               if (moveList[moveNum - 1][0] == NULLCHAR) {
4964                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4965                 DisplayError(str, 0);
4966               } else {
4967                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4968                 SendMoveToProgram(moveNum - 1, &first);
4969               }
4970             }
4971         }
4972 #endif
4973     }
4974
4975     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4976         /* If move comes from a remote source, animate it.  If it
4977            isn't remote, it will have already been animated. */
4978         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4979             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4980         }
4981         if (!pausing && appData.highlightLastMove) {
4982             SetHighlights(fromX, fromY, toX, toY);
4983         }
4984     }
4985
4986     /* Start the clocks */
4987     whiteFlag = blackFlag = FALSE;
4988     appData.clockMode = !(basetime == 0 && increment == 0);
4989     if (ticking == 0) {
4990       ics_clock_paused = TRUE;
4991       StopClocks();
4992     } else if (ticking == 1) {
4993       ics_clock_paused = FALSE;
4994     }
4995     if (gameMode == IcsIdle ||
4996         relation == RELATION_OBSERVING_STATIC ||
4997         relation == RELATION_EXAMINING ||
4998         ics_clock_paused)
4999       DisplayBothClocks();
5000     else
5001       StartClocks();
5002
5003     /* Display opponents and material strengths */
5004     if (gameInfo.variant != VariantBughouse &&
5005         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5006         if (tinyLayout || smallLayout) {
5007             if(gameInfo.variant == VariantNormal)
5008               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5009                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5010                     basetime, increment);
5011             else
5012               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5013                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5014                     basetime, increment, (int) gameInfo.variant);
5015         } else {
5016             if(gameInfo.variant == VariantNormal)
5017               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5018                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5019                     basetime, increment);
5020             else
5021               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5022                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5023                     basetime, increment, VariantName(gameInfo.variant));
5024         }
5025         DisplayTitle(str);
5026   if (appData.debugMode) {
5027     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5028   }
5029     }
5030
5031
5032     /* Display the board */
5033     if (!pausing && !appData.noGUI) {
5034
5035       if (appData.premove)
5036           if (!gotPremove ||
5037              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5038              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5039               ClearPremoveHighlights();
5040
5041       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5042         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5043       DrawPosition(j, boards[currentMove]);
5044
5045       DisplayMove(moveNum - 1);
5046       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5047             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5048               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5049         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5050       }
5051     }
5052
5053     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5054 #if ZIPPY
5055     if(bookHit) { // [HGM] book: simulate book reply
5056         static char bookMove[MSG_SIZ]; // a bit generous?
5057
5058         programStats.nodes = programStats.depth = programStats.time =
5059         programStats.score = programStats.got_only_move = 0;
5060         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5061
5062         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5063         strcat(bookMove, bookHit);
5064         HandleMachineMove(bookMove, &first);
5065     }
5066 #endif
5067 }
5068
5069 void
5070 GetMoveListEvent ()
5071 {
5072     char buf[MSG_SIZ];
5073     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5074         ics_getting_history = H_REQUESTED;
5075         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5076         SendToICS(buf);
5077     }
5078 }
5079
5080 void
5081 SendToBoth (char *msg)
5082 {   // to make it easy to keep two engines in step in dual analysis
5083     SendToProgram(msg, &first);
5084     if(second.analyzing) SendToProgram(msg, &second);
5085 }
5086
5087 void
5088 AnalysisPeriodicEvent (int force)
5089 {
5090     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5091          && !force) || !appData.periodicUpdates)
5092       return;
5093
5094     /* Send . command to Crafty to collect stats */
5095     SendToBoth(".\n");
5096
5097     /* Don't send another until we get a response (this makes
5098        us stop sending to old Crafty's which don't understand
5099        the "." command (sending illegal cmds resets node count & time,
5100        which looks bad)) */
5101     programStats.ok_to_send = 0;
5102 }
5103
5104 void
5105 ics_update_width (int new_width)
5106 {
5107         ics_printf("set width %d\n", new_width);
5108 }
5109
5110 void
5111 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5112 {
5113     char buf[MSG_SIZ];
5114
5115     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5116         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5117             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5118             SendToProgram(buf, cps);
5119             return;
5120         }
5121         // null move in variant where engine does not understand it (for analysis purposes)
5122         SendBoard(cps, moveNum + 1); // send position after move in stead.
5123         return;
5124     }
5125     if (cps->useUsermove) {
5126       SendToProgram("usermove ", cps);
5127     }
5128     if (cps->useSAN) {
5129       char *space;
5130       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5131         int len = space - parseList[moveNum];
5132         memcpy(buf, parseList[moveNum], len);
5133         buf[len++] = '\n';
5134         buf[len] = NULLCHAR;
5135       } else {
5136         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5137       }
5138       SendToProgram(buf, cps);
5139     } else {
5140       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5141         AlphaRank(moveList[moveNum], 4);
5142         SendToProgram(moveList[moveNum], cps);
5143         AlphaRank(moveList[moveNum], 4); // and back
5144       } else
5145       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5146        * the engine. It would be nice to have a better way to identify castle
5147        * moves here. */
5148       if(appData.fischerCastling && cps->useOOCastle) {
5149         int fromX = moveList[moveNum][0] - AAA;
5150         int fromY = moveList[moveNum][1] - ONE;
5151         int toX = moveList[moveNum][2] - AAA;
5152         int toY = moveList[moveNum][3] - ONE;
5153         if((boards[moveNum][fromY][fromX] == WhiteKing
5154             && boards[moveNum][toY][toX] == WhiteRook)
5155            || (boards[moveNum][fromY][fromX] == BlackKing
5156                && boards[moveNum][toY][toX] == BlackRook)) {
5157           if(toX > fromX) SendToProgram("O-O\n", cps);
5158           else SendToProgram("O-O-O\n", cps);
5159         }
5160         else SendToProgram(moveList[moveNum], cps);
5161       } else
5162       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5163         char *m = moveList[moveNum];
5164         static char c[2];
5165         *c = m[7]; // promoChar
5166         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
5167           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5168                                                m[2], m[3] - '0',
5169                                                m[5], m[6] - '0',
5170                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5171         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5172           *c = m[9];
5173           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
5174                                                m[7], m[8] - '0',
5175                                                m[7], m[8] - '0',
5176                                                m[5], m[6] - '0',
5177                                                m[5], m[6] - '0',
5178                                                m[2], m[3] - '0', c);
5179         } else
5180           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5181                                                m[5], m[6] - '0',
5182                                                m[5], m[6] - '0',
5183                                                m[2], m[3] - '0', c);
5184           SendToProgram(buf, cps);
5185       } else
5186       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5187         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5188           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5189           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5190                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5191         } else
5192           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5193                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5194         SendToProgram(buf, cps);
5195       }
5196       else SendToProgram(moveList[moveNum], cps);
5197       /* End of additions by Tord */
5198     }
5199
5200     /* [HGM] setting up the opening has brought engine in force mode! */
5201     /*       Send 'go' if we are in a mode where machine should play. */
5202     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5203         (gameMode == TwoMachinesPlay   ||
5204 #if ZIPPY
5205          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5206 #endif
5207          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5208         SendToProgram("go\n", cps);
5209   if (appData.debugMode) {
5210     fprintf(debugFP, "(extra)\n");
5211   }
5212     }
5213     setboardSpoiledMachineBlack = 0;
5214 }
5215
5216 void
5217 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5218 {
5219     char user_move[MSG_SIZ];
5220     char suffix[4];
5221
5222     if(gameInfo.variant == VariantSChess && promoChar) {
5223         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5224         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5225     } else suffix[0] = NULLCHAR;
5226
5227     switch (moveType) {
5228       default:
5229         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5230                 (int)moveType, fromX, fromY, toX, toY);
5231         DisplayError(user_move + strlen("say "), 0);
5232         break;
5233       case WhiteKingSideCastle:
5234       case BlackKingSideCastle:
5235       case WhiteQueenSideCastleWild:
5236       case BlackQueenSideCastleWild:
5237       /* PUSH Fabien */
5238       case WhiteHSideCastleFR:
5239       case BlackHSideCastleFR:
5240       /* POP Fabien */
5241         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5242         break;
5243       case WhiteQueenSideCastle:
5244       case BlackQueenSideCastle:
5245       case WhiteKingSideCastleWild:
5246       case BlackKingSideCastleWild:
5247       /* PUSH Fabien */
5248       case WhiteASideCastleFR:
5249       case BlackASideCastleFR:
5250       /* POP Fabien */
5251         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5252         break;
5253       case WhiteNonPromotion:
5254       case BlackNonPromotion:
5255         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5256         break;
5257       case WhitePromotion:
5258       case BlackPromotion:
5259         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5260            gameInfo.variant == VariantMakruk)
5261           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5262                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5263                 PieceToChar(WhiteFerz));
5264         else if(gameInfo.variant == VariantGreat)
5265           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5266                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5267                 PieceToChar(WhiteMan));
5268         else
5269           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5270                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5271                 promoChar);
5272         break;
5273       case WhiteDrop:
5274       case BlackDrop:
5275       drop:
5276         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5277                  ToUpper(PieceToChar((ChessSquare) fromX)),
5278                  AAA + toX, ONE + toY);
5279         break;
5280       case IllegalMove:  /* could be a variant we don't quite understand */
5281         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5282       case NormalMove:
5283       case WhiteCapturesEnPassant:
5284       case BlackCapturesEnPassant:
5285         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5286                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5287         break;
5288     }
5289     SendToICS(user_move);
5290     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5291         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5292 }
5293
5294 void
5295 UploadGameEvent ()
5296 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5297     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5298     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5299     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5300       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5301       return;
5302     }
5303     if(gameMode != IcsExamining) { // is this ever not the case?
5304         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5305
5306         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5307           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5308         } else { // on FICS we must first go to general examine mode
5309           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5310         }
5311         if(gameInfo.variant != VariantNormal) {
5312             // try figure out wild number, as xboard names are not always valid on ICS
5313             for(i=1; i<=36; i++) {
5314               snprintf(buf, MSG_SIZ, "wild/%d", i);
5315                 if(StringToVariant(buf) == gameInfo.variant) break;
5316             }
5317             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5318             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5319             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5320         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5321         SendToICS(ics_prefix);
5322         SendToICS(buf);
5323         if(startedFromSetupPosition || backwardMostMove != 0) {
5324           fen = PositionToFEN(backwardMostMove, NULL, 1);
5325           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5326             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5327             SendToICS(buf);
5328           } else { // FICS: everything has to set by separate bsetup commands
5329             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5330             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5331             SendToICS(buf);
5332             if(!WhiteOnMove(backwardMostMove)) {
5333                 SendToICS("bsetup tomove black\n");
5334             }
5335             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5336             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5337             SendToICS(buf);
5338             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5339             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5340             SendToICS(buf);
5341             i = boards[backwardMostMove][EP_STATUS];
5342             if(i >= 0) { // set e.p.
5343               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5344                 SendToICS(buf);
5345             }
5346             bsetup++;
5347           }
5348         }
5349       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5350             SendToICS("bsetup done\n"); // switch to normal examining.
5351     }
5352     for(i = backwardMostMove; i<last; i++) {
5353         char buf[20];
5354         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5355         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5356             int len = strlen(moveList[i]);
5357             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5358             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5359         }
5360         SendToICS(buf);
5361     }
5362     SendToICS(ics_prefix);
5363     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5364 }
5365
5366 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5367 int legNr = 1;
5368
5369 void
5370 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5371 {
5372     if (rf == DROP_RANK) {
5373       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5374       sprintf(move, "%c@%c%c\n",
5375                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5376     } else {
5377         if (promoChar == 'x' || promoChar == NULLCHAR) {
5378           sprintf(move, "%c%c%c%c\n",
5379                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5380           if(killX >= 0 && killY >= 0) {
5381             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5382             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5383           }
5384         } else {
5385             sprintf(move, "%c%c%c%c%c\n",
5386                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5387           if(killX >= 0 && killY >= 0) {
5388             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5389             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5390           }
5391         }
5392     }
5393 }
5394
5395 void
5396 ProcessICSInitScript (FILE *f)
5397 {
5398     char buf[MSG_SIZ];
5399
5400     while (fgets(buf, MSG_SIZ, f)) {
5401         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5402     }
5403
5404     fclose(f);
5405 }
5406
5407
5408 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5409 int dragging;
5410 static ClickType lastClickType;
5411
5412 int
5413 PieceInString (char *s, ChessSquare piece)
5414 {
5415   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5416   while((p = strchr(s, ID))) {
5417     if(!suffix || p[1] == suffix) return TRUE;
5418     s = p;
5419   }
5420   return FALSE;
5421 }
5422
5423 int
5424 Partner (ChessSquare *p)
5425 { // change piece into promotion partner if one shogi-promotes to the other
5426   ChessSquare partner = promoPartner[*p];
5427   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5428   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5429   *p = partner;
5430   return 1;
5431 }
5432
5433 void
5434 Sweep (int step)
5435 {
5436     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5437     static int toggleFlag;
5438     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5439     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5440     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5441     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5442     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5443     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5444     do {
5445         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5446         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5447         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5448         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5449         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5450         if(!step) step = -1;
5451     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5452             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5453             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5454             promoSweep == pawn ||
5455             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5456             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5457     if(toX >= 0) {
5458         int victim = boards[currentMove][toY][toX];
5459         boards[currentMove][toY][toX] = promoSweep;
5460         DrawPosition(FALSE, boards[currentMove]);
5461         boards[currentMove][toY][toX] = victim;
5462     } else
5463     ChangeDragPiece(promoSweep);
5464 }
5465
5466 int
5467 PromoScroll (int x, int y)
5468 {
5469   int step = 0;
5470
5471   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5472   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5473   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5474   if(!step) return FALSE;
5475   lastX = x; lastY = y;
5476   if((promoSweep < BlackPawn) == flipView) step = -step;
5477   if(step > 0) selectFlag = 1;
5478   if(!selectFlag) Sweep(step);
5479   return FALSE;
5480 }
5481
5482 void
5483 NextPiece (int step)
5484 {
5485     ChessSquare piece = boards[currentMove][toY][toX];
5486     do {
5487         pieceSweep -= step;
5488         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5489         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5490         if(!step) step = -1;
5491     } while(PieceToChar(pieceSweep) == '.');
5492     boards[currentMove][toY][toX] = pieceSweep;
5493     DrawPosition(FALSE, boards[currentMove]);
5494     boards[currentMove][toY][toX] = piece;
5495 }
5496 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5497 void
5498 AlphaRank (char *move, int n)
5499 {
5500 //    char *p = move, c; int x, y;
5501
5502     if (appData.debugMode) {
5503         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5504     }
5505
5506     if(move[1]=='*' &&
5507        move[2]>='0' && move[2]<='9' &&
5508        move[3]>='a' && move[3]<='x'    ) {
5509         move[1] = '@';
5510         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5511         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5512     } else
5513     if(move[0]>='0' && move[0]<='9' &&
5514        move[1]>='a' && move[1]<='x' &&
5515        move[2]>='0' && move[2]<='9' &&
5516        move[3]>='a' && move[3]<='x'    ) {
5517         /* input move, Shogi -> normal */
5518         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5519         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5520         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5521         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5522     } else
5523     if(move[1]=='@' &&
5524        move[3]>='0' && move[3]<='9' &&
5525        move[2]>='a' && move[2]<='x'    ) {
5526         move[1] = '*';
5527         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5528         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5529     } else
5530     if(
5531        move[0]>='a' && move[0]<='x' &&
5532        move[3]>='0' && move[3]<='9' &&
5533        move[2]>='a' && move[2]<='x'    ) {
5534          /* output move, normal -> Shogi */
5535         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5536         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5537         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5538         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5539         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5540     }
5541     if (appData.debugMode) {
5542         fprintf(debugFP, "   out = '%s'\n", move);
5543     }
5544 }
5545
5546 char yy_textstr[8000];
5547
5548 /* Parser for moves from gnuchess, ICS, or user typein box */
5549 Boolean
5550 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5551 {
5552     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5553
5554     switch (*moveType) {
5555       case WhitePromotion:
5556       case BlackPromotion:
5557       case WhiteNonPromotion:
5558       case BlackNonPromotion:
5559       case NormalMove:
5560       case FirstLeg:
5561       case WhiteCapturesEnPassant:
5562       case BlackCapturesEnPassant:
5563       case WhiteKingSideCastle:
5564       case WhiteQueenSideCastle:
5565       case BlackKingSideCastle:
5566       case BlackQueenSideCastle:
5567       case WhiteKingSideCastleWild:
5568       case WhiteQueenSideCastleWild:
5569       case BlackKingSideCastleWild:
5570       case BlackQueenSideCastleWild:
5571       /* Code added by Tord: */
5572       case WhiteHSideCastleFR:
5573       case WhiteASideCastleFR:
5574       case BlackHSideCastleFR:
5575       case BlackASideCastleFR:
5576       /* End of code added by Tord */
5577       case IllegalMove:         /* bug or odd chess variant */
5578         if(currentMoveString[1] == '@') { // illegal drop
5579           *fromX = WhiteOnMove(moveNum) ?
5580             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5581             (int) CharToPiece(ToLower(currentMoveString[0]));
5582           goto drop;
5583         }
5584         *fromX = currentMoveString[0] - AAA;
5585         *fromY = currentMoveString[1] - ONE;
5586         *toX = currentMoveString[2] - AAA;
5587         *toY = currentMoveString[3] - ONE;
5588         *promoChar = currentMoveString[4];
5589         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5590         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5591             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5592     if (appData.debugMode) {
5593         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5594     }
5595             *fromX = *fromY = *toX = *toY = 0;
5596             return FALSE;
5597         }
5598         if (appData.testLegality) {
5599           return (*moveType != IllegalMove);
5600         } else {
5601           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5602                          // [HGM] lion: if this is a double move we are less critical
5603                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5604         }
5605
5606       case WhiteDrop:
5607       case BlackDrop:
5608         *fromX = *moveType == WhiteDrop ?
5609           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5610           (int) CharToPiece(ToLower(currentMoveString[0]));
5611       drop:
5612         *fromY = DROP_RANK;
5613         *toX = currentMoveString[2] - AAA;
5614         *toY = currentMoveString[3] - ONE;
5615         *promoChar = NULLCHAR;
5616         return TRUE;
5617
5618       case AmbiguousMove:
5619       case ImpossibleMove:
5620       case EndOfFile:
5621       case ElapsedTime:
5622       case Comment:
5623       case PGNTag:
5624       case NAG:
5625       case WhiteWins:
5626       case BlackWins:
5627       case GameIsDrawn:
5628       default:
5629     if (appData.debugMode) {
5630         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5631     }
5632         /* bug? */
5633         *fromX = *fromY = *toX = *toY = 0;
5634         *promoChar = NULLCHAR;
5635         return FALSE;
5636     }
5637 }
5638
5639 Boolean pushed = FALSE;
5640 char *lastParseAttempt;
5641
5642 void
5643 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5644 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5645   int fromX, fromY, toX, toY; char promoChar;
5646   ChessMove moveType;
5647   Boolean valid;
5648   int nr = 0;
5649
5650   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5651   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5652     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5653     pushed = TRUE;
5654   }
5655   endPV = forwardMostMove;
5656   do {
5657     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5658     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5659     lastParseAttempt = pv;
5660     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5661     if(!valid && nr == 0 &&
5662        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5663         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5664         // Hande case where played move is different from leading PV move
5665         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5666         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5667         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5668         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5669           endPV += 2; // if position different, keep this
5670           moveList[endPV-1][0] = fromX + AAA;
5671           moveList[endPV-1][1] = fromY + ONE;
5672           moveList[endPV-1][2] = toX + AAA;
5673           moveList[endPV-1][3] = toY + ONE;
5674           parseList[endPV-1][0] = NULLCHAR;
5675           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5676         }
5677       }
5678     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5679     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5680     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5681     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5682         valid++; // allow comments in PV
5683         continue;
5684     }
5685     nr++;
5686     if(endPV+1 > framePtr) break; // no space, truncate
5687     if(!valid) break;
5688     endPV++;
5689     CopyBoard(boards[endPV], boards[endPV-1]);
5690     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5691     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5692     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5693     CoordsToAlgebraic(boards[endPV - 1],
5694                              PosFlags(endPV - 1),
5695                              fromY, fromX, toY, toX, promoChar,
5696                              parseList[endPV - 1]);
5697   } while(valid);
5698   if(atEnd == 2) return; // used hidden, for PV conversion
5699   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5700   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5701   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5702                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5703   DrawPosition(TRUE, boards[currentMove]);
5704 }
5705
5706 int
5707 MultiPV (ChessProgramState *cps, int kind)
5708 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5709         int i;
5710         for(i=0; i<cps->nrOptions; i++) {
5711             char *s = cps->option[i].name;
5712             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5713             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5714                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5715         }
5716         return -1;
5717 }
5718
5719 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5720 static int multi, pv_margin;
5721 static ChessProgramState *activeCps;
5722
5723 Boolean
5724 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5725 {
5726         int startPV, lineStart, origIndex = index;
5727         char *p, buf2[MSG_SIZ];
5728         ChessProgramState *cps = (pane ? &second : &first);
5729
5730         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5731         lastX = x; lastY = y;
5732         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5733         lineStart = startPV = index;
5734         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5735         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5736         index = startPV;
5737         do{ while(buf[index] && buf[index] != '\n') index++;
5738         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5739         buf[index] = 0;
5740         if(lineStart == 0 && gameMode == AnalyzeMode) {
5741             int n = 0;
5742             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5743             if(n == 0) { // click not on "fewer" or "more"
5744                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5745                     pv_margin = cps->option[multi].value;
5746                     activeCps = cps; // non-null signals margin adjustment
5747                 }
5748             } else if((multi = MultiPV(cps, 1)) >= 0) {
5749                 n += cps->option[multi].value; if(n < 1) n = 1;
5750                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5751                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5752                 cps->option[multi].value = n;
5753                 *start = *end = 0;
5754                 return FALSE;
5755             }
5756         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5757                 ExcludeClick(origIndex - lineStart);
5758                 return FALSE;
5759         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5760                 Collapse(origIndex - lineStart);
5761                 return FALSE;
5762         }
5763         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5764         *start = startPV; *end = index-1;
5765         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5766         return TRUE;
5767 }
5768
5769 char *
5770 PvToSAN (char *pv)
5771 {
5772         static char buf[10*MSG_SIZ];
5773         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5774         *buf = NULLCHAR;
5775         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5776         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5777         for(i = forwardMostMove; i<endPV; i++){
5778             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5779             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5780             k += strlen(buf+k);
5781         }
5782         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5783         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5784         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5785         endPV = savedEnd;
5786         return buf;
5787 }
5788
5789 Boolean
5790 LoadPV (int x, int y)
5791 { // called on right mouse click to load PV
5792   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5793   lastX = x; lastY = y;
5794   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5795   extendGame = FALSE;
5796   return TRUE;
5797 }
5798
5799 void
5800 UnLoadPV ()
5801 {
5802   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5803   if(activeCps) {
5804     if(pv_margin != activeCps->option[multi].value) {
5805       char buf[MSG_SIZ];
5806       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5807       SendToProgram(buf, activeCps);
5808       activeCps->option[multi].value = pv_margin;
5809     }
5810     activeCps = NULL;
5811     return;
5812   }
5813   if(endPV < 0) return;
5814   if(appData.autoCopyPV) CopyFENToClipboard();
5815   endPV = -1;
5816   if(extendGame && currentMove > forwardMostMove) {
5817         Boolean saveAnimate = appData.animate;
5818         if(pushed) {
5819             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5820                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5821             } else storedGames--; // abandon shelved tail of original game
5822         }
5823         pushed = FALSE;
5824         forwardMostMove = currentMove;
5825         currentMove = oldFMM;
5826         appData.animate = FALSE;
5827         ToNrEvent(forwardMostMove);
5828         appData.animate = saveAnimate;
5829   }
5830   currentMove = forwardMostMove;
5831   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5832   ClearPremoveHighlights();
5833   DrawPosition(TRUE, boards[currentMove]);
5834 }
5835
5836 void
5837 MovePV (int x, int y, int h)
5838 { // step through PV based on mouse coordinates (called on mouse move)
5839   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5840
5841   if(activeCps) { // adjusting engine's multi-pv margin
5842     if(x > lastX) pv_margin++; else
5843     if(x < lastX) pv_margin -= (pv_margin > 0);
5844     if(x != lastX) {
5845       char buf[MSG_SIZ];
5846       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5847       DisplayMessage(buf, "");
5848     }
5849     lastX = x;
5850     return;
5851   }
5852   // we must somehow check if right button is still down (might be released off board!)
5853   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5854   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5855   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5856   if(!step) return;
5857   lastX = x; lastY = y;
5858
5859   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5860   if(endPV < 0) return;
5861   if(y < margin) step = 1; else
5862   if(y > h - margin) step = -1;
5863   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5864   currentMove += step;
5865   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5866   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5867                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5868   DrawPosition(FALSE, boards[currentMove]);
5869 }
5870
5871
5872 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5873 // All positions will have equal probability, but the current method will not provide a unique
5874 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5875 #define DARK 1
5876 #define LITE 2
5877 #define ANY 3
5878
5879 int squaresLeft[4];
5880 int piecesLeft[(int)BlackPawn];
5881 int seed, nrOfShuffles;
5882
5883 void
5884 GetPositionNumber ()
5885 {       // sets global variable seed
5886         int i;
5887
5888         seed = appData.defaultFrcPosition;
5889         if(seed < 0) { // randomize based on time for negative FRC position numbers
5890                 for(i=0; i<50; i++) seed += random();
5891                 seed = random() ^ random() >> 8 ^ random() << 8;
5892                 if(seed<0) seed = -seed;
5893         }
5894 }
5895
5896 int
5897 put (Board board, int pieceType, int rank, int n, int shade)
5898 // put the piece on the (n-1)-th empty squares of the given shade
5899 {
5900         int i;
5901
5902         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5903                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5904                         board[rank][i] = (ChessSquare) pieceType;
5905                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5906                         squaresLeft[ANY]--;
5907                         piecesLeft[pieceType]--;
5908                         return i;
5909                 }
5910         }
5911         return -1;
5912 }
5913
5914
5915 void
5916 AddOnePiece (Board board, int pieceType, int rank, int shade)
5917 // calculate where the next piece goes, (any empty square), and put it there
5918 {
5919         int i;
5920
5921         i = seed % squaresLeft[shade];
5922         nrOfShuffles *= squaresLeft[shade];
5923         seed /= squaresLeft[shade];
5924         put(board, pieceType, rank, i, shade);
5925 }
5926
5927 void
5928 AddTwoPieces (Board board, int pieceType, int rank)
5929 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5930 {
5931         int i, n=squaresLeft[ANY], j=n-1, k;
5932
5933         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5934         i = seed % k;  // pick one
5935         nrOfShuffles *= k;
5936         seed /= k;
5937         while(i >= j) i -= j--;
5938         j = n - 1 - j; i += j;
5939         put(board, pieceType, rank, j, ANY);
5940         put(board, pieceType, rank, i, ANY);
5941 }
5942
5943 void
5944 SetUpShuffle (Board board, int number)
5945 {
5946         int i, p, first=1;
5947
5948         GetPositionNumber(); nrOfShuffles = 1;
5949
5950         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5951         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5952         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5953
5954         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5955
5956         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5957             p = (int) board[0][i];
5958             if(p < (int) BlackPawn) piecesLeft[p] ++;
5959             board[0][i] = EmptySquare;
5960         }
5961
5962         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5963             // shuffles restricted to allow normal castling put KRR first
5964             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5965                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5966             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5967                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5968             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5969                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5970             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5971                 put(board, WhiteRook, 0, 0, ANY);
5972             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5973         }
5974
5975         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5976             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5977             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5978                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5979                 while(piecesLeft[p] >= 2) {
5980                     AddOnePiece(board, p, 0, LITE);
5981                     AddOnePiece(board, p, 0, DARK);
5982                 }
5983                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5984             }
5985
5986         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5987             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5988             // but we leave King and Rooks for last, to possibly obey FRC restriction
5989             if(p == (int)WhiteRook) continue;
5990             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5991             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5992         }
5993
5994         // now everything is placed, except perhaps King (Unicorn) and Rooks
5995
5996         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5997             // Last King gets castling rights
5998             while(piecesLeft[(int)WhiteUnicorn]) {
5999                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6000                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6001             }
6002
6003             while(piecesLeft[(int)WhiteKing]) {
6004                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6005                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6006             }
6007
6008
6009         } else {
6010             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6011             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6012         }
6013
6014         // Only Rooks can be left; simply place them all
6015         while(piecesLeft[(int)WhiteRook]) {
6016                 i = put(board, WhiteRook, 0, 0, ANY);
6017                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6018                         if(first) {
6019                                 first=0;
6020                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6021                         }
6022                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6023                 }
6024         }
6025         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6026             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6027         }
6028
6029         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6030 }
6031
6032 int
6033 ptclen (const char *s, char *escapes)
6034 {
6035     int n = 0;
6036     if(!*escapes) return strlen(s);
6037     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6038     return n;
6039 }
6040
6041 int
6042 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6043 /* [HGM] moved here from winboard.c because of its general usefulness */
6044 /*       Basically a safe strcpy that uses the last character as King */
6045 {
6046     int result = FALSE; int NrPieces;
6047     unsigned char partner[EmptySquare];
6048
6049     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6050                     && NrPieces >= 12 && !(NrPieces&1)) {
6051         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6052
6053         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6054         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6055             char *p, c=0;
6056             if(map[j] == '/') offs = WhitePBishop - i, j++;
6057             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6058             table[i+offs] = map[j++];
6059             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6060             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6061             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6062         }
6063         table[(int) WhiteKing]  = map[j++];
6064         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6065             char *p, c=0;
6066             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6067             i = WHITE_TO_BLACK ii;
6068             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6069             table[i+offs] = map[j++];
6070             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6071             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6072             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6073         }
6074         table[(int) BlackKing]  = map[j++];
6075
6076
6077         if(*escapes) { // set up promotion pairing
6078             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6079             // pieceToChar entirely filled, so we can look up specified partners
6080             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6081                 int c = table[i];
6082                 if(c == '^' || c == '-') { // has specified partner
6083                     int p;
6084                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6085                     if(c == '^') table[i] = '+';
6086                     if(p < EmptySquare) {
6087                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6088                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6089                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6090                     }
6091                 } else if(c == '*') {
6092                     table[i] = partner[i];
6093                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6094                 }
6095             }
6096         }
6097
6098         result = TRUE;
6099     }
6100
6101     return result;
6102 }
6103
6104 int
6105 SetCharTable (unsigned char *table, const char * map)
6106 {
6107     return SetCharTableEsc(table, map, "");
6108 }
6109
6110 void
6111 Prelude (Board board)
6112 {       // [HGM] superchess: random selection of exo-pieces
6113         int i, j, k; ChessSquare p;
6114         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6115
6116         GetPositionNumber(); // use FRC position number
6117
6118         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6119             SetCharTable(pieceToChar, appData.pieceToCharTable);
6120             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6121                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6122         }
6123
6124         j = seed%4;                 seed /= 4;
6125         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6126         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6127         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6128         j = seed%3 + (seed%3 >= j); seed /= 3;
6129         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6130         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6131         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6132         j = seed%3;                 seed /= 3;
6133         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6134         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6135         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6136         j = seed%2 + (seed%2 >= j); seed /= 2;
6137         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6138         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6139         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6140         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6141         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6142         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6143         put(board, exoPieces[0],    0, 0, ANY);
6144         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6145 }
6146
6147 void
6148 InitPosition (int redraw)
6149 {
6150     ChessSquare (* pieces)[BOARD_FILES];
6151     int i, j, pawnRow=1, pieceRows=1, overrule,
6152     oldx = gameInfo.boardWidth,
6153     oldy = gameInfo.boardHeight,
6154     oldh = gameInfo.holdingsWidth;
6155     static int oldv;
6156
6157     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6158
6159     /* [AS] Initialize pv info list [HGM] and game status */
6160     {
6161         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6162             pvInfoList[i].depth = 0;
6163             boards[i][EP_STATUS] = EP_NONE;
6164             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6165         }
6166
6167         initialRulePlies = 0; /* 50-move counter start */
6168
6169         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6170         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6171     }
6172
6173
6174     /* [HGM] logic here is completely changed. In stead of full positions */
6175     /* the initialized data only consist of the two backranks. The switch */
6176     /* selects which one we will use, which is than copied to the Board   */
6177     /* initialPosition, which for the rest is initialized by Pawns and    */
6178     /* empty squares. This initial position is then copied to boards[0],  */
6179     /* possibly after shuffling, so that it remains available.            */
6180
6181     gameInfo.holdingsWidth = 0; /* default board sizes */
6182     gameInfo.boardWidth    = 8;
6183     gameInfo.boardHeight   = 8;
6184     gameInfo.holdingsSize  = 0;
6185     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6186     for(i=0; i<BOARD_FILES-6; i++)
6187       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6188     initialPosition[EP_STATUS] = EP_NONE;
6189     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6190     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6191     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6192          SetCharTable(pieceNickName, appData.pieceNickNames);
6193     else SetCharTable(pieceNickName, "............");
6194     pieces = FIDEArray;
6195
6196     switch (gameInfo.variant) {
6197     case VariantFischeRandom:
6198       shuffleOpenings = TRUE;
6199       appData.fischerCastling = TRUE;
6200     default:
6201       break;
6202     case VariantShatranj:
6203       pieces = ShatranjArray;
6204       nrCastlingRights = 0;
6205       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6206       break;
6207     case VariantMakruk:
6208       pieces = makrukArray;
6209       nrCastlingRights = 0;
6210       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6211       break;
6212     case VariantASEAN:
6213       pieces = aseanArray;
6214       nrCastlingRights = 0;
6215       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6216       break;
6217     case VariantTwoKings:
6218       pieces = twoKingsArray;
6219       break;
6220     case VariantGrand:
6221       pieces = GrandArray;
6222       nrCastlingRights = 0;
6223       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6224       gameInfo.boardWidth = 10;
6225       gameInfo.boardHeight = 10;
6226       gameInfo.holdingsSize = 7;
6227       break;
6228     case VariantCapaRandom:
6229       shuffleOpenings = TRUE;
6230       appData.fischerCastling = TRUE;
6231     case VariantCapablanca:
6232       pieces = CapablancaArray;
6233       gameInfo.boardWidth = 10;
6234       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6235       break;
6236     case VariantGothic:
6237       pieces = GothicArray;
6238       gameInfo.boardWidth = 10;
6239       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6240       break;
6241     case VariantSChess:
6242       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6243       gameInfo.holdingsSize = 7;
6244       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6245       break;
6246     case VariantJanus:
6247       pieces = JanusArray;
6248       gameInfo.boardWidth = 10;
6249       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6250       nrCastlingRights = 6;
6251         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6252         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6253         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6254         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6255         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6256         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6257       break;
6258     case VariantFalcon:
6259       pieces = FalconArray;
6260       gameInfo.boardWidth = 10;
6261       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6262       break;
6263     case VariantXiangqi:
6264       pieces = XiangqiArray;
6265       gameInfo.boardWidth  = 9;
6266       gameInfo.boardHeight = 10;
6267       nrCastlingRights = 0;
6268       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6269       break;
6270     case VariantShogi:
6271       pieces = ShogiArray;
6272       gameInfo.boardWidth  = 9;
6273       gameInfo.boardHeight = 9;
6274       gameInfo.holdingsSize = 7;
6275       nrCastlingRights = 0;
6276       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6277       break;
6278     case VariantChu:
6279       pieces = ChuArray; pieceRows = 3;
6280       gameInfo.boardWidth  = 12;
6281       gameInfo.boardHeight = 12;
6282       nrCastlingRights = 0;
6283 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6284   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6285       SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6286                                    "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6287       break;
6288     case VariantCourier:
6289       pieces = CourierArray;
6290       gameInfo.boardWidth  = 12;
6291       nrCastlingRights = 0;
6292       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6293       break;
6294     case VariantKnightmate:
6295       pieces = KnightmateArray;
6296       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6297       break;
6298     case VariantSpartan:
6299       pieces = SpartanArray;
6300       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6301       break;
6302     case VariantLion:
6303       pieces = lionArray;
6304       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6305       break;
6306     case VariantChuChess:
6307       pieces = ChuChessArray;
6308       gameInfo.boardWidth = 10;
6309       gameInfo.boardHeight = 10;
6310       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6311       break;
6312     case VariantFairy:
6313       pieces = fairyArray;
6314       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6315       break;
6316     case VariantGreat:
6317       pieces = GreatArray;
6318       gameInfo.boardWidth = 10;
6319       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6320       gameInfo.holdingsSize = 8;
6321       break;
6322     case VariantSuper:
6323       pieces = FIDEArray;
6324       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6325       gameInfo.holdingsSize = 8;
6326       startedFromSetupPosition = TRUE;
6327       break;
6328     case VariantCrazyhouse:
6329     case VariantBughouse:
6330       pieces = FIDEArray;
6331       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6332       gameInfo.holdingsSize = 5;
6333       break;
6334     case VariantWildCastle:
6335       pieces = FIDEArray;
6336       /* !!?shuffle with kings guaranteed to be on d or e file */
6337       shuffleOpenings = 1;
6338       break;
6339     case VariantNoCastle:
6340       pieces = FIDEArray;
6341       nrCastlingRights = 0;
6342       /* !!?unconstrained back-rank shuffle */
6343       shuffleOpenings = 1;
6344       break;
6345     }
6346
6347     overrule = 0;
6348     if(appData.NrFiles >= 0) {
6349         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6350         gameInfo.boardWidth = appData.NrFiles;
6351     }
6352     if(appData.NrRanks >= 0) {
6353         gameInfo.boardHeight = appData.NrRanks;
6354     }
6355     if(appData.holdingsSize >= 0) {
6356         i = appData.holdingsSize;
6357         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6358         gameInfo.holdingsSize = i;
6359     }
6360     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6361     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6362         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6363
6364     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6365     if(pawnRow < 1) pawnRow = 1;
6366     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6367        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6368     if(gameInfo.variant == VariantChu) pawnRow = 3;
6369
6370     /* User pieceToChar list overrules defaults */
6371     if(appData.pieceToCharTable != NULL)
6372         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6373
6374     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6375
6376         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6377             s = (ChessSquare) 0; /* account holding counts in guard band */
6378         for( i=0; i<BOARD_HEIGHT; i++ )
6379             initialPosition[i][j] = s;
6380
6381         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6382         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6383         initialPosition[pawnRow][j] = WhitePawn;
6384         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6385         if(gameInfo.variant == VariantXiangqi) {
6386             if(j&1) {
6387                 initialPosition[pawnRow][j] =
6388                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6389                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6390                    initialPosition[2][j] = WhiteCannon;
6391                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6392                 }
6393             }
6394         }
6395         if(gameInfo.variant == VariantChu) {
6396              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6397                initialPosition[pawnRow+1][j] = WhiteCobra,
6398                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6399              for(i=1; i<pieceRows; i++) {
6400                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6401                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6402              }
6403         }
6404         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6405             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6406                initialPosition[0][j] = WhiteRook;
6407                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6408             }
6409         }
6410         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6411     }
6412     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6413     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6414
6415             j=BOARD_LEFT+1;
6416             initialPosition[1][j] = WhiteBishop;
6417             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6418             j=BOARD_RGHT-2;
6419             initialPosition[1][j] = WhiteRook;
6420             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6421     }
6422
6423     if( nrCastlingRights == -1) {
6424         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6425         /*       This sets default castling rights from none to normal corners   */
6426         /* Variants with other castling rights must set them themselves above    */
6427         nrCastlingRights = 6;
6428
6429         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6430         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6431         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6432         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6433         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6434         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6435      }
6436
6437      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6438      if(gameInfo.variant == VariantGreat) { // promotion commoners
6439         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6440         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6441         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6442         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6443      }
6444      if( gameInfo.variant == VariantSChess ) {
6445       initialPosition[1][0] = BlackMarshall;
6446       initialPosition[2][0] = BlackAngel;
6447       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6448       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6449       initialPosition[1][1] = initialPosition[2][1] =
6450       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6451      }
6452   if (appData.debugMode) {
6453     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6454   }
6455     if(shuffleOpenings) {
6456         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6457         startedFromSetupPosition = TRUE;
6458     }
6459     if(startedFromPositionFile) {
6460       /* [HGM] loadPos: use PositionFile for every new game */
6461       CopyBoard(initialPosition, filePosition);
6462       for(i=0; i<nrCastlingRights; i++)
6463           initialRights[i] = filePosition[CASTLING][i];
6464       startedFromSetupPosition = TRUE;
6465     }
6466     if(*appData.men) LoadPieceDesc(appData.men);
6467
6468     CopyBoard(boards[0], initialPosition);
6469
6470     if(oldx != gameInfo.boardWidth ||
6471        oldy != gameInfo.boardHeight ||
6472        oldv != gameInfo.variant ||
6473        oldh != gameInfo.holdingsWidth
6474                                          )
6475             InitDrawingSizes(-2 ,0);
6476
6477     oldv = gameInfo.variant;
6478     if (redraw)
6479       DrawPosition(TRUE, boards[currentMove]);
6480 }
6481
6482 void
6483 SendBoard (ChessProgramState *cps, int moveNum)
6484 {
6485     char message[MSG_SIZ];
6486
6487     if (cps->useSetboard) {
6488       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6489       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6490       SendToProgram(message, cps);
6491       free(fen);
6492
6493     } else {
6494       ChessSquare *bp;
6495       int i, j, left=0, right=BOARD_WIDTH;
6496       /* Kludge to set black to move, avoiding the troublesome and now
6497        * deprecated "black" command.
6498        */
6499       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6500         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6501
6502       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6503
6504       SendToProgram("edit\n", cps);
6505       SendToProgram("#\n", cps);
6506       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6507         bp = &boards[moveNum][i][left];
6508         for (j = left; j < right; j++, bp++) {
6509           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6510           if ((int) *bp < (int) BlackPawn) {
6511             if(j == BOARD_RGHT+1)
6512                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6513             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6514             if(message[0] == '+' || message[0] == '~') {
6515               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6516                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6517                         AAA + j, ONE + i - '0');
6518             }
6519             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6520                 message[1] = BOARD_RGHT   - 1 - j + '1';
6521                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6522             }
6523             SendToProgram(message, cps);
6524           }
6525         }
6526       }
6527
6528       SendToProgram("c\n", cps);
6529       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6530         bp = &boards[moveNum][i][left];
6531         for (j = left; j < right; j++, bp++) {
6532           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6533           if (((int) *bp != (int) EmptySquare)
6534               && ((int) *bp >= (int) BlackPawn)) {
6535             if(j == BOARD_LEFT-2)
6536                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6537             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6538                     AAA + j, ONE + i - '0');
6539             if(message[0] == '+' || message[0] == '~') {
6540               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6541                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6542                         AAA + j, ONE + i - '0');
6543             }
6544             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6545                 message[1] = BOARD_RGHT   - 1 - j + '1';
6546                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6547             }
6548             SendToProgram(message, cps);
6549           }
6550         }
6551       }
6552
6553       SendToProgram(".\n", cps);
6554     }
6555     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6556 }
6557
6558 char exclusionHeader[MSG_SIZ];
6559 int exCnt, excludePtr;
6560 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6561 static Exclusion excluTab[200];
6562 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6563
6564 static void
6565 WriteMap (int s)
6566 {
6567     int j;
6568     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6569     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6570 }
6571
6572 static void
6573 ClearMap ()
6574 {
6575     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6576     excludePtr = 24; exCnt = 0;
6577     WriteMap(0);
6578 }
6579
6580 static void
6581 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6582 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6583     char buf[2*MOVE_LEN], *p;
6584     Exclusion *e = excluTab;
6585     int i;
6586     for(i=0; i<exCnt; i++)
6587         if(e[i].ff == fromX && e[i].fr == fromY &&
6588            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6589     if(i == exCnt) { // was not in exclude list; add it
6590         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6591         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6592             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6593             return; // abort
6594         }
6595         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6596         excludePtr++; e[i].mark = excludePtr++;
6597         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6598         exCnt++;
6599     }
6600     exclusionHeader[e[i].mark] = state;
6601 }
6602
6603 static int
6604 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6605 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6606     char buf[MSG_SIZ];
6607     int j, k;
6608     ChessMove moveType;
6609     if((signed char)promoChar == -1) { // kludge to indicate best move
6610         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6611             return 1; // if unparsable, abort
6612     }
6613     // update exclusion map (resolving toggle by consulting existing state)
6614     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6615     j = k%8; k >>= 3;
6616     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6617     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6618          excludeMap[k] |=   1<<j;
6619     else excludeMap[k] &= ~(1<<j);
6620     // update header
6621     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6622     // inform engine
6623     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6624     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6625     SendToBoth(buf);
6626     return (state == '+');
6627 }
6628
6629 static void
6630 ExcludeClick (int index)
6631 {
6632     int i, j;
6633     Exclusion *e = excluTab;
6634     if(index < 25) { // none, best or tail clicked
6635         if(index < 13) { // none: include all
6636             WriteMap(0); // clear map
6637             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6638             SendToBoth("include all\n"); // and inform engine
6639         } else if(index > 18) { // tail
6640             if(exclusionHeader[19] == '-') { // tail was excluded
6641                 SendToBoth("include all\n");
6642                 WriteMap(0); // clear map completely
6643                 // now re-exclude selected moves
6644                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6645                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6646             } else { // tail was included or in mixed state
6647                 SendToBoth("exclude all\n");
6648                 WriteMap(0xFF); // fill map completely
6649                 // now re-include selected moves
6650                 j = 0; // count them
6651                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6652                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6653                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6654             }
6655         } else { // best
6656             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6657         }
6658     } else {
6659         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6660             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6661             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6662             break;
6663         }
6664     }
6665 }
6666
6667 ChessSquare
6668 DefaultPromoChoice (int white)
6669 {
6670     ChessSquare result;
6671     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6672        gameInfo.variant == VariantMakruk)
6673         result = WhiteFerz; // no choice
6674     else if(gameInfo.variant == VariantASEAN)
6675         result = WhiteRook; // no choice
6676     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6677         result= WhiteKing; // in Suicide Q is the last thing we want
6678     else if(gameInfo.variant == VariantSpartan)
6679         result = white ? WhiteQueen : WhiteAngel;
6680     else result = WhiteQueen;
6681     if(!white) result = WHITE_TO_BLACK result;
6682     return result;
6683 }
6684
6685 static int autoQueen; // [HGM] oneclick
6686
6687 int
6688 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6689 {
6690     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6691     /* [HGM] add Shogi promotions */
6692     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6693     ChessSquare piece, partner;
6694     ChessMove moveType;
6695     Boolean premove;
6696
6697     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6698     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6699
6700     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6701       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6702         return FALSE;
6703
6704     piece = boards[currentMove][fromY][fromX];
6705     if(gameInfo.variant == VariantChu) {
6706         promotionZoneSize = BOARD_HEIGHT/3;
6707         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6708     } else if(gameInfo.variant == VariantShogi) {
6709         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6710         highestPromotingPiece = (int)WhiteAlfil;
6711     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6712         promotionZoneSize = 3;
6713     }
6714
6715     // Treat Lance as Pawn when it is not representing Amazon or Lance
6716     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6717         if(piece == WhiteLance) piece = WhitePawn; else
6718         if(piece == BlackLance) piece = BlackPawn;
6719     }
6720
6721     // next weed out all moves that do not touch the promotion zone at all
6722     if((int)piece >= BlackPawn) {
6723         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6724              return FALSE;
6725         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6726         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6727     } else {
6728         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6729            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6730         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6731              return FALSE;
6732     }
6733
6734     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6735
6736     // weed out mandatory Shogi promotions
6737     if(gameInfo.variant == VariantShogi) {
6738         if(piece >= BlackPawn) {
6739             if(toY == 0 && piece == BlackPawn ||
6740                toY == 0 && piece == BlackQueen ||
6741                toY <= 1 && piece == BlackKnight) {
6742                 *promoChoice = '+';
6743                 return FALSE;
6744             }
6745         } else {
6746             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6747                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6748                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6749                 *promoChoice = '+';
6750                 return FALSE;
6751             }
6752         }
6753     }
6754
6755     // weed out obviously illegal Pawn moves
6756     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6757         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6758         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6759         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6760         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6761         // note we are not allowed to test for valid (non-)capture, due to premove
6762     }
6763
6764     // we either have a choice what to promote to, or (in Shogi) whether to promote
6765     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6766        gameInfo.variant == VariantMakruk) {
6767         ChessSquare p=BlackFerz;  // no choice
6768         while(p < EmptySquare) {  //but make sure we use piece that exists
6769             *promoChoice = PieceToChar(p++);
6770             if(*promoChoice != '.') break;
6771         }
6772         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6773     }
6774     // no sense asking what we must promote to if it is going to explode...
6775     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6776         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6777         return FALSE;
6778     }
6779     // give caller the default choice even if we will not make it
6780     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6781     partner = piece; // pieces can promote if the pieceToCharTable says so
6782     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6783     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6784     if(        sweepSelect && gameInfo.variant != VariantGreat
6785                            && gameInfo.variant != VariantGrand
6786                            && gameInfo.variant != VariantSuper) return FALSE;
6787     if(autoQueen) return FALSE; // predetermined
6788
6789     // suppress promotion popup on illegal moves that are not premoves
6790     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6791               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6792     if(appData.testLegality && !premove) {
6793         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6794                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6795         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6796         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6797             return FALSE;
6798     }
6799
6800     return TRUE;
6801 }
6802
6803 int
6804 InPalace (int row, int column)
6805 {   /* [HGM] for Xiangqi */
6806     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6807          column < (BOARD_WIDTH + 4)/2 &&
6808          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6809     return FALSE;
6810 }
6811
6812 int
6813 PieceForSquare (int x, int y)
6814 {
6815   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6816      return -1;
6817   else
6818      return boards[currentMove][y][x];
6819 }
6820
6821 int
6822 OKToStartUserMove (int x, int y)
6823 {
6824     ChessSquare from_piece;
6825     int white_piece;
6826
6827     if (matchMode) return FALSE;
6828     if (gameMode == EditPosition) return TRUE;
6829
6830     if (x >= 0 && y >= 0)
6831       from_piece = boards[currentMove][y][x];
6832     else
6833       from_piece = EmptySquare;
6834
6835     if (from_piece == EmptySquare) return FALSE;
6836
6837     white_piece = (int)from_piece >= (int)WhitePawn &&
6838       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6839
6840     switch (gameMode) {
6841       case AnalyzeFile:
6842       case TwoMachinesPlay:
6843       case EndOfGame:
6844         return FALSE;
6845
6846       case IcsObserving:
6847       case IcsIdle:
6848         return FALSE;
6849
6850       case MachinePlaysWhite:
6851       case IcsPlayingBlack:
6852         if (appData.zippyPlay) return FALSE;
6853         if (white_piece) {
6854             DisplayMoveError(_("You are playing Black"));
6855             return FALSE;
6856         }
6857         break;
6858
6859       case MachinePlaysBlack:
6860       case IcsPlayingWhite:
6861         if (appData.zippyPlay) return FALSE;
6862         if (!white_piece) {
6863             DisplayMoveError(_("You are playing White"));
6864             return FALSE;
6865         }
6866         break;
6867
6868       case PlayFromGameFile:
6869             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6870       case EditGame:
6871       case AnalyzeMode:
6872         if (!white_piece && WhiteOnMove(currentMove)) {
6873             DisplayMoveError(_("It is White's turn"));
6874             return FALSE;
6875         }
6876         if (white_piece && !WhiteOnMove(currentMove)) {
6877             DisplayMoveError(_("It is Black's turn"));
6878             return FALSE;
6879         }
6880         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6881             /* Editing correspondence game history */
6882             /* Could disallow this or prompt for confirmation */
6883             cmailOldMove = -1;
6884         }
6885         break;
6886
6887       case BeginningOfGame:
6888         if (appData.icsActive) return FALSE;
6889         if (!appData.noChessProgram) {
6890             if (!white_piece) {
6891                 DisplayMoveError(_("You are playing White"));
6892                 return FALSE;
6893             }
6894         }
6895         break;
6896
6897       case Training:
6898         if (!white_piece && WhiteOnMove(currentMove)) {
6899             DisplayMoveError(_("It is White's turn"));
6900             return FALSE;
6901         }
6902         if (white_piece && !WhiteOnMove(currentMove)) {
6903             DisplayMoveError(_("It is Black's turn"));
6904             return FALSE;
6905         }
6906         break;
6907
6908       default:
6909       case IcsExamining:
6910         break;
6911     }
6912     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6913         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6914         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6915         && gameMode != AnalyzeFile && gameMode != Training) {
6916         DisplayMoveError(_("Displayed position is not current"));
6917         return FALSE;
6918     }
6919     return TRUE;
6920 }
6921
6922 Boolean
6923 OnlyMove (int *x, int *y, Boolean captures)
6924 {
6925     DisambiguateClosure cl;
6926     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6927     switch(gameMode) {
6928       case MachinePlaysBlack:
6929       case IcsPlayingWhite:
6930       case BeginningOfGame:
6931         if(!WhiteOnMove(currentMove)) return FALSE;
6932         break;
6933       case MachinePlaysWhite:
6934       case IcsPlayingBlack:
6935         if(WhiteOnMove(currentMove)) return FALSE;
6936         break;
6937       case EditGame:
6938         break;
6939       default:
6940         return FALSE;
6941     }
6942     cl.pieceIn = EmptySquare;
6943     cl.rfIn = *y;
6944     cl.ffIn = *x;
6945     cl.rtIn = -1;
6946     cl.ftIn = -1;
6947     cl.promoCharIn = NULLCHAR;
6948     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6949     if( cl.kind == NormalMove ||
6950         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6951         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6952         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6953       fromX = cl.ff;
6954       fromY = cl.rf;
6955       *x = cl.ft;
6956       *y = cl.rt;
6957       return TRUE;
6958     }
6959     if(cl.kind != ImpossibleMove) return FALSE;
6960     cl.pieceIn = EmptySquare;
6961     cl.rfIn = -1;
6962     cl.ffIn = -1;
6963     cl.rtIn = *y;
6964     cl.ftIn = *x;
6965     cl.promoCharIn = NULLCHAR;
6966     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6967     if( cl.kind == NormalMove ||
6968         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6969         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6970         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6971       fromX = cl.ff;
6972       fromY = cl.rf;
6973       *x = cl.ft;
6974       *y = cl.rt;
6975       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6976       return TRUE;
6977     }
6978     return FALSE;
6979 }
6980
6981 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6982 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6983 int lastLoadGameUseList = FALSE;
6984 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6985 ChessMove lastLoadGameStart = EndOfFile;
6986 int doubleClick;
6987 Boolean addToBookFlag;
6988 static Board rightsBoard, nullBoard;
6989
6990 void
6991 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6992 {
6993     ChessMove moveType;
6994     ChessSquare pup;
6995     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6996
6997     /* Check if the user is playing in turn.  This is complicated because we
6998        let the user "pick up" a piece before it is his turn.  So the piece he
6999        tried to pick up may have been captured by the time he puts it down!
7000        Therefore we use the color the user is supposed to be playing in this
7001        test, not the color of the piece that is currently on the starting
7002        square---except in EditGame mode, where the user is playing both
7003        sides; fortunately there the capture race can't happen.  (It can
7004        now happen in IcsExamining mode, but that's just too bad.  The user
7005        will get a somewhat confusing message in that case.)
7006        */
7007
7008     switch (gameMode) {
7009       case AnalyzeFile:
7010       case TwoMachinesPlay:
7011       case EndOfGame:
7012       case IcsObserving:
7013       case IcsIdle:
7014         /* We switched into a game mode where moves are not accepted,
7015            perhaps while the mouse button was down. */
7016         return;
7017
7018       case MachinePlaysWhite:
7019         /* User is moving for Black */
7020         if (WhiteOnMove(currentMove)) {
7021             DisplayMoveError(_("It is White's turn"));
7022             return;
7023         }
7024         break;
7025
7026       case MachinePlaysBlack:
7027         /* User is moving for White */
7028         if (!WhiteOnMove(currentMove)) {
7029             DisplayMoveError(_("It is Black's turn"));
7030             return;
7031         }
7032         break;
7033
7034       case PlayFromGameFile:
7035             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7036       case EditGame:
7037       case IcsExamining:
7038       case BeginningOfGame:
7039       case AnalyzeMode:
7040       case Training:
7041         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7042         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7043             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7044             /* User is moving for Black */
7045             if (WhiteOnMove(currentMove)) {
7046                 DisplayMoveError(_("It is White's turn"));
7047                 return;
7048             }
7049         } else {
7050             /* User is moving for White */
7051             if (!WhiteOnMove(currentMove)) {
7052                 DisplayMoveError(_("It is Black's turn"));
7053                 return;
7054             }
7055         }
7056         break;
7057
7058       case IcsPlayingBlack:
7059         /* User is moving for Black */
7060         if (WhiteOnMove(currentMove)) {
7061             if (!appData.premove) {
7062                 DisplayMoveError(_("It is White's turn"));
7063             } else if (toX >= 0 && toY >= 0) {
7064                 premoveToX = toX;
7065                 premoveToY = toY;
7066                 premoveFromX = fromX;
7067                 premoveFromY = fromY;
7068                 premovePromoChar = promoChar;
7069                 gotPremove = 1;
7070                 if (appData.debugMode)
7071                     fprintf(debugFP, "Got premove: fromX %d,"
7072                             "fromY %d, toX %d, toY %d\n",
7073                             fromX, fromY, toX, toY);
7074             }
7075             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7076             return;
7077         }
7078         break;
7079
7080       case IcsPlayingWhite:
7081         /* User is moving for White */
7082         if (!WhiteOnMove(currentMove)) {
7083             if (!appData.premove) {
7084                 DisplayMoveError(_("It is Black's turn"));
7085             } else if (toX >= 0 && toY >= 0) {
7086                 premoveToX = toX;
7087                 premoveToY = toY;
7088                 premoveFromX = fromX;
7089                 premoveFromY = fromY;
7090                 premovePromoChar = promoChar;
7091                 gotPremove = 1;
7092                 if (appData.debugMode)
7093                     fprintf(debugFP, "Got premove: fromX %d,"
7094                             "fromY %d, toX %d, toY %d\n",
7095                             fromX, fromY, toX, toY);
7096             }
7097             DrawPosition(TRUE, boards[currentMove]);
7098             return;
7099         }
7100         break;
7101
7102       default:
7103         break;
7104
7105       case EditPosition:
7106         /* EditPosition, empty square, or different color piece;
7107            click-click move is possible */
7108         if (toX == -2 || toY == -2) {
7109             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7110             DrawPosition(FALSE, boards[currentMove]);
7111             return;
7112         } else if (toX >= 0 && toY >= 0) {
7113             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7114                 ChessSquare p = boards[0][rf][ff];
7115                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7116                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7117                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7118                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7119                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7120                     gatingPiece = p;
7121                 }
7122             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7123             boards[0][toY][toX] = boards[0][fromY][fromX];
7124             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7125                 if(boards[0][fromY][0] != EmptySquare) {
7126                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7127                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7128                 }
7129             } else
7130             if(fromX == BOARD_RGHT+1) {
7131                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7132                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7133                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7134                 }
7135             } else
7136             boards[0][fromY][fromX] = gatingPiece;
7137             ClearHighlights();
7138             DrawPosition(FALSE, boards[currentMove]);
7139             return;
7140         }
7141         return;
7142     }
7143
7144     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7145     pup = boards[currentMove][toY][toX];
7146
7147     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7148     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7149          if( pup != EmptySquare ) return;
7150          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7151            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7152                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7153            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7154            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7155            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7156            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7157          fromY = DROP_RANK;
7158     }
7159
7160     /* [HGM] always test for legality, to get promotion info */
7161     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7162                                          fromY, fromX, toY, toX, promoChar);
7163
7164     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7165
7166     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7167
7168     /* [HGM] but possibly ignore an IllegalMove result */
7169     if (appData.testLegality) {
7170         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7171             DisplayMoveError(_("Illegal move"));
7172             return;
7173         }
7174     }
7175
7176     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7177         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7178              ClearPremoveHighlights(); // was included
7179         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7180         DrawPosition(FALSE, NULL);
7181         return;
7182     }
7183
7184     if(addToBookFlag) { // adding moves to book
7185         char buf[MSG_SIZ], move[MSG_SIZ];
7186         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7187         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7188                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7189         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7190         AddBookMove(buf);
7191         addToBookFlag = FALSE;
7192         ClearHighlights();
7193         return;
7194     }
7195
7196     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7197 }
7198
7199 /* Common tail of UserMoveEvent and DropMenuEvent */
7200 int
7201 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7202 {
7203     char *bookHit = 0;
7204
7205     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7206         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7207         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7208         if(WhiteOnMove(currentMove)) {
7209             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7210         } else {
7211             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7212         }
7213     }
7214
7215     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7216        move type in caller when we know the move is a legal promotion */
7217     if(moveType == NormalMove && promoChar)
7218         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7219
7220     /* [HGM] <popupFix> The following if has been moved here from
7221        UserMoveEvent(). Because it seemed to belong here (why not allow
7222        piece drops in training games?), and because it can only be
7223        performed after it is known to what we promote. */
7224     if (gameMode == Training) {
7225       /* compare the move played on the board to the next move in the
7226        * game. If they match, display the move and the opponent's response.
7227        * If they don't match, display an error message.
7228        */
7229       int saveAnimate;
7230       Board testBoard;
7231       CopyBoard(testBoard, boards[currentMove]);
7232       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7233
7234       if (CompareBoards(testBoard, boards[currentMove+1])) {
7235         ForwardInner(currentMove+1);
7236
7237         /* Autoplay the opponent's response.
7238          * if appData.animate was TRUE when Training mode was entered,
7239          * the response will be animated.
7240          */
7241         saveAnimate = appData.animate;
7242         appData.animate = animateTraining;
7243         ForwardInner(currentMove+1);
7244         appData.animate = saveAnimate;
7245
7246         /* check for the end of the game */
7247         if (currentMove >= forwardMostMove) {
7248           gameMode = PlayFromGameFile;
7249           ModeHighlight();
7250           SetTrainingModeOff();
7251           DisplayInformation(_("End of game"));
7252         }
7253       } else {
7254         DisplayError(_("Incorrect move"), 0);
7255       }
7256       return 1;
7257     }
7258
7259   /* Ok, now we know that the move is good, so we can kill
7260      the previous line in Analysis Mode */
7261   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7262                                 && currentMove < forwardMostMove) {
7263     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7264     else forwardMostMove = currentMove;
7265   }
7266
7267   ClearMap();
7268
7269   /* If we need the chess program but it's dead, restart it */
7270   ResurrectChessProgram();
7271
7272   /* A user move restarts a paused game*/
7273   if (pausing)
7274     PauseEvent();
7275
7276   thinkOutput[0] = NULLCHAR;
7277
7278   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7279
7280   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7281     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7282     return 1;
7283   }
7284
7285   if (gameMode == BeginningOfGame) {
7286     if (appData.noChessProgram) {
7287       gameMode = EditGame;
7288       SetGameInfo();
7289     } else {
7290       char buf[MSG_SIZ];
7291       gameMode = MachinePlaysBlack;
7292       StartClocks();
7293       SetGameInfo();
7294       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7295       DisplayTitle(buf);
7296       if (first.sendName) {
7297         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7298         SendToProgram(buf, &first);
7299       }
7300       StartClocks();
7301     }
7302     ModeHighlight();
7303   }
7304
7305   /* Relay move to ICS or chess engine */
7306   if (appData.icsActive) {
7307     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7308         gameMode == IcsExamining) {
7309       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7310         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7311         SendToICS("draw ");
7312         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7313       }
7314       // also send plain move, in case ICS does not understand atomic claims
7315       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7316       ics_user_moved = 1;
7317     }
7318   } else {
7319     if (first.sendTime && (gameMode == BeginningOfGame ||
7320                            gameMode == MachinePlaysWhite ||
7321                            gameMode == MachinePlaysBlack)) {
7322       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7323     }
7324     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7325          // [HGM] book: if program might be playing, let it use book
7326         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7327         first.maybeThinking = TRUE;
7328     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7329         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7330         SendBoard(&first, currentMove+1);
7331         if(second.analyzing) {
7332             if(!second.useSetboard) SendToProgram("undo\n", &second);
7333             SendBoard(&second, currentMove+1);
7334         }
7335     } else {
7336         SendMoveToProgram(forwardMostMove-1, &first);
7337         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7338     }
7339     if (currentMove == cmailOldMove + 1) {
7340       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7341     }
7342   }
7343
7344   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7345
7346   switch (gameMode) {
7347   case EditGame:
7348     if(appData.testLegality)
7349     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7350     case MT_NONE:
7351     case MT_CHECK:
7352       break;
7353     case MT_CHECKMATE:
7354     case MT_STAINMATE:
7355       if (WhiteOnMove(currentMove)) {
7356         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7357       } else {
7358         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7359       }
7360       break;
7361     case MT_STALEMATE:
7362       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7363       break;
7364     }
7365     break;
7366
7367   case MachinePlaysBlack:
7368   case MachinePlaysWhite:
7369     /* disable certain menu options while machine is thinking */
7370     SetMachineThinkingEnables();
7371     break;
7372
7373   default:
7374     break;
7375   }
7376
7377   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7378   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7379
7380   if(bookHit) { // [HGM] book: simulate book reply
7381         static char bookMove[MSG_SIZ]; // a bit generous?
7382
7383         programStats.nodes = programStats.depth = programStats.time =
7384         programStats.score = programStats.got_only_move = 0;
7385         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7386
7387         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7388         strcat(bookMove, bookHit);
7389         HandleMachineMove(bookMove, &first);
7390   }
7391   return 1;
7392 }
7393
7394 void
7395 MarkByFEN(char *fen)
7396 {
7397         int r, f;
7398         if(!appData.markers || !appData.highlightDragging) return;
7399         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7400         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7401         while(*fen) {
7402             int s = 0;
7403             marker[r][f] = 0;
7404             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7405             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7406             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7407             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7408             if(*fen == 'T') marker[r][f++] = 0; else
7409             if(*fen == 'Y') marker[r][f++] = 1; else
7410             if(*fen == 'G') marker[r][f++] = 3; else
7411             if(*fen == 'B') marker[r][f++] = 4; else
7412             if(*fen == 'C') marker[r][f++] = 5; else
7413             if(*fen == 'M') marker[r][f++] = 6; else
7414             if(*fen == 'W') marker[r][f++] = 7; else
7415             if(*fen == 'D') marker[r][f++] = 8; else
7416             if(*fen == 'R') marker[r][f++] = 2; else {
7417                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7418               f += s; fen -= s>0;
7419             }
7420             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7421             if(r < 0) break;
7422             fen++;
7423         }
7424         DrawPosition(TRUE, NULL);
7425 }
7426
7427 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7428
7429 void
7430 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7431 {
7432     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7433     Markers *m = (Markers *) closure;
7434     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7435                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7436         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7437                          || kind == WhiteCapturesEnPassant
7438                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7439     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7440 }
7441
7442 static int hoverSavedValid;
7443
7444 void
7445 MarkTargetSquares (int clear)
7446 {
7447   int x, y, sum=0;
7448   if(clear) { // no reason to ever suppress clearing
7449     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7450     hoverSavedValid = 0;
7451     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7452   } else {
7453     int capt = 0;
7454     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7455        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7456     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7457     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7458       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7459       if(capt)
7460       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7461     }
7462   }
7463   DrawPosition(FALSE, NULL);
7464 }
7465
7466 int
7467 Explode (Board board, int fromX, int fromY, int toX, int toY)
7468 {
7469     if(gameInfo.variant == VariantAtomic &&
7470        (board[toY][toX] != EmptySquare ||                     // capture?
7471         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7472                          board[fromY][fromX] == BlackPawn   )
7473       )) {
7474         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7475         return TRUE;
7476     }
7477     return FALSE;
7478 }
7479
7480 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7481
7482 int
7483 CanPromote (ChessSquare piece, int y)
7484 {
7485         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7486         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7487         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7488         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7489            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7490           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7491            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7492         return (piece == BlackPawn && y <= zone ||
7493                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7494                 piece == BlackLance && y <= zone ||
7495                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7496 }
7497
7498 void
7499 HoverEvent (int xPix, int yPix, int x, int y)
7500 {
7501         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7502         int r, f;
7503         if(!first.highlight) return;
7504         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7505         if(x == oldX && y == oldY) return; // only do something if we enter new square
7506         oldFromX = fromX; oldFromY = fromY;
7507         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7508           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7509             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7510           hoverSavedValid = 1;
7511         } else if(oldX != x || oldY != y) {
7512           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7513           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7514           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7515             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7516           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7517             char buf[MSG_SIZ];
7518             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7519             SendToProgram(buf, &first);
7520           }
7521           oldX = x; oldY = y;
7522 //        SetHighlights(fromX, fromY, x, y);
7523         }
7524 }
7525
7526 void ReportClick(char *action, int x, int y)
7527 {
7528         char buf[MSG_SIZ]; // Inform engine of what user does
7529         int r, f;
7530         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7531           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7532             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7533         if(!first.highlight || gameMode == EditPosition) return;
7534         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7535         SendToProgram(buf, &first);
7536 }
7537
7538 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7539
7540 void
7541 LeftClick (ClickType clickType, int xPix, int yPix)
7542 {
7543     int x, y;
7544     Boolean saveAnimate;
7545     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7546     char promoChoice = NULLCHAR;
7547     ChessSquare piece;
7548     static TimeMark lastClickTime, prevClickTime;
7549
7550     if(flashing) return;
7551
7552     x = EventToSquare(xPix, BOARD_WIDTH);
7553     y = EventToSquare(yPix, BOARD_HEIGHT);
7554     if (!flipView && y >= 0) {
7555         y = BOARD_HEIGHT - 1 - y;
7556     }
7557     if (flipView && x >= 0) {
7558         x = BOARD_WIDTH - 1 - x;
7559     }
7560
7561     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7562         static int dummy;
7563         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7564         right = TRUE;
7565         return;
7566     }
7567
7568     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7569
7570     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7571
7572     if (clickType == Press) ErrorPopDown();
7573     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7574
7575     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7576         defaultPromoChoice = promoSweep;
7577         promoSweep = EmptySquare;   // terminate sweep
7578         promoDefaultAltered = TRUE;
7579         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7580     }
7581
7582     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7583         if(clickType == Release) return; // ignore upclick of click-click destination
7584         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7585         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7586         if(gameInfo.holdingsWidth &&
7587                 (WhiteOnMove(currentMove)
7588                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7589                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7590             // click in right holdings, for determining promotion piece
7591             ChessSquare p = boards[currentMove][y][x];
7592             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7593             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7594             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7595                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7596                 fromX = fromY = -1;
7597                 return;
7598             }
7599         }
7600         DrawPosition(FALSE, boards[currentMove]);
7601         return;
7602     }
7603
7604     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7605     if(clickType == Press
7606             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7607               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7608               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7609         return;
7610
7611     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7612         // could be static click on premove from-square: abort premove
7613         gotPremove = 0;
7614         ClearPremoveHighlights();
7615     }
7616
7617     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7618         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7619
7620     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7621         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7622                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7623         defaultPromoChoice = DefaultPromoChoice(side);
7624     }
7625
7626     autoQueen = appData.alwaysPromoteToQueen;
7627
7628     if (fromX == -1) {
7629       int originalY = y;
7630       gatingPiece = EmptySquare;
7631       if (clickType != Press) {
7632         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7633             DragPieceEnd(xPix, yPix); dragging = 0;
7634             DrawPosition(FALSE, NULL);
7635         }
7636         return;
7637       }
7638       doubleClick = FALSE;
7639       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7640         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7641       }
7642       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7643       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7644          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7645          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7646             /* First square */
7647             if (OKToStartUserMove(fromX, fromY)) {
7648                 second = 0;
7649                 ReportClick("lift", x, y);
7650                 MarkTargetSquares(0);
7651                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7652                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7653                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7654                     promoSweep = defaultPromoChoice;
7655                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7656                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7657                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7658                 }
7659                 if (appData.highlightDragging) {
7660                     SetHighlights(fromX, fromY, -1, -1);
7661                 } else {
7662                     ClearHighlights();
7663                 }
7664             } else fromX = fromY = -1;
7665             return;
7666         }
7667     }
7668
7669     /* fromX != -1 */
7670     if (clickType == Press && gameMode != EditPosition) {
7671         ChessSquare fromP;
7672         ChessSquare toP;
7673         int frc;
7674
7675         // ignore off-board to clicks
7676         if(y < 0 || x < 0) return;
7677
7678         /* Check if clicking again on the same color piece */
7679         fromP = boards[currentMove][fromY][fromX];
7680         toP = boards[currentMove][y][x];
7681         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7682         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7683             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7684            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7685              WhitePawn <= toP && toP <= WhiteKing &&
7686              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7687              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7688             (BlackPawn <= fromP && fromP <= BlackKing &&
7689              BlackPawn <= toP && toP <= BlackKing &&
7690              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7691              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7692             /* Clicked again on same color piece -- changed his mind */
7693             second = (x == fromX && y == fromY);
7694             killX = killY = kill2X = kill2Y = -1;
7695             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7696                 second = FALSE; // first double-click rather than scond click
7697                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7698             }
7699             promoDefaultAltered = FALSE;
7700            if(!second) MarkTargetSquares(1);
7701            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7702             if (appData.highlightDragging) {
7703                 SetHighlights(x, y, -1, -1);
7704             } else {
7705                 ClearHighlights();
7706             }
7707             if (OKToStartUserMove(x, y)) {
7708                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7709                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7710                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7711                  gatingPiece = boards[currentMove][fromY][fromX];
7712                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7713                 fromX = x;
7714                 fromY = y; dragging = 1;
7715                 if(!second) ReportClick("lift", x, y);
7716                 MarkTargetSquares(0);
7717                 DragPieceBegin(xPix, yPix, FALSE);
7718                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7719                     promoSweep = defaultPromoChoice;
7720                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7721                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7722                 }
7723             }
7724            }
7725            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7726            second = FALSE;
7727         }
7728         // ignore clicks on holdings
7729         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7730     }
7731
7732     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7733         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7734         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7735         return;
7736     }
7737
7738     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7739         DragPieceEnd(xPix, yPix); dragging = 0;
7740         if(clearFlag) {
7741             // a deferred attempt to click-click move an empty square on top of a piece
7742             boards[currentMove][y][x] = EmptySquare;
7743             ClearHighlights();
7744             DrawPosition(FALSE, boards[currentMove]);
7745             fromX = fromY = -1; clearFlag = 0;
7746             return;
7747         }
7748         if (appData.animateDragging) {
7749             /* Undo animation damage if any */
7750             DrawPosition(FALSE, NULL);
7751         }
7752         if (second) {
7753             /* Second up/down in same square; just abort move */
7754             second = 0;
7755             fromX = fromY = -1;
7756             gatingPiece = EmptySquare;
7757             ClearHighlights();
7758             gotPremove = 0;
7759             ClearPremoveHighlights();
7760             MarkTargetSquares(-1);
7761             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7762         } else {
7763             /* First upclick in same square; start click-click mode */
7764             SetHighlights(x, y, -1, -1);
7765         }
7766         return;
7767     }
7768
7769     clearFlag = 0;
7770
7771     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7772        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7773         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7774         DisplayMessage(_("only marked squares are legal"),"");
7775         DrawPosition(TRUE, NULL);
7776         return; // ignore to-click
7777     }
7778
7779     /* we now have a different from- and (possibly off-board) to-square */
7780     /* Completed move */
7781     if(!sweepSelecting) {
7782         toX = x;
7783         toY = y;
7784     }
7785
7786     piece = boards[currentMove][fromY][fromX];
7787
7788     saveAnimate = appData.animate;
7789     if (clickType == Press) {
7790         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7791         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7792             // must be Edit Position mode with empty-square selected
7793             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7794             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7795             return;
7796         }
7797         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7798             return;
7799         }
7800         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7801             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7802         } else
7803         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7804         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7805           if(appData.sweepSelect) {
7806             promoSweep = defaultPromoChoice;
7807             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7808             selectFlag = 0; lastX = xPix; lastY = yPix;
7809             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7810             saveFlash = appData.flashCount; appData.flashCount = 0;
7811             Sweep(0); // Pawn that is going to promote: preview promotion piece
7812             sweepSelecting = 1;
7813             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7814             MarkTargetSquares(1);
7815           }
7816           return; // promo popup appears on up-click
7817         }
7818         /* Finish clickclick move */
7819         if (appData.animate || appData.highlightLastMove) {
7820             SetHighlights(fromX, fromY, toX, toY);
7821         } else {
7822             ClearHighlights();
7823         }
7824         MarkTargetSquares(1);
7825     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7826         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7827         *promoRestrict = 0; appData.flashCount = saveFlash;
7828         if (appData.animate || appData.highlightLastMove) {
7829             SetHighlights(fromX, fromY, toX, toY);
7830         } else {
7831             ClearHighlights();
7832         }
7833         MarkTargetSquares(1);
7834     } else {
7835 #if 0
7836 // [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
7837         /* Finish drag move */
7838         if (appData.highlightLastMove) {
7839             SetHighlights(fromX, fromY, toX, toY);
7840         } else {
7841             ClearHighlights();
7842         }
7843 #endif
7844         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7845           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7846         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7847         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7848           dragging *= 2;            // flag button-less dragging if we are dragging
7849           MarkTargetSquares(1);
7850           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7851           else {
7852             kill2X = killX; kill2Y = killY;
7853             killX = x; killY = y;     // remember this square as intermediate
7854             ReportClick("put", x, y); // and inform engine
7855             ReportClick("lift", x, y);
7856             MarkTargetSquares(0);
7857             return;
7858           }
7859         }
7860         DragPieceEnd(xPix, yPix); dragging = 0;
7861         /* Don't animate move and drag both */
7862         appData.animate = FALSE;
7863         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7864     }
7865
7866     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7867     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7868         ChessSquare piece = boards[currentMove][fromY][fromX];
7869         if(gameMode == EditPosition && piece != EmptySquare &&
7870            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7871             int n;
7872
7873             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7874                 n = PieceToNumber(piece - (int)BlackPawn);
7875                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7876                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7877                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7878             } else
7879             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7880                 n = PieceToNumber(piece);
7881                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7882                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7883                 boards[currentMove][n][BOARD_WIDTH-2]++;
7884             }
7885             boards[currentMove][fromY][fromX] = EmptySquare;
7886         }
7887         ClearHighlights();
7888         fromX = fromY = -1;
7889         MarkTargetSquares(1);
7890         DrawPosition(TRUE, boards[currentMove]);
7891         return;
7892     }
7893
7894     // off-board moves should not be highlighted
7895     if(x < 0 || y < 0) ClearHighlights();
7896     else ReportClick("put", x, y);
7897
7898     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7899
7900     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7901
7902     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7903         SetHighlights(fromX, fromY, toX, toY);
7904         MarkTargetSquares(1);
7905         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7906             // [HGM] super: promotion to captured piece selected from holdings
7907             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7908             promotionChoice = TRUE;
7909             // kludge follows to temporarily execute move on display, without promoting yet
7910             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7911             boards[currentMove][toY][toX] = p;
7912             DrawPosition(FALSE, boards[currentMove]);
7913             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7914             boards[currentMove][toY][toX] = q;
7915             DisplayMessage("Click in holdings to choose piece", "");
7916             return;
7917         }
7918         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7919         PromotionPopUp(promoChoice);
7920     } else {
7921         int oldMove = currentMove;
7922         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7923         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7924         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7925         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7926         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7927            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7928             DrawPosition(TRUE, boards[currentMove]);
7929         fromX = fromY = -1;
7930         flashing = 0;
7931     }
7932     appData.animate = saveAnimate;
7933     if (appData.animate || appData.animateDragging) {
7934         /* Undo animation damage if needed */
7935 //      DrawPosition(FALSE, NULL);
7936     }
7937 }
7938
7939 int
7940 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7941 {   // front-end-free part taken out of PieceMenuPopup
7942     int whichMenu; int xSqr, ySqr;
7943
7944     if(seekGraphUp) { // [HGM] seekgraph
7945         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7946         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7947         return -2;
7948     }
7949
7950     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7951          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7952         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7953         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7954         if(action == Press)   {
7955             originalFlip = flipView;
7956             flipView = !flipView; // temporarily flip board to see game from partners perspective
7957             DrawPosition(TRUE, partnerBoard);
7958             DisplayMessage(partnerStatus, "");
7959             partnerUp = TRUE;
7960         } else if(action == Release) {
7961             flipView = originalFlip;
7962             DrawPosition(TRUE, boards[currentMove]);
7963             partnerUp = FALSE;
7964         }
7965         return -2;
7966     }
7967
7968     xSqr = EventToSquare(x, BOARD_WIDTH);
7969     ySqr = EventToSquare(y, BOARD_HEIGHT);
7970     if (action == Release) {
7971         if(pieceSweep != EmptySquare) {
7972             EditPositionMenuEvent(pieceSweep, toX, toY);
7973             pieceSweep = EmptySquare;
7974         } else UnLoadPV(); // [HGM] pv
7975     }
7976     if (action != Press) return -2; // return code to be ignored
7977     switch (gameMode) {
7978       case IcsExamining:
7979         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7980       case EditPosition:
7981         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7982         if (xSqr < 0 || ySqr < 0) return -1;
7983         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7984         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7985         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7986         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7987         NextPiece(0);
7988         return 2; // grab
7989       case IcsObserving:
7990         if(!appData.icsEngineAnalyze) return -1;
7991       case IcsPlayingWhite:
7992       case IcsPlayingBlack:
7993         if(!appData.zippyPlay) goto noZip;
7994       case AnalyzeMode:
7995       case AnalyzeFile:
7996       case MachinePlaysWhite:
7997       case MachinePlaysBlack:
7998       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7999         if (!appData.dropMenu) {
8000           LoadPV(x, y);
8001           return 2; // flag front-end to grab mouse events
8002         }
8003         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8004            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8005       case EditGame:
8006       noZip:
8007         if (xSqr < 0 || ySqr < 0) return -1;
8008         if (!appData.dropMenu || appData.testLegality &&
8009             gameInfo.variant != VariantBughouse &&
8010             gameInfo.variant != VariantCrazyhouse) return -1;
8011         whichMenu = 1; // drop menu
8012         break;
8013       default:
8014         return -1;
8015     }
8016
8017     if (((*fromX = xSqr) < 0) ||
8018         ((*fromY = ySqr) < 0)) {
8019         *fromX = *fromY = -1;
8020         return -1;
8021     }
8022     if (flipView)
8023       *fromX = BOARD_WIDTH - 1 - *fromX;
8024     else
8025       *fromY = BOARD_HEIGHT - 1 - *fromY;
8026
8027     return whichMenu;
8028 }
8029
8030 void
8031 Wheel (int dir, int x, int y)
8032 {
8033     if(gameMode == EditPosition) {
8034         int xSqr = EventToSquare(x, BOARD_WIDTH);
8035         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8036         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8037         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8038         do {
8039             boards[currentMove][ySqr][xSqr] += dir;
8040             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8041             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8042         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8043         DrawPosition(FALSE, boards[currentMove]);
8044     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8045 }
8046
8047 void
8048 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8049 {
8050 //    char * hint = lastHint;
8051     FrontEndProgramStats stats;
8052
8053     stats.which = cps == &first ? 0 : 1;
8054     stats.depth = cpstats->depth;
8055     stats.nodes = cpstats->nodes;
8056     stats.score = cpstats->score;
8057     stats.time = cpstats->time;
8058     stats.pv = cpstats->movelist;
8059     stats.hint = lastHint;
8060     stats.an_move_index = 0;
8061     stats.an_move_count = 0;
8062
8063     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8064         stats.hint = cpstats->move_name;
8065         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8066         stats.an_move_count = cpstats->nr_moves;
8067     }
8068
8069     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
8070
8071     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8072         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8073
8074     SetProgramStats( &stats );
8075 }
8076
8077 void
8078 ClearEngineOutputPane (int which)
8079 {
8080     static FrontEndProgramStats dummyStats;
8081     dummyStats.which = which;
8082     dummyStats.pv = "#";
8083     SetProgramStats( &dummyStats );
8084 }
8085
8086 #define MAXPLAYERS 500
8087
8088 char *
8089 TourneyStandings (int display)
8090 {
8091     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8092     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8093     char result, *p, *names[MAXPLAYERS];
8094
8095     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8096         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8097     names[0] = p = strdup(appData.participants);
8098     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8099
8100     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8101
8102     while(result = appData.results[nr]) {
8103         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8104         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8105         wScore = bScore = 0;
8106         switch(result) {
8107           case '+': wScore = 2; break;
8108           case '-': bScore = 2; break;
8109           case '=': wScore = bScore = 1; break;
8110           case ' ':
8111           case '*': return strdup("busy"); // tourney not finished
8112         }
8113         score[w] += wScore;
8114         score[b] += bScore;
8115         games[w]++;
8116         games[b]++;
8117         nr++;
8118     }
8119     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8120     for(w=0; w<nPlayers; w++) {
8121         bScore = -1;
8122         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8123         ranking[w] = b; points[w] = bScore; score[b] = -2;
8124     }
8125     p = malloc(nPlayers*34+1);
8126     for(w=0; w<nPlayers && w<display; w++)
8127         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8128     free(names[0]);
8129     return p;
8130 }
8131
8132 void
8133 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8134 {       // count all piece types
8135         int p, f, r;
8136         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8137         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8138         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8139                 p = board[r][f];
8140                 pCnt[p]++;
8141                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8142                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8143                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8144                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8145                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8146                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8147         }
8148 }
8149
8150 int
8151 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8152 {
8153         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8154         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8155
8156         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8157         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8158         if(myPawns == 2 && nMine == 3) // KPP
8159             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8160         if(myPawns == 1 && nMine == 2) // KP
8161             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8162         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8163             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8164         if(myPawns) return FALSE;
8165         if(pCnt[WhiteRook+side])
8166             return pCnt[BlackRook-side] ||
8167                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8168                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8169                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8170         if(pCnt[WhiteCannon+side]) {
8171             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8172             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8173         }
8174         if(pCnt[WhiteKnight+side])
8175             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8176         return FALSE;
8177 }
8178
8179 int
8180 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8181 {
8182         VariantClass v = gameInfo.variant;
8183
8184         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8185         if(v == VariantShatranj) return TRUE; // always winnable through baring
8186         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8187         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8188
8189         if(v == VariantXiangqi) {
8190                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8191
8192                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8193                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8194                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8195                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8196                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8197                 if(stale) // we have at least one last-rank P plus perhaps C
8198                     return majors // KPKX
8199                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8200                 else // KCA*E*
8201                     return pCnt[WhiteFerz+side] // KCAK
8202                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8203                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8204                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8205
8206         } else if(v == VariantKnightmate) {
8207                 if(nMine == 1) return FALSE;
8208                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8209         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8210                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8211
8212                 if(nMine == 1) return FALSE; // bare King
8213                 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
8214                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8215                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8216                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8217                 if(pCnt[WhiteKnight+side])
8218                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8219                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8220                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8221                 if(nBishops)
8222                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8223                 if(pCnt[WhiteAlfil+side])
8224                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8225                 if(pCnt[WhiteWazir+side])
8226                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8227         }
8228
8229         return TRUE;
8230 }
8231
8232 int
8233 CompareWithRights (Board b1, Board b2)
8234 {
8235     int rights = 0;
8236     if(!CompareBoards(b1, b2)) return FALSE;
8237     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8238     /* compare castling rights */
8239     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8240            rights++; /* King lost rights, while rook still had them */
8241     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8242         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8243            rights++; /* but at least one rook lost them */
8244     }
8245     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8246            rights++;
8247     if( b1[CASTLING][5] != NoRights ) {
8248         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8249            rights++;
8250     }
8251     return rights == 0;
8252 }
8253
8254 int
8255 Adjudicate (ChessProgramState *cps)
8256 {       // [HGM] some adjudications useful with buggy engines
8257         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8258         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8259         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8260         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8261         int k, drop, count = 0; static int bare = 1;
8262         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8263         Boolean canAdjudicate = !appData.icsActive;
8264
8265         // most tests only when we understand the game, i.e. legality-checking on
8266             if( appData.testLegality )
8267             {   /* [HGM] Some more adjudications for obstinate engines */
8268                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8269                 static int moveCount = 6;
8270                 ChessMove result;
8271                 char *reason = NULL;
8272
8273                 /* Count what is on board. */
8274                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8275
8276                 /* Some material-based adjudications that have to be made before stalemate test */
8277                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8278                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8279                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8280                      if(canAdjudicate && appData.checkMates) {
8281                          if(engineOpponent)
8282                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8283                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8284                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8285                          return 1;
8286                      }
8287                 }
8288
8289                 /* Bare King in Shatranj (loses) or Losers (wins) */
8290                 if( nrW == 1 || nrB == 1) {
8291                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8292                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8293                      if(canAdjudicate && appData.checkMates) {
8294                          if(engineOpponent)
8295                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8296                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8297                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8298                          return 1;
8299                      }
8300                   } else
8301                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8302                   {    /* bare King */
8303                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8304                         if(canAdjudicate && appData.checkMates) {
8305                             /* but only adjudicate if adjudication enabled */
8306                             if(engineOpponent)
8307                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8308                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8309                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8310                             return 1;
8311                         }
8312                   }
8313                 } else bare = 1;
8314
8315
8316             // don't wait for engine to announce game end if we can judge ourselves
8317             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8318               case MT_CHECK:
8319                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8320                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8321                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8322                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8323                             checkCnt++;
8324                         if(checkCnt >= 2) {
8325                             reason = "Xboard adjudication: 3rd check";
8326                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8327                             break;
8328                         }
8329                     }
8330                 }
8331               case MT_NONE:
8332               default:
8333                 break;
8334               case MT_STEALMATE:
8335               case MT_STALEMATE:
8336               case MT_STAINMATE:
8337                 reason = "Xboard adjudication: Stalemate";
8338                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8339                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8340                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8341                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8342                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8343                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8344                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8345                                                                         EP_CHECKMATE : EP_WINS);
8346                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8347                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8348                 }
8349                 break;
8350               case MT_CHECKMATE:
8351                 reason = "Xboard adjudication: Checkmate";
8352                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8353                 if(gameInfo.variant == VariantShogi) {
8354                     if(forwardMostMove > backwardMostMove
8355                        && moveList[forwardMostMove-1][1] == '@'
8356                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8357                         reason = "XBoard adjudication: pawn-drop mate";
8358                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8359                     }
8360                 }
8361                 break;
8362             }
8363
8364                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8365                     case EP_STALEMATE:
8366                         result = GameIsDrawn; break;
8367                     case EP_CHECKMATE:
8368                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8369                     case EP_WINS:
8370                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8371                     default:
8372                         result = EndOfFile;
8373                 }
8374                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8375                     if(engineOpponent)
8376                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8377                     GameEnds( result, reason, GE_XBOARD );
8378                     return 1;
8379                 }
8380
8381                 /* Next absolutely insufficient mating material. */
8382                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8383                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8384                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8385
8386                      /* always flag draws, for judging claims */
8387                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8388
8389                      if(canAdjudicate && appData.materialDraws) {
8390                          /* but only adjudicate them if adjudication enabled */
8391                          if(engineOpponent) {
8392                            SendToProgram("force\n", engineOpponent); // suppress reply
8393                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8394                          }
8395                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8396                          return 1;
8397                      }
8398                 }
8399
8400                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8401                 if(gameInfo.variant == VariantXiangqi ?
8402                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8403                  : nrW + nrB == 4 &&
8404                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8405                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8406                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8407                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8408                    ) ) {
8409                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8410                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8411                           if(engineOpponent) {
8412                             SendToProgram("force\n", engineOpponent); // suppress reply
8413                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8414                           }
8415                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8416                           return 1;
8417                      }
8418                 } else moveCount = 6;
8419             }
8420
8421         // Repetition draws and 50-move rule can be applied independently of legality testing
8422
8423                 /* Check for rep-draws */
8424                 count = 0;
8425                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8426                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8427                 for(k = forwardMostMove-2;
8428                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8429                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8430                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8431                     k-=2)
8432                 {   int rights=0;
8433                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8434                         /* compare castling rights */
8435                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8436                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8437                                 rights++; /* King lost rights, while rook still had them */
8438                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8439                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8440                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8441                                    rights++; /* but at least one rook lost them */
8442                         }
8443                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8444                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8445                                 rights++;
8446                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8447                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8448                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8449                                    rights++;
8450                         }
8451                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8452                             && appData.drawRepeats > 1) {
8453                              /* adjudicate after user-specified nr of repeats */
8454                              int result = GameIsDrawn;
8455                              char *details = "XBoard adjudication: repetition draw";
8456                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8457                                 // [HGM] xiangqi: check for forbidden perpetuals
8458                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8459                                 for(m=forwardMostMove; m>k; m-=2) {
8460                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8461                                         ourPerpetual = 0; // the current mover did not always check
8462                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8463                                         hisPerpetual = 0; // the opponent did not always check
8464                                 }
8465                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8466                                                                         ourPerpetual, hisPerpetual);
8467                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8468                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8469                                     details = "Xboard adjudication: perpetual checking";
8470                                 } else
8471                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8472                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8473                                 } else
8474                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8475                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8476                                         result = BlackWins;
8477                                         details = "Xboard adjudication: repetition";
8478                                     }
8479                                 } else // it must be XQ
8480                                 // Now check for perpetual chases
8481                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8482                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8483                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8484                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8485                                         static char resdet[MSG_SIZ];
8486                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8487                                         details = resdet;
8488                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8489                                     } else
8490                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8491                                         break; // Abort repetition-checking loop.
8492                                 }
8493                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8494                              }
8495                              if(engineOpponent) {
8496                                SendToProgram("force\n", engineOpponent); // suppress reply
8497                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8498                              }
8499                              GameEnds( result, details, GE_XBOARD );
8500                              return 1;
8501                         }
8502                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8503                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8504                     }
8505                 }
8506
8507                 /* Now we test for 50-move draws. Determine ply count */
8508                 count = forwardMostMove;
8509                 /* look for last irreversble move */
8510                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8511                     count--;
8512                 /* if we hit starting position, add initial plies */
8513                 if( count == backwardMostMove )
8514                     count -= initialRulePlies;
8515                 count = forwardMostMove - count;
8516                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8517                         // adjust reversible move counter for checks in Xiangqi
8518                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8519                         if(i < backwardMostMove) i = backwardMostMove;
8520                         while(i <= forwardMostMove) {
8521                                 lastCheck = inCheck; // check evasion does not count
8522                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8523                                 if(inCheck || lastCheck) count--; // check does not count
8524                                 i++;
8525                         }
8526                 }
8527                 if( count >= 100)
8528                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8529                          /* this is used to judge if draw claims are legal */
8530                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8531                          if(engineOpponent) {
8532                            SendToProgram("force\n", engineOpponent); // suppress reply
8533                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8534                          }
8535                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8536                          return 1;
8537                 }
8538
8539                 /* if draw offer is pending, treat it as a draw claim
8540                  * when draw condition present, to allow engines a way to
8541                  * claim draws before making their move to avoid a race
8542                  * condition occurring after their move
8543                  */
8544                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8545                          char *p = NULL;
8546                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8547                              p = "Draw claim: 50-move rule";
8548                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8549                              p = "Draw claim: 3-fold repetition";
8550                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8551                              p = "Draw claim: insufficient mating material";
8552                          if( p != NULL && canAdjudicate) {
8553                              if(engineOpponent) {
8554                                SendToProgram("force\n", engineOpponent); // suppress reply
8555                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8556                              }
8557                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8558                              return 1;
8559                          }
8560                 }
8561
8562                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8563                     if(engineOpponent) {
8564                       SendToProgram("force\n", engineOpponent); // suppress reply
8565                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8566                     }
8567                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8568                     return 1;
8569                 }
8570         return 0;
8571 }
8572
8573 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8574 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8575 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8576
8577 static int
8578 BitbaseProbe ()
8579 {
8580     int pieces[10], squares[10], cnt=0, r, f, res;
8581     static int loaded;
8582     static PPROBE_EGBB probeBB;
8583     if(!appData.testLegality) return 10;
8584     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8585     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8586     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8587     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8588         ChessSquare piece = boards[forwardMostMove][r][f];
8589         int black = (piece >= BlackPawn);
8590         int type = piece - black*BlackPawn;
8591         if(piece == EmptySquare) continue;
8592         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8593         if(type == WhiteKing) type = WhiteQueen + 1;
8594         type = egbbCode[type];
8595         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8596         pieces[cnt] = type + black*6;
8597         if(++cnt > 5) return 11;
8598     }
8599     pieces[cnt] = squares[cnt] = 0;
8600     // probe EGBB
8601     if(loaded == 2) return 13; // loading failed before
8602     if(loaded == 0) {
8603         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8604         HMODULE lib;
8605         PLOAD_EGBB loadBB;
8606         loaded = 2; // prepare for failure
8607         if(!path) return 13; // no egbb installed
8608         strncpy(buf, path + 8, MSG_SIZ);
8609         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8610         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8611         lib = LoadLibrary(buf);
8612         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8613         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8614         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8615         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8616         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8617         loaded = 1; // success!
8618     }
8619     res = probeBB(forwardMostMove & 1, pieces, squares);
8620     return res > 0 ? 1 : res < 0 ? -1 : 0;
8621 }
8622
8623 char *
8624 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8625 {   // [HGM] book: this routine intercepts moves to simulate book replies
8626     char *bookHit = NULL;
8627
8628     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8629         char buf[MSG_SIZ];
8630         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8631         SendToProgram(buf, cps);
8632     }
8633     //first determine if the incoming move brings opponent into his book
8634     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8635         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8636     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8637     if(bookHit != NULL && !cps->bookSuspend) {
8638         // make sure opponent is not going to reply after receiving move to book position
8639         SendToProgram("force\n", cps);
8640         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8641     }
8642     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8643     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8644     // now arrange restart after book miss
8645     if(bookHit) {
8646         // after a book hit we never send 'go', and the code after the call to this routine
8647         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8648         char buf[MSG_SIZ], *move = bookHit;
8649         if(cps->useSAN) {
8650             int fromX, fromY, toX, toY;
8651             char promoChar;
8652             ChessMove moveType;
8653             move = buf + 30;
8654             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8655                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8656                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8657                                     PosFlags(forwardMostMove),
8658                                     fromY, fromX, toY, toX, promoChar, move);
8659             } else {
8660                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8661                 bookHit = NULL;
8662             }
8663         }
8664         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8665         SendToProgram(buf, cps);
8666         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8667     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8668         SendToProgram("go\n", cps);
8669         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8670     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8671         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8672             SendToProgram("go\n", cps);
8673         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8674     }
8675     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8676 }
8677
8678 int
8679 LoadError (char *errmess, ChessProgramState *cps)
8680 {   // unloads engine and switches back to -ncp mode if it was first
8681     if(cps->initDone) return FALSE;
8682     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8683     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8684     cps->pr = NoProc;
8685     if(cps == &first) {
8686         appData.noChessProgram = TRUE;
8687         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8688         gameMode = BeginningOfGame; ModeHighlight();
8689         SetNCPMode();
8690     }
8691     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8692     DisplayMessage("", ""); // erase waiting message
8693     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8694     return TRUE;
8695 }
8696
8697 char *savedMessage;
8698 ChessProgramState *savedState;
8699 void
8700 DeferredBookMove (void)
8701 {
8702         if(savedState->lastPing != savedState->lastPong)
8703                     ScheduleDelayedEvent(DeferredBookMove, 10);
8704         else
8705         HandleMachineMove(savedMessage, savedState);
8706 }
8707
8708 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8709 static ChessProgramState *stalledEngine;
8710 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8711
8712 void
8713 HandleMachineMove (char *message, ChessProgramState *cps)
8714 {
8715     static char firstLeg[20], legs;
8716     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8717     char realname[MSG_SIZ];
8718     int fromX, fromY, toX, toY;
8719     ChessMove moveType;
8720     char promoChar, roar;
8721     char *p, *pv=buf1;
8722     int oldError;
8723     char *bookHit;
8724
8725     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8726         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8727         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8728             DisplayError(_("Invalid pairing from pairing engine"), 0);
8729             return;
8730         }
8731         pairingReceived = 1;
8732         NextMatchGame();
8733         return; // Skim the pairing messages here.
8734     }
8735
8736     oldError = cps->userError; cps->userError = 0;
8737
8738 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8739     /*
8740      * Kludge to ignore BEL characters
8741      */
8742     while (*message == '\007') message++;
8743
8744     /*
8745      * [HGM] engine debug message: ignore lines starting with '#' character
8746      */
8747     if(cps->debug && *message == '#') return;
8748
8749     /*
8750      * Look for book output
8751      */
8752     if (cps == &first && bookRequested) {
8753         if (message[0] == '\t' || message[0] == ' ') {
8754             /* Part of the book output is here; append it */
8755             strcat(bookOutput, message);
8756             strcat(bookOutput, "  \n");
8757             return;
8758         } else if (bookOutput[0] != NULLCHAR) {
8759             /* All of book output has arrived; display it */
8760             char *p = bookOutput;
8761             while (*p != NULLCHAR) {
8762                 if (*p == '\t') *p = ' ';
8763                 p++;
8764             }
8765             DisplayInformation(bookOutput);
8766             bookRequested = FALSE;
8767             /* Fall through to parse the current output */
8768         }
8769     }
8770
8771     /*
8772      * Look for machine move.
8773      */
8774     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8775         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8776     {
8777         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8778             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8779             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8780             stalledEngine = cps;
8781             if(appData.ponderNextMove) { // bring opponent out of ponder
8782                 if(gameMode == TwoMachinesPlay) {
8783                     if(cps->other->pause)
8784                         PauseEngine(cps->other);
8785                     else
8786                         SendToProgram("easy\n", cps->other);
8787                 }
8788             }
8789             StopClocks();
8790             return;
8791         }
8792
8793       if(cps->usePing) {
8794
8795         /* This method is only useful on engines that support ping */
8796         if(abortEngineThink) {
8797             if (appData.debugMode) {
8798                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8799             }
8800             SendToProgram("undo\n", cps);
8801             return;
8802         }
8803
8804         if (cps->lastPing != cps->lastPong) {
8805             /* Extra move from before last new; ignore */
8806             if (appData.debugMode) {
8807                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8808             }
8809           return;
8810         }
8811
8812       } else {
8813
8814         int machineWhite = FALSE;
8815
8816         switch (gameMode) {
8817           case BeginningOfGame:
8818             /* Extra move from before last reset; ignore */
8819             if (appData.debugMode) {
8820                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8821             }
8822             return;
8823
8824           case EndOfGame:
8825           case IcsIdle:
8826           default:
8827             /* Extra move after we tried to stop.  The mode test is
8828                not a reliable way of detecting this problem, but it's
8829                the best we can do on engines that don't support ping.
8830             */
8831             if (appData.debugMode) {
8832                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8833                         cps->which, gameMode);
8834             }
8835             SendToProgram("undo\n", cps);
8836             return;
8837
8838           case MachinePlaysWhite:
8839           case IcsPlayingWhite:
8840             machineWhite = TRUE;
8841             break;
8842
8843           case MachinePlaysBlack:
8844           case IcsPlayingBlack:
8845             machineWhite = FALSE;
8846             break;
8847
8848           case TwoMachinesPlay:
8849             machineWhite = (cps->twoMachinesColor[0] == 'w');
8850             break;
8851         }
8852         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8853             if (appData.debugMode) {
8854                 fprintf(debugFP,
8855                         "Ignoring move out of turn by %s, gameMode %d"
8856                         ", forwardMost %d\n",
8857                         cps->which, gameMode, forwardMostMove);
8858             }
8859             return;
8860         }
8861       }
8862
8863         if(cps->alphaRank) AlphaRank(machineMove, 4);
8864
8865         // [HGM] lion: (some very limited) support for Alien protocol
8866         killX = killY = kill2X = kill2Y = -1;
8867         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8868             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8869             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8870             return;
8871         }
8872         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8873             char *q = strchr(p+1, ',');            // second comma?
8874             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8875             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8876             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8877         }
8878         if(firstLeg[0]) { // there was a previous leg;
8879             // only support case where same piece makes two step
8880             char buf[20], *p = machineMove+1, *q = buf+1, f;
8881             safeStrCpy(buf, machineMove, 20);
8882             while(isdigit(*q)) q++; // find start of to-square
8883             safeStrCpy(machineMove, firstLeg, 20);
8884             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8885             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
8886             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)
8887             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8888             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8889             firstLeg[0] = NULLCHAR; legs = 0;
8890         }
8891
8892         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8893                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8894             /* Machine move could not be parsed; ignore it. */
8895           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8896                     machineMove, _(cps->which));
8897             DisplayMoveError(buf1);
8898             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8899                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8900             if (gameMode == TwoMachinesPlay) {
8901               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8902                        buf1, GE_XBOARD);
8903             }
8904             return;
8905         }
8906
8907         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8908         /* So we have to redo legality test with true e.p. status here,  */
8909         /* to make sure an illegal e.p. capture does not slip through,   */
8910         /* to cause a forfeit on a justified illegal-move complaint      */
8911         /* of the opponent.                                              */
8912         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8913            ChessMove moveType;
8914            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8915                              fromY, fromX, toY, toX, promoChar);
8916             if(moveType == IllegalMove) {
8917               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8918                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8919                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8920                            buf1, GE_XBOARD);
8921                 return;
8922            } else if(!appData.fischerCastling)
8923            /* [HGM] Kludge to handle engines that send FRC-style castling
8924               when they shouldn't (like TSCP-Gothic) */
8925            switch(moveType) {
8926              case WhiteASideCastleFR:
8927              case BlackASideCastleFR:
8928                toX+=2;
8929                currentMoveString[2]++;
8930                break;
8931              case WhiteHSideCastleFR:
8932              case BlackHSideCastleFR:
8933                toX--;
8934                currentMoveString[2]--;
8935                break;
8936              default: ; // nothing to do, but suppresses warning of pedantic compilers
8937            }
8938         }
8939         hintRequested = FALSE;
8940         lastHint[0] = NULLCHAR;
8941         bookRequested = FALSE;
8942         /* Program may be pondering now */
8943         cps->maybeThinking = TRUE;
8944         if (cps->sendTime == 2) cps->sendTime = 1;
8945         if (cps->offeredDraw) cps->offeredDraw--;
8946
8947         /* [AS] Save move info*/
8948         pvInfoList[ forwardMostMove ].score = programStats.score;
8949         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8950         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8951
8952         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8953
8954         /* Test suites abort the 'game' after one move */
8955         if(*appData.finger) {
8956            static FILE *f;
8957            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8958            if(!f) f = fopen(appData.finger, "w");
8959            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8960            else { DisplayFatalError("Bad output file", errno, 0); return; }
8961            free(fen);
8962            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8963         }
8964         if(appData.epd) {
8965            if(solvingTime >= 0) {
8966               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8967               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8968            } else {
8969               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8970               if(solvingTime == -2) second.matchWins++;
8971            }
8972            OutputKibitz(2, buf1);
8973            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8974         }
8975
8976         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8977         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8978             int count = 0;
8979
8980             while( count < adjudicateLossPlies ) {
8981                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8982
8983                 if( count & 1 ) {
8984                     score = -score; /* Flip score for winning side */
8985                 }
8986
8987                 if( score > appData.adjudicateLossThreshold ) {
8988                     break;
8989                 }
8990
8991                 count++;
8992             }
8993
8994             if( count >= adjudicateLossPlies ) {
8995                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8996
8997                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8998                     "Xboard adjudication",
8999                     GE_XBOARD );
9000
9001                 return;
9002             }
9003         }
9004
9005         if(Adjudicate(cps)) {
9006             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9007             return; // [HGM] adjudicate: for all automatic game ends
9008         }
9009
9010 #if ZIPPY
9011         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9012             first.initDone) {
9013           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9014                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9015                 SendToICS("draw ");
9016                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9017           }
9018           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9019           ics_user_moved = 1;
9020           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9021                 char buf[3*MSG_SIZ];
9022
9023                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9024                         programStats.score / 100.,
9025                         programStats.depth,
9026                         programStats.time / 100.,
9027                         (unsigned int)programStats.nodes,
9028                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9029                         programStats.movelist);
9030                 SendToICS(buf);
9031           }
9032         }
9033 #endif
9034
9035         /* [AS] Clear stats for next move */
9036         ClearProgramStats();
9037         thinkOutput[0] = NULLCHAR;
9038         hiddenThinkOutputState = 0;
9039
9040         bookHit = NULL;
9041         if (gameMode == TwoMachinesPlay) {
9042             /* [HGM] relaying draw offers moved to after reception of move */
9043             /* and interpreting offer as claim if it brings draw condition */
9044             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9045                 SendToProgram("draw\n", cps->other);
9046             }
9047             if (cps->other->sendTime) {
9048                 SendTimeRemaining(cps->other,
9049                                   cps->other->twoMachinesColor[0] == 'w');
9050             }
9051             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9052             if (firstMove && !bookHit) {
9053                 firstMove = FALSE;
9054                 if (cps->other->useColors) {
9055                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9056                 }
9057                 SendToProgram("go\n", cps->other);
9058             }
9059             cps->other->maybeThinking = TRUE;
9060         }
9061
9062         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9063
9064         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9065
9066         if (!pausing && appData.ringBellAfterMoves) {
9067             if(!roar) RingBell();
9068         }
9069
9070         /*
9071          * Reenable menu items that were disabled while
9072          * machine was thinking
9073          */
9074         if (gameMode != TwoMachinesPlay)
9075             SetUserThinkingEnables();
9076
9077         // [HGM] book: after book hit opponent has received move and is now in force mode
9078         // force the book reply into it, and then fake that it outputted this move by jumping
9079         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9080         if(bookHit) {
9081                 static char bookMove[MSG_SIZ]; // a bit generous?
9082
9083                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9084                 strcat(bookMove, bookHit);
9085                 message = bookMove;
9086                 cps = cps->other;
9087                 programStats.nodes = programStats.depth = programStats.time =
9088                 programStats.score = programStats.got_only_move = 0;
9089                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9090
9091                 if(cps->lastPing != cps->lastPong) {
9092                     savedMessage = message; // args for deferred call
9093                     savedState = cps;
9094                     ScheduleDelayedEvent(DeferredBookMove, 10);
9095                     return;
9096                 }
9097                 goto FakeBookMove;
9098         }
9099
9100         return;
9101     }
9102
9103     /* Set special modes for chess engines.  Later something general
9104      *  could be added here; for now there is just one kludge feature,
9105      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9106      *  when "xboard" is given as an interactive command.
9107      */
9108     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9109         cps->useSigint = FALSE;
9110         cps->useSigterm = FALSE;
9111     }
9112     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9113       ParseFeatures(message+8, cps);
9114       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9115     }
9116
9117     if (!strncmp(message, "setup ", 6) && 
9118         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9119           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9120                                         ) { // [HGM] allow first engine to define opening position
9121       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9122       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9123       *buf = NULLCHAR;
9124       if(sscanf(message, "setup (%s", buf) == 1) {
9125         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9126         ASSIGN(appData.pieceToCharTable, buf);
9127       }
9128       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9129       if(dummy >= 3) {
9130         while(message[s] && message[s++] != ' ');
9131         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9132            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9133             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9134             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9135           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9136           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9137           startedFromSetupPosition = FALSE;
9138         }
9139       }
9140       if(startedFromSetupPosition) return;
9141       ParseFEN(boards[0], &dummy, message+s, FALSE);
9142       DrawPosition(TRUE, boards[0]);
9143       CopyBoard(initialPosition, boards[0]);
9144       startedFromSetupPosition = TRUE;
9145       return;
9146     }
9147     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9148       ChessSquare piece = WhitePawn;
9149       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9150       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9151       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9152       piece += CharToPiece(ID & 255) - WhitePawn;
9153       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9154       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9155       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9156       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9157       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9158       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9159                                                && gameInfo.variant != VariantGreat
9160                                                && gameInfo.variant != VariantFairy    ) return;
9161       if(piece < EmptySquare) {
9162         pieceDefs = TRUE;
9163         ASSIGN(pieceDesc[piece], buf1);
9164         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9165       }
9166       return;
9167     }
9168     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9169       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9170       Sweep(0);
9171       return;
9172     }
9173     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9174      * want this, I was asked to put it in, and obliged.
9175      */
9176     if (!strncmp(message, "setboard ", 9)) {
9177         Board initial_position;
9178
9179         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9180
9181         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9182             DisplayError(_("Bad FEN received from engine"), 0);
9183             return ;
9184         } else {
9185            Reset(TRUE, FALSE);
9186            CopyBoard(boards[0], initial_position);
9187            initialRulePlies = FENrulePlies;
9188            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9189            else gameMode = MachinePlaysBlack;
9190            DrawPosition(FALSE, boards[currentMove]);
9191         }
9192         return;
9193     }
9194
9195     /*
9196      * Look for communication commands
9197      */
9198     if (!strncmp(message, "telluser ", 9)) {
9199         if(message[9] == '\\' && message[10] == '\\')
9200             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9201         PlayTellSound();
9202         DisplayNote(message + 9);
9203         return;
9204     }
9205     if (!strncmp(message, "tellusererror ", 14)) {
9206         cps->userError = 1;
9207         if(message[14] == '\\' && message[15] == '\\')
9208             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9209         PlayTellSound();
9210         DisplayError(message + 14, 0);
9211         return;
9212     }
9213     if (!strncmp(message, "tellopponent ", 13)) {
9214       if (appData.icsActive) {
9215         if (loggedOn) {
9216           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9217           SendToICS(buf1);
9218         }
9219       } else {
9220         DisplayNote(message + 13);
9221       }
9222       return;
9223     }
9224     if (!strncmp(message, "tellothers ", 11)) {
9225       if (appData.icsActive) {
9226         if (loggedOn) {
9227           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9228           SendToICS(buf1);
9229         }
9230       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9231       return;
9232     }
9233     if (!strncmp(message, "tellall ", 8)) {
9234       if (appData.icsActive) {
9235         if (loggedOn) {
9236           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9237           SendToICS(buf1);
9238         }
9239       } else {
9240         DisplayNote(message + 8);
9241       }
9242       return;
9243     }
9244     if (strncmp(message, "warning", 7) == 0) {
9245         /* Undocumented feature, use tellusererror in new code */
9246         DisplayError(message, 0);
9247         return;
9248     }
9249     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9250         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9251         strcat(realname, " query");
9252         AskQuestion(realname, buf2, buf1, cps->pr);
9253         return;
9254     }
9255     /* Commands from the engine directly to ICS.  We don't allow these to be
9256      *  sent until we are logged on. Crafty kibitzes have been known to
9257      *  interfere with the login process.
9258      */
9259     if (loggedOn) {
9260         if (!strncmp(message, "tellics ", 8)) {
9261             SendToICS(message + 8);
9262             SendToICS("\n");
9263             return;
9264         }
9265         if (!strncmp(message, "tellicsnoalias ", 15)) {
9266             SendToICS(ics_prefix);
9267             SendToICS(message + 15);
9268             SendToICS("\n");
9269             return;
9270         }
9271         /* The following are for backward compatibility only */
9272         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9273             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9274             SendToICS(ics_prefix);
9275             SendToICS(message);
9276             SendToICS("\n");
9277             return;
9278         }
9279     }
9280     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9281         if(initPing == cps->lastPong) {
9282             if(gameInfo.variant == VariantUnknown) {
9283                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9284                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9285                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9286             }
9287             initPing = -1;
9288         }
9289         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9290             abortEngineThink = FALSE;
9291             DisplayMessage("", "");
9292             ThawUI();
9293         }
9294         return;
9295     }
9296     if(!strncmp(message, "highlight ", 10)) {
9297         if(appData.testLegality && !*engineVariant && appData.markers) return;
9298         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9299         return;
9300     }
9301     if(!strncmp(message, "click ", 6)) {
9302         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9303         if(appData.testLegality || !appData.oneClick) return;
9304         sscanf(message+6, "%c%d%c", &f, &y, &c);
9305         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9306         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9307         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9308         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9309         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9310         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9311             LeftClick(Release, lastLeftX, lastLeftY);
9312         controlKey  = (c == ',');
9313         LeftClick(Press, x, y);
9314         LeftClick(Release, x, y);
9315         first.highlight = f;
9316         return;
9317     }
9318     /*
9319      * If the move is illegal, cancel it and redraw the board.
9320      * Also deal with other error cases.  Matching is rather loose
9321      * here to accommodate engines written before the spec.
9322      */
9323     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9324         strncmp(message, "Error", 5) == 0) {
9325         if (StrStr(message, "name") ||
9326             StrStr(message, "rating") || StrStr(message, "?") ||
9327             StrStr(message, "result") || StrStr(message, "board") ||
9328             StrStr(message, "bk") || StrStr(message, "computer") ||
9329             StrStr(message, "variant") || StrStr(message, "hint") ||
9330             StrStr(message, "random") || StrStr(message, "depth") ||
9331             StrStr(message, "accepted")) {
9332             return;
9333         }
9334         if (StrStr(message, "protover")) {
9335           /* Program is responding to input, so it's apparently done
9336              initializing, and this error message indicates it is
9337              protocol version 1.  So we don't need to wait any longer
9338              for it to initialize and send feature commands. */
9339           FeatureDone(cps, 1);
9340           cps->protocolVersion = 1;
9341           return;
9342         }
9343         cps->maybeThinking = FALSE;
9344
9345         if (StrStr(message, "draw")) {
9346             /* Program doesn't have "draw" command */
9347             cps->sendDrawOffers = 0;
9348             return;
9349         }
9350         if (cps->sendTime != 1 &&
9351             (StrStr(message, "time") || StrStr(message, "otim"))) {
9352           /* Program apparently doesn't have "time" or "otim" command */
9353           cps->sendTime = 0;
9354           return;
9355         }
9356         if (StrStr(message, "analyze")) {
9357             cps->analysisSupport = FALSE;
9358             cps->analyzing = FALSE;
9359 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9360             EditGameEvent(); // [HGM] try to preserve loaded game
9361             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9362             DisplayError(buf2, 0);
9363             return;
9364         }
9365         if (StrStr(message, "(no matching move)st")) {
9366           /* Special kludge for GNU Chess 4 only */
9367           cps->stKludge = TRUE;
9368           SendTimeControl(cps, movesPerSession, timeControl,
9369                           timeIncrement, appData.searchDepth,
9370                           searchTime);
9371           return;
9372         }
9373         if (StrStr(message, "(no matching move)sd")) {
9374           /* Special kludge for GNU Chess 4 only */
9375           cps->sdKludge = TRUE;
9376           SendTimeControl(cps, movesPerSession, timeControl,
9377                           timeIncrement, appData.searchDepth,
9378                           searchTime);
9379           return;
9380         }
9381         if (!StrStr(message, "llegal")) {
9382             return;
9383         }
9384         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9385             gameMode == IcsIdle) return;
9386         if (forwardMostMove <= backwardMostMove) return;
9387         if (pausing) PauseEvent();
9388       if(appData.forceIllegal) {
9389             // [HGM] illegal: machine refused move; force position after move into it
9390           SendToProgram("force\n", cps);
9391           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9392                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9393                 // when black is to move, while there might be nothing on a2 or black
9394                 // might already have the move. So send the board as if white has the move.
9395                 // But first we must change the stm of the engine, as it refused the last move
9396                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9397                 if(WhiteOnMove(forwardMostMove)) {
9398                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9399                     SendBoard(cps, forwardMostMove); // kludgeless board
9400                 } else {
9401                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9402                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9403                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9404                 }
9405           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9406             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9407                  gameMode == TwoMachinesPlay)
9408               SendToProgram("go\n", cps);
9409             return;
9410       } else
9411         if (gameMode == PlayFromGameFile) {
9412             /* Stop reading this game file */
9413             gameMode = EditGame;
9414             ModeHighlight();
9415         }
9416         /* [HGM] illegal-move claim should forfeit game when Xboard */
9417         /* only passes fully legal moves                            */
9418         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9419             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9420                                 "False illegal-move claim", GE_XBOARD );
9421             return; // do not take back move we tested as valid
9422         }
9423         currentMove = forwardMostMove-1;
9424         DisplayMove(currentMove-1); /* before DisplayMoveError */
9425         SwitchClocks(forwardMostMove-1); // [HGM] race
9426         DisplayBothClocks();
9427         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9428                 parseList[currentMove], _(cps->which));
9429         DisplayMoveError(buf1);
9430         DrawPosition(FALSE, boards[currentMove]);
9431
9432         SetUserThinkingEnables();
9433         return;
9434     }
9435     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9436         /* Program has a broken "time" command that
9437            outputs a string not ending in newline.
9438            Don't use it. */
9439         cps->sendTime = 0;
9440     }
9441     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9442         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9443             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9444     }
9445
9446     /*
9447      * If chess program startup fails, exit with an error message.
9448      * Attempts to recover here are futile. [HGM] Well, we try anyway
9449      */
9450     if ((StrStr(message, "unknown host") != NULL)
9451         || (StrStr(message, "No remote directory") != NULL)
9452         || (StrStr(message, "not found") != NULL)
9453         || (StrStr(message, "No such file") != NULL)
9454         || (StrStr(message, "can't alloc") != NULL)
9455         || (StrStr(message, "Permission denied") != NULL)) {
9456
9457         cps->maybeThinking = FALSE;
9458         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9459                 _(cps->which), cps->program, cps->host, message);
9460         RemoveInputSource(cps->isr);
9461         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9462             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9463             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9464         }
9465         return;
9466     }
9467
9468     /*
9469      * Look for hint output
9470      */
9471     if (sscanf(message, "Hint: %s", buf1) == 1) {
9472         if (cps == &first && hintRequested) {
9473             hintRequested = FALSE;
9474             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9475                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9476                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9477                                     PosFlags(forwardMostMove),
9478                                     fromY, fromX, toY, toX, promoChar, buf1);
9479                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9480                 DisplayInformation(buf2);
9481             } else {
9482                 /* Hint move could not be parsed!? */
9483               snprintf(buf2, sizeof(buf2),
9484                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9485                         buf1, _(cps->which));
9486                 DisplayError(buf2, 0);
9487             }
9488         } else {
9489           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9490         }
9491         return;
9492     }
9493
9494     /*
9495      * Ignore other messages if game is not in progress
9496      */
9497     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9498         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9499
9500     /*
9501      * look for win, lose, draw, or draw offer
9502      */
9503     if (strncmp(message, "1-0", 3) == 0) {
9504         char *p, *q, *r = "";
9505         p = strchr(message, '{');
9506         if (p) {
9507             q = strchr(p, '}');
9508             if (q) {
9509                 *q = NULLCHAR;
9510                 r = p + 1;
9511             }
9512         }
9513         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9514         return;
9515     } else if (strncmp(message, "0-1", 3) == 0) {
9516         char *p, *q, *r = "";
9517         p = strchr(message, '{');
9518         if (p) {
9519             q = strchr(p, '}');
9520             if (q) {
9521                 *q = NULLCHAR;
9522                 r = p + 1;
9523             }
9524         }
9525         /* Kludge for Arasan 4.1 bug */
9526         if (strcmp(r, "Black resigns") == 0) {
9527             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9528             return;
9529         }
9530         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9531         return;
9532     } else if (strncmp(message, "1/2", 3) == 0) {
9533         char *p, *q, *r = "";
9534         p = strchr(message, '{');
9535         if (p) {
9536             q = strchr(p, '}');
9537             if (q) {
9538                 *q = NULLCHAR;
9539                 r = p + 1;
9540             }
9541         }
9542
9543         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9544         return;
9545
9546     } else if (strncmp(message, "White resign", 12) == 0) {
9547         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9548         return;
9549     } else if (strncmp(message, "Black resign", 12) == 0) {
9550         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9551         return;
9552     } else if (strncmp(message, "White matches", 13) == 0 ||
9553                strncmp(message, "Black matches", 13) == 0   ) {
9554         /* [HGM] ignore GNUShogi noises */
9555         return;
9556     } else if (strncmp(message, "White", 5) == 0 &&
9557                message[5] != '(' &&
9558                StrStr(message, "Black") == NULL) {
9559         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9560         return;
9561     } else if (strncmp(message, "Black", 5) == 0 &&
9562                message[5] != '(') {
9563         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9564         return;
9565     } else if (strcmp(message, "resign") == 0 ||
9566                strcmp(message, "computer resigns") == 0) {
9567         switch (gameMode) {
9568           case MachinePlaysBlack:
9569           case IcsPlayingBlack:
9570             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9571             break;
9572           case MachinePlaysWhite:
9573           case IcsPlayingWhite:
9574             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9575             break;
9576           case TwoMachinesPlay:
9577             if (cps->twoMachinesColor[0] == 'w')
9578               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9579             else
9580               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9581             break;
9582           default:
9583             /* can't happen */
9584             break;
9585         }
9586         return;
9587     } else if (strncmp(message, "opponent mates", 14) == 0) {
9588         switch (gameMode) {
9589           case MachinePlaysBlack:
9590           case IcsPlayingBlack:
9591             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9592             break;
9593           case MachinePlaysWhite:
9594           case IcsPlayingWhite:
9595             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9596             break;
9597           case TwoMachinesPlay:
9598             if (cps->twoMachinesColor[0] == 'w')
9599               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9600             else
9601               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9602             break;
9603           default:
9604             /* can't happen */
9605             break;
9606         }
9607         return;
9608     } else if (strncmp(message, "computer mates", 14) == 0) {
9609         switch (gameMode) {
9610           case MachinePlaysBlack:
9611           case IcsPlayingBlack:
9612             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9613             break;
9614           case MachinePlaysWhite:
9615           case IcsPlayingWhite:
9616             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9617             break;
9618           case TwoMachinesPlay:
9619             if (cps->twoMachinesColor[0] == 'w')
9620               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9621             else
9622               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9623             break;
9624           default:
9625             /* can't happen */
9626             break;
9627         }
9628         return;
9629     } else if (strncmp(message, "checkmate", 9) == 0) {
9630         if (WhiteOnMove(forwardMostMove)) {
9631             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9632         } else {
9633             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9634         }
9635         return;
9636     } else if (strstr(message, "Draw") != NULL ||
9637                strstr(message, "game is a draw") != NULL) {
9638         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9639         return;
9640     } else if (strstr(message, "offer") != NULL &&
9641                strstr(message, "draw") != NULL) {
9642 #if ZIPPY
9643         if (appData.zippyPlay && first.initDone) {
9644             /* Relay offer to ICS */
9645             SendToICS(ics_prefix);
9646             SendToICS("draw\n");
9647         }
9648 #endif
9649         cps->offeredDraw = 2; /* valid until this engine moves twice */
9650         if (gameMode == TwoMachinesPlay) {
9651             if (cps->other->offeredDraw) {
9652                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9653             /* [HGM] in two-machine mode we delay relaying draw offer      */
9654             /* until after we also have move, to see if it is really claim */
9655             }
9656         } else if (gameMode == MachinePlaysWhite ||
9657                    gameMode == MachinePlaysBlack) {
9658           if (userOfferedDraw) {
9659             DisplayInformation(_("Machine accepts your draw offer"));
9660             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9661           } else {
9662             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9663           }
9664         }
9665     }
9666
9667
9668     /*
9669      * Look for thinking output
9670      */
9671     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9672           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9673                                 ) {
9674         int plylev, mvleft, mvtot, curscore, time;
9675         char mvname[MOVE_LEN];
9676         u64 nodes; // [DM]
9677         char plyext;
9678         int ignore = FALSE;
9679         int prefixHint = FALSE;
9680         mvname[0] = NULLCHAR;
9681
9682         switch (gameMode) {
9683           case MachinePlaysBlack:
9684           case IcsPlayingBlack:
9685             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9686             break;
9687           case MachinePlaysWhite:
9688           case IcsPlayingWhite:
9689             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9690             break;
9691           case AnalyzeMode:
9692           case AnalyzeFile:
9693             break;
9694           case IcsObserving: /* [DM] icsEngineAnalyze */
9695             if (!appData.icsEngineAnalyze) ignore = TRUE;
9696             break;
9697           case TwoMachinesPlay:
9698             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9699                 ignore = TRUE;
9700             }
9701             break;
9702           default:
9703             ignore = TRUE;
9704             break;
9705         }
9706
9707         if (!ignore) {
9708             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9709             int solved = 0;
9710             buf1[0] = NULLCHAR;
9711             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9712                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9713                 char score_buf[MSG_SIZ];
9714
9715                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9716                     nodes += u64Const(0x100000000);
9717
9718                 if (plyext != ' ' && plyext != '\t') {
9719                     time *= 100;
9720                 }
9721
9722                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9723                 if( cps->scoreIsAbsolute &&
9724                     ( gameMode == MachinePlaysBlack ||
9725                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9726                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9727                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9728                      !WhiteOnMove(currentMove)
9729                     ) )
9730                 {
9731                     curscore = -curscore;
9732                 }
9733
9734                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9735
9736                 if(*bestMove) { // rememer time best EPD move was first found
9737                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9738                     ChessMove mt; char *p = bestMove;
9739                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9740                     solved = 0;
9741                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9742                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9743                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9744                             solved = 1;
9745                             break;
9746                         }
9747                         while(*p && *p != ' ') p++;
9748                         while(*p == ' ') p++;
9749                     }
9750                     if(!solved) solvingTime = -1;
9751                 }
9752                 if(*avoidMove && !solved) {
9753                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9754                     ChessMove mt; char *p = avoidMove, solved = 1;
9755                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9756                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9757                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9758                             solved = 0; solvingTime = -2;
9759                             break;
9760                         }
9761                         while(*p && *p != ' ') p++;
9762                         while(*p == ' ') p++;
9763                     }
9764                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9765                 }
9766
9767                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9768                         char buf[MSG_SIZ];
9769                         FILE *f;
9770                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9771                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9772                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9773                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9774                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9775                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9776                                 fclose(f);
9777                         }
9778                         else
9779                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9780                           DisplayError(_("failed writing PV"), 0);
9781                 }
9782
9783                 tempStats.depth = plylev;
9784                 tempStats.nodes = nodes;
9785                 tempStats.time = time;
9786                 tempStats.score = curscore;
9787                 tempStats.got_only_move = 0;
9788
9789                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9790                         int ticklen;
9791
9792                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9793                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9794                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9795                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9796                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9797                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9798                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9799                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9800                 }
9801
9802                 /* Buffer overflow protection */
9803                 if (pv[0] != NULLCHAR) {
9804                     if (strlen(pv) >= sizeof(tempStats.movelist)
9805                         && appData.debugMode) {
9806                         fprintf(debugFP,
9807                                 "PV is too long; using the first %u bytes.\n",
9808                                 (unsigned) sizeof(tempStats.movelist) - 1);
9809                     }
9810
9811                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9812                 } else {
9813                     sprintf(tempStats.movelist, " no PV\n");
9814                 }
9815
9816                 if (tempStats.seen_stat) {
9817                     tempStats.ok_to_send = 1;
9818                 }
9819
9820                 if (strchr(tempStats.movelist, '(') != NULL) {
9821                     tempStats.line_is_book = 1;
9822                     tempStats.nr_moves = 0;
9823                     tempStats.moves_left = 0;
9824                 } else {
9825                     tempStats.line_is_book = 0;
9826                 }
9827
9828                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9829                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9830
9831                 SendProgramStatsToFrontend( cps, &tempStats );
9832
9833                 /*
9834                     [AS] Protect the thinkOutput buffer from overflow... this
9835                     is only useful if buf1 hasn't overflowed first!
9836                 */
9837                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9838                 if(curscore >= MATE_SCORE) 
9839                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9840                 else if(curscore <= -MATE_SCORE) 
9841                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9842                 else
9843                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9844                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9845                          plylev,
9846                          (gameMode == TwoMachinesPlay ?
9847                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9848                          score_buf,
9849                          prefixHint ? lastHint : "",
9850                          prefixHint ? " " : "" );
9851
9852                 if( buf1[0] != NULLCHAR ) {
9853                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9854
9855                     if( strlen(pv) > max_len ) {
9856                         if( appData.debugMode) {
9857                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9858                         }
9859                         pv[max_len+1] = '\0';
9860                     }
9861
9862                     strcat( thinkOutput, pv);
9863                 }
9864
9865                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9866                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9867                     DisplayMove(currentMove - 1);
9868                 }
9869                 return;
9870
9871             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9872                 /* crafty (9.25+) says "(only move) <move>"
9873                  * if there is only 1 legal move
9874                  */
9875                 sscanf(p, "(only move) %s", buf1);
9876                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9877                 sprintf(programStats.movelist, "%s (only move)", buf1);
9878                 programStats.depth = 1;
9879                 programStats.nr_moves = 1;
9880                 programStats.moves_left = 1;
9881                 programStats.nodes = 1;
9882                 programStats.time = 1;
9883                 programStats.got_only_move = 1;
9884
9885                 /* Not really, but we also use this member to
9886                    mean "line isn't going to change" (Crafty
9887                    isn't searching, so stats won't change) */
9888                 programStats.line_is_book = 1;
9889
9890                 SendProgramStatsToFrontend( cps, &programStats );
9891
9892                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9893                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9894                     DisplayMove(currentMove - 1);
9895                 }
9896                 return;
9897             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9898                               &time, &nodes, &plylev, &mvleft,
9899                               &mvtot, mvname) >= 5) {
9900                 /* The stat01: line is from Crafty (9.29+) in response
9901                    to the "." command */
9902                 programStats.seen_stat = 1;
9903                 cps->maybeThinking = TRUE;
9904
9905                 if (programStats.got_only_move || !appData.periodicUpdates)
9906                   return;
9907
9908                 programStats.depth = plylev;
9909                 programStats.time = time;
9910                 programStats.nodes = nodes;
9911                 programStats.moves_left = mvleft;
9912                 programStats.nr_moves = mvtot;
9913                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9914                 programStats.ok_to_send = 1;
9915                 programStats.movelist[0] = '\0';
9916
9917                 SendProgramStatsToFrontend( cps, &programStats );
9918
9919                 return;
9920
9921             } else if (strncmp(message,"++",2) == 0) {
9922                 /* Crafty 9.29+ outputs this */
9923                 programStats.got_fail = 2;
9924                 return;
9925
9926             } else if (strncmp(message,"--",2) == 0) {
9927                 /* Crafty 9.29+ outputs this */
9928                 programStats.got_fail = 1;
9929                 return;
9930
9931             } else if (thinkOutput[0] != NULLCHAR &&
9932                        strncmp(message, "    ", 4) == 0) {
9933                 unsigned message_len;
9934
9935                 p = message;
9936                 while (*p && *p == ' ') p++;
9937
9938                 message_len = strlen( p );
9939
9940                 /* [AS] Avoid buffer overflow */
9941                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9942                     strcat(thinkOutput, " ");
9943                     strcat(thinkOutput, p);
9944                 }
9945
9946                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9947                     strcat(programStats.movelist, " ");
9948                     strcat(programStats.movelist, p);
9949                 }
9950
9951                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9952                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9953                     DisplayMove(currentMove - 1);
9954                 }
9955                 return;
9956             }
9957         }
9958         else {
9959             buf1[0] = NULLCHAR;
9960
9961             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9962                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9963             {
9964                 ChessProgramStats cpstats;
9965
9966                 if (plyext != ' ' && plyext != '\t') {
9967                     time *= 100;
9968                 }
9969
9970                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9971                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9972                     curscore = -curscore;
9973                 }
9974
9975                 cpstats.depth = plylev;
9976                 cpstats.nodes = nodes;
9977                 cpstats.time = time;
9978                 cpstats.score = curscore;
9979                 cpstats.got_only_move = 0;
9980                 cpstats.movelist[0] = '\0';
9981
9982                 if (buf1[0] != NULLCHAR) {
9983                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9984                 }
9985
9986                 cpstats.ok_to_send = 0;
9987                 cpstats.line_is_book = 0;
9988                 cpstats.nr_moves = 0;
9989                 cpstats.moves_left = 0;
9990
9991                 SendProgramStatsToFrontend( cps, &cpstats );
9992             }
9993         }
9994     }
9995 }
9996
9997
9998 /* Parse a game score from the character string "game", and
9999    record it as the history of the current game.  The game
10000    score is NOT assumed to start from the standard position.
10001    The display is not updated in any way.
10002    */
10003 void
10004 ParseGameHistory (char *game)
10005 {
10006     ChessMove moveType;
10007     int fromX, fromY, toX, toY, boardIndex;
10008     char promoChar;
10009     char *p, *q;
10010     char buf[MSG_SIZ];
10011
10012     if (appData.debugMode)
10013       fprintf(debugFP, "Parsing game history: %s\n", game);
10014
10015     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10016     gameInfo.site = StrSave(appData.icsHost);
10017     gameInfo.date = PGNDate();
10018     gameInfo.round = StrSave("-");
10019
10020     /* Parse out names of players */
10021     while (*game == ' ') game++;
10022     p = buf;
10023     while (*game != ' ') *p++ = *game++;
10024     *p = NULLCHAR;
10025     gameInfo.white = StrSave(buf);
10026     while (*game == ' ') game++;
10027     p = buf;
10028     while (*game != ' ' && *game != '\n') *p++ = *game++;
10029     *p = NULLCHAR;
10030     gameInfo.black = StrSave(buf);
10031
10032     /* Parse moves */
10033     boardIndex = blackPlaysFirst ? 1 : 0;
10034     yynewstr(game);
10035     for (;;) {
10036         yyboardindex = boardIndex;
10037         moveType = (ChessMove) Myylex();
10038         switch (moveType) {
10039           case IllegalMove:             /* maybe suicide chess, etc. */
10040   if (appData.debugMode) {
10041     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10042     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10043     setbuf(debugFP, NULL);
10044   }
10045           case WhitePromotion:
10046           case BlackPromotion:
10047           case WhiteNonPromotion:
10048           case BlackNonPromotion:
10049           case NormalMove:
10050           case FirstLeg:
10051           case WhiteCapturesEnPassant:
10052           case BlackCapturesEnPassant:
10053           case WhiteKingSideCastle:
10054           case WhiteQueenSideCastle:
10055           case BlackKingSideCastle:
10056           case BlackQueenSideCastle:
10057           case WhiteKingSideCastleWild:
10058           case WhiteQueenSideCastleWild:
10059           case BlackKingSideCastleWild:
10060           case BlackQueenSideCastleWild:
10061           /* PUSH Fabien */
10062           case WhiteHSideCastleFR:
10063           case WhiteASideCastleFR:
10064           case BlackHSideCastleFR:
10065           case BlackASideCastleFR:
10066           /* POP Fabien */
10067             fromX = currentMoveString[0] - AAA;
10068             fromY = currentMoveString[1] - ONE;
10069             toX = currentMoveString[2] - AAA;
10070             toY = currentMoveString[3] - ONE;
10071             promoChar = currentMoveString[4];
10072             break;
10073           case WhiteDrop:
10074           case BlackDrop:
10075             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10076             fromX = moveType == WhiteDrop ?
10077               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10078             (int) CharToPiece(ToLower(currentMoveString[0]));
10079             fromY = DROP_RANK;
10080             toX = currentMoveString[2] - AAA;
10081             toY = currentMoveString[3] - ONE;
10082             promoChar = NULLCHAR;
10083             break;
10084           case AmbiguousMove:
10085             /* bug? */
10086             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10087   if (appData.debugMode) {
10088     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10089     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10090     setbuf(debugFP, NULL);
10091   }
10092             DisplayError(buf, 0);
10093             return;
10094           case ImpossibleMove:
10095             /* bug? */
10096             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10097   if (appData.debugMode) {
10098     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10099     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10100     setbuf(debugFP, NULL);
10101   }
10102             DisplayError(buf, 0);
10103             return;
10104           case EndOfFile:
10105             if (boardIndex < backwardMostMove) {
10106                 /* Oops, gap.  How did that happen? */
10107                 DisplayError(_("Gap in move list"), 0);
10108                 return;
10109             }
10110             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10111             if (boardIndex > forwardMostMove) {
10112                 forwardMostMove = boardIndex;
10113             }
10114             return;
10115           case ElapsedTime:
10116             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10117                 strcat(parseList[boardIndex-1], " ");
10118                 strcat(parseList[boardIndex-1], yy_text);
10119             }
10120             continue;
10121           case Comment:
10122           case PGNTag:
10123           case NAG:
10124           default:
10125             /* ignore */
10126             continue;
10127           case WhiteWins:
10128           case BlackWins:
10129           case GameIsDrawn:
10130           case GameUnfinished:
10131             if (gameMode == IcsExamining) {
10132                 if (boardIndex < backwardMostMove) {
10133                     /* Oops, gap.  How did that happen? */
10134                     return;
10135                 }
10136                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10137                 return;
10138             }
10139             gameInfo.result = moveType;
10140             p = strchr(yy_text, '{');
10141             if (p == NULL) p = strchr(yy_text, '(');
10142             if (p == NULL) {
10143                 p = yy_text;
10144                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10145             } else {
10146                 q = strchr(p, *p == '{' ? '}' : ')');
10147                 if (q != NULL) *q = NULLCHAR;
10148                 p++;
10149             }
10150             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10151             gameInfo.resultDetails = StrSave(p);
10152             continue;
10153         }
10154         if (boardIndex >= forwardMostMove &&
10155             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10156             backwardMostMove = blackPlaysFirst ? 1 : 0;
10157             return;
10158         }
10159         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10160                                  fromY, fromX, toY, toX, promoChar,
10161                                  parseList[boardIndex]);
10162         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10163         /* currentMoveString is set as a side-effect of yylex */
10164         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10165         strcat(moveList[boardIndex], "\n");
10166         boardIndex++;
10167         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10168         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10169           case MT_NONE:
10170           case MT_STALEMATE:
10171           default:
10172             break;
10173           case MT_CHECK:
10174             if(!IS_SHOGI(gameInfo.variant))
10175                 strcat(parseList[boardIndex - 1], "+");
10176             break;
10177           case MT_CHECKMATE:
10178           case MT_STAINMATE:
10179             strcat(parseList[boardIndex - 1], "#");
10180             break;
10181         }
10182     }
10183 }
10184
10185
10186 /* Apply a move to the given board  */
10187 void
10188 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10189 {
10190   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10191   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10192
10193     /* [HGM] compute & store e.p. status and castling rights for new position */
10194     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10195
10196       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10197       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10198       board[EP_STATUS] = EP_NONE;
10199       board[EP_FILE] = board[EP_RANK] = 100;
10200
10201   if (fromY == DROP_RANK) {
10202         /* must be first */
10203         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10204             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10205             return;
10206         }
10207         piece = board[toY][toX] = (ChessSquare) fromX;
10208   } else {
10209 //      ChessSquare victim;
10210       int i;
10211
10212       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10213 //           victim = board[killY][killX],
10214            killed = board[killY][killX],
10215            board[killY][killX] = EmptySquare,
10216            board[EP_STATUS] = EP_CAPTURE;
10217            if( kill2X >= 0 && kill2Y >= 0)
10218              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10219       }
10220
10221       if( board[toY][toX] != EmptySquare ) {
10222            board[EP_STATUS] = EP_CAPTURE;
10223            if( (fromX != toX || fromY != toY) && // not igui!
10224                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10225                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10226                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10227            }
10228       }
10229
10230       pawn = board[fromY][fromX];
10231       if( pawn == WhiteLance || pawn == BlackLance ) {
10232            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10233                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10234                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10235            }
10236       }
10237       if( pawn == WhitePawn ) {
10238            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10239                board[EP_STATUS] = EP_PAWN_MOVE;
10240            if( toY-fromY>=2) {
10241                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10242                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10243                         gameInfo.variant != VariantBerolina || toX < fromX)
10244                       board[EP_STATUS] = toX | berolina;
10245                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10246                         gameInfo.variant != VariantBerolina || toX > fromX)
10247                       board[EP_STATUS] = toX;
10248            }
10249       } else
10250       if( pawn == BlackPawn ) {
10251            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10252                board[EP_STATUS] = EP_PAWN_MOVE;
10253            if( toY-fromY<= -2) {
10254                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10255                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10256                         gameInfo.variant != VariantBerolina || toX < fromX)
10257                       board[EP_STATUS] = toX | berolina;
10258                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10259                         gameInfo.variant != VariantBerolina || toX > fromX)
10260                       board[EP_STATUS] = toX;
10261            }
10262        }
10263
10264        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10265        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10266        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10267        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10268
10269        for(i=0; i<nrCastlingRights; i++) {
10270            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10271               board[CASTLING][i] == toX   && castlingRank[i] == toY
10272              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10273        }
10274
10275        if(gameInfo.variant == VariantSChess) { // update virginity
10276            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10277            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10278            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10279            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10280        }
10281
10282      if (fromX == toX && fromY == toY && killX < 0) return;
10283
10284      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10285      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10286      if(gameInfo.variant == VariantKnightmate)
10287          king += (int) WhiteUnicorn - (int) WhiteKing;
10288
10289     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10290        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10291         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10292         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10293         board[EP_STATUS] = EP_NONE; // capture was fake!
10294     } else
10295     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10296         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10297         board[toY][toX] = piece;
10298         board[EP_STATUS] = EP_NONE; // capture was fake!
10299     } else
10300     /* Code added by Tord: */
10301     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10302     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10303         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10304       board[EP_STATUS] = EP_NONE; // capture was fake!
10305       board[fromY][fromX] = EmptySquare;
10306       board[toY][toX] = EmptySquare;
10307       if((toX > fromX) != (piece == WhiteRook)) {
10308         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10309       } else {
10310         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10311       }
10312     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10313                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10314       board[EP_STATUS] = EP_NONE;
10315       board[fromY][fromX] = EmptySquare;
10316       board[toY][toX] = EmptySquare;
10317       if((toX > fromX) != (piece == BlackRook)) {
10318         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10319       } else {
10320         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10321       }
10322     /* End of code added by Tord */
10323
10324     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10325         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10326         board[toY][toX] = piece;
10327     } else if (board[fromY][fromX] == king
10328         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10329         && toY == fromY && toX > fromX+1) {
10330         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10331                                                                                              ; // castle with nearest piece
10332         board[fromY][toX-1] = board[fromY][rookX];
10333         board[fromY][rookX] = EmptySquare;
10334         board[fromY][fromX] = EmptySquare;
10335         board[toY][toX] = king;
10336     } else if (board[fromY][fromX] == king
10337         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10338                && toY == fromY && toX < fromX-1) {
10339         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10340                                                                                   ; // castle with nearest piece
10341         board[fromY][toX+1] = board[fromY][rookX];
10342         board[fromY][rookX] = EmptySquare;
10343         board[fromY][fromX] = EmptySquare;
10344         board[toY][toX] = king;
10345     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10346                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10347                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10348                ) {
10349         /* white pawn promotion */
10350         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10351         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10352             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10353         board[fromY][fromX] = EmptySquare;
10354     } else if ((fromY >= BOARD_HEIGHT>>1)
10355                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10356                && (toX != fromX)
10357                && gameInfo.variant != VariantXiangqi
10358                && gameInfo.variant != VariantBerolina
10359                && (pawn == WhitePawn)
10360                && (board[toY][toX] == EmptySquare)) {
10361         board[fromY][fromX] = EmptySquare;
10362         board[toY][toX] = piece;
10363         if(toY == epRank - 128 + 1)
10364             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10365         else
10366             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10367     } else if ((fromY == BOARD_HEIGHT-4)
10368                && (toX == fromX)
10369                && gameInfo.variant == VariantBerolina
10370                && (board[fromY][fromX] == WhitePawn)
10371                && (board[toY][toX] == EmptySquare)) {
10372         board[fromY][fromX] = EmptySquare;
10373         board[toY][toX] = WhitePawn;
10374         if(oldEP & EP_BEROLIN_A) {
10375                 captured = board[fromY][fromX-1];
10376                 board[fromY][fromX-1] = EmptySquare;
10377         }else{  captured = board[fromY][fromX+1];
10378                 board[fromY][fromX+1] = EmptySquare;
10379         }
10380     } else if (board[fromY][fromX] == king
10381         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10382                && toY == fromY && toX > fromX+1) {
10383         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10384                                                                                              ;
10385         board[fromY][toX-1] = board[fromY][rookX];
10386         board[fromY][rookX] = EmptySquare;
10387         board[fromY][fromX] = EmptySquare;
10388         board[toY][toX] = king;
10389     } else if (board[fromY][fromX] == king
10390         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10391                && toY == fromY && toX < fromX-1) {
10392         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10393                                                                                 ;
10394         board[fromY][toX+1] = board[fromY][rookX];
10395         board[fromY][rookX] = EmptySquare;
10396         board[fromY][fromX] = EmptySquare;
10397         board[toY][toX] = king;
10398     } else if (fromY == 7 && fromX == 3
10399                && board[fromY][fromX] == BlackKing
10400                && toY == 7 && toX == 5) {
10401         board[fromY][fromX] = EmptySquare;
10402         board[toY][toX] = BlackKing;
10403         board[fromY][7] = EmptySquare;
10404         board[toY][4] = BlackRook;
10405     } else if (fromY == 7 && fromX == 3
10406                && board[fromY][fromX] == BlackKing
10407                && toY == 7 && toX == 1) {
10408         board[fromY][fromX] = EmptySquare;
10409         board[toY][toX] = BlackKing;
10410         board[fromY][0] = EmptySquare;
10411         board[toY][2] = BlackRook;
10412     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10413                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10414                && toY < promoRank && promoChar
10415                ) {
10416         /* black pawn promotion */
10417         board[toY][toX] = CharToPiece(ToLower(promoChar));
10418         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10419             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10420         board[fromY][fromX] = EmptySquare;
10421     } else if ((fromY < BOARD_HEIGHT>>1)
10422                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10423                && (toX != fromX)
10424                && gameInfo.variant != VariantXiangqi
10425                && gameInfo.variant != VariantBerolina
10426                && (pawn == BlackPawn)
10427                && (board[toY][toX] == EmptySquare)) {
10428         board[fromY][fromX] = EmptySquare;
10429         board[toY][toX] = piece;
10430         if(toY == epRank - 128 - 1)
10431             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10432         else
10433             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10434     } else if ((fromY == 3)
10435                && (toX == fromX)
10436                && gameInfo.variant == VariantBerolina
10437                && (board[fromY][fromX] == BlackPawn)
10438                && (board[toY][toX] == EmptySquare)) {
10439         board[fromY][fromX] = EmptySquare;
10440         board[toY][toX] = BlackPawn;
10441         if(oldEP & EP_BEROLIN_A) {
10442                 captured = board[fromY][fromX-1];
10443                 board[fromY][fromX-1] = EmptySquare;
10444         }else{  captured = board[fromY][fromX+1];
10445                 board[fromY][fromX+1] = EmptySquare;
10446         }
10447     } else {
10448         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10449         board[fromY][fromX] = EmptySquare;
10450         board[toY][toX] = piece;
10451     }
10452   }
10453
10454     if (gameInfo.holdingsWidth != 0) {
10455
10456       /* !!A lot more code needs to be written to support holdings  */
10457       /* [HGM] OK, so I have written it. Holdings are stored in the */
10458       /* penultimate board files, so they are automaticlly stored   */
10459       /* in the game history.                                       */
10460       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10461                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10462         /* Delete from holdings, by decreasing count */
10463         /* and erasing image if necessary            */
10464         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10465         if(p < (int) BlackPawn) { /* white drop */
10466              p -= (int)WhitePawn;
10467                  p = PieceToNumber((ChessSquare)p);
10468              if(p >= gameInfo.holdingsSize) p = 0;
10469              if(--board[p][BOARD_WIDTH-2] <= 0)
10470                   board[p][BOARD_WIDTH-1] = EmptySquare;
10471              if((int)board[p][BOARD_WIDTH-2] < 0)
10472                         board[p][BOARD_WIDTH-2] = 0;
10473         } else {                  /* black drop */
10474              p -= (int)BlackPawn;
10475                  p = PieceToNumber((ChessSquare)p);
10476              if(p >= gameInfo.holdingsSize) p = 0;
10477              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10478                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10479              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10480                         board[BOARD_HEIGHT-1-p][1] = 0;
10481         }
10482       }
10483       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10484           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10485         /* [HGM] holdings: Add to holdings, if holdings exist */
10486         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10487                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10488                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10489         }
10490         p = (int) captured;
10491         if (p >= (int) BlackPawn) {
10492           p -= (int)BlackPawn;
10493           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10494                   /* Restore shogi-promoted piece to its original  first */
10495                   captured = (ChessSquare) (DEMOTED(captured));
10496                   p = DEMOTED(p);
10497           }
10498           p = PieceToNumber((ChessSquare)p);
10499           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10500           board[p][BOARD_WIDTH-2]++;
10501           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10502         } else {
10503           p -= (int)WhitePawn;
10504           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10505                   captured = (ChessSquare) (DEMOTED(captured));
10506                   p = DEMOTED(p);
10507           }
10508           p = PieceToNumber((ChessSquare)p);
10509           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10510           board[BOARD_HEIGHT-1-p][1]++;
10511           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10512         }
10513       }
10514     } else if (gameInfo.variant == VariantAtomic) {
10515       if (captured != EmptySquare) {
10516         int y, x;
10517         for (y = toY-1; y <= toY+1; y++) {
10518           for (x = toX-1; x <= toX+1; x++) {
10519             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10520                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10521               board[y][x] = EmptySquare;
10522             }
10523           }
10524         }
10525         board[toY][toX] = EmptySquare;
10526       }
10527     }
10528
10529     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10530         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10531     } else
10532     if(promoChar == '+') {
10533         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10534         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10535         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10536           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10537     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10538         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10539         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10540            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10541         board[toY][toX] = newPiece;
10542     }
10543     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10544                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10545         // [HGM] superchess: take promotion piece out of holdings
10546         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10547         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10548             if(!--board[k][BOARD_WIDTH-2])
10549                 board[k][BOARD_WIDTH-1] = EmptySquare;
10550         } else {
10551             if(!--board[BOARD_HEIGHT-1-k][1])
10552                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10553         }
10554     }
10555 }
10556
10557 /* Updates forwardMostMove */
10558 void
10559 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10560 {
10561     int x = toX, y = toY;
10562     char *s = parseList[forwardMostMove];
10563     ChessSquare p = boards[forwardMostMove][toY][toX];
10564 //    forwardMostMove++; // [HGM] bare: moved downstream
10565
10566     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10567     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10568     (void) CoordsToAlgebraic(boards[forwardMostMove],
10569                              PosFlags(forwardMostMove),
10570                              fromY, fromX, y, x, (killX < 0)*promoChar,
10571                              s);
10572     if(kill2X >= 0 && kill2Y >= 0)
10573         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10574     if(killX >= 0 && killY >= 0)
10575         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10576                                            toX + AAA, toY + ONE - '0', promoChar);
10577
10578     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10579         int timeLeft; static int lastLoadFlag=0; int king, piece;
10580         piece = boards[forwardMostMove][fromY][fromX];
10581         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10582         if(gameInfo.variant == VariantKnightmate)
10583             king += (int) WhiteUnicorn - (int) WhiteKing;
10584         if(forwardMostMove == 0) {
10585             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10586                 fprintf(serverMoves, "%s;", UserName());
10587             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10588                 fprintf(serverMoves, "%s;", second.tidy);
10589             fprintf(serverMoves, "%s;", first.tidy);
10590             if(gameMode == MachinePlaysWhite)
10591                 fprintf(serverMoves, "%s;", UserName());
10592             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10593                 fprintf(serverMoves, "%s;", second.tidy);
10594         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10595         lastLoadFlag = loadFlag;
10596         // print base move
10597         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10598         // print castling suffix
10599         if( toY == fromY && piece == king ) {
10600             if(toX-fromX > 1)
10601                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10602             if(fromX-toX >1)
10603                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10604         }
10605         // e.p. suffix
10606         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10607              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10608              boards[forwardMostMove][toY][toX] == EmptySquare
10609              && fromX != toX && fromY != toY)
10610                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10611         // promotion suffix
10612         if(promoChar != NULLCHAR) {
10613             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10614                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10615                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10616             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10617         }
10618         if(!loadFlag) {
10619                 char buf[MOVE_LEN*2], *p; int len;
10620             fprintf(serverMoves, "/%d/%d",
10621                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10622             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10623             else                      timeLeft = blackTimeRemaining/1000;
10624             fprintf(serverMoves, "/%d", timeLeft);
10625                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10626                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10627                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10628                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10629             fprintf(serverMoves, "/%s", buf);
10630         }
10631         fflush(serverMoves);
10632     }
10633
10634     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10635         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10636       return;
10637     }
10638     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10639     if (commentList[forwardMostMove+1] != NULL) {
10640         free(commentList[forwardMostMove+1]);
10641         commentList[forwardMostMove+1] = NULL;
10642     }
10643     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10644     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10645     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10646     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10647     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10648     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10649     adjustedClock = FALSE;
10650     gameInfo.result = GameUnfinished;
10651     if (gameInfo.resultDetails != NULL) {
10652         free(gameInfo.resultDetails);
10653         gameInfo.resultDetails = NULL;
10654     }
10655     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10656                               moveList[forwardMostMove - 1]);
10657     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10658       case MT_NONE:
10659       case MT_STALEMATE:
10660       default:
10661         break;
10662       case MT_CHECK:
10663         if(!IS_SHOGI(gameInfo.variant))
10664             strcat(parseList[forwardMostMove - 1], "+");
10665         break;
10666       case MT_CHECKMATE:
10667       case MT_STAINMATE:
10668         strcat(parseList[forwardMostMove - 1], "#");
10669         break;
10670     }
10671 }
10672
10673 /* Updates currentMove if not pausing */
10674 void
10675 ShowMove (int fromX, int fromY, int toX, int toY)
10676 {
10677     int instant = (gameMode == PlayFromGameFile) ?
10678         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10679     if(appData.noGUI) return;
10680     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10681         if (!instant) {
10682             if (forwardMostMove == currentMove + 1) {
10683                 AnimateMove(boards[forwardMostMove - 1],
10684                             fromX, fromY, toX, toY);
10685             }
10686         }
10687         currentMove = forwardMostMove;
10688     }
10689
10690     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10691
10692     if (instant) return;
10693
10694     DisplayMove(currentMove - 1);
10695     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10696             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10697                 SetHighlights(fromX, fromY, toX, toY);
10698             }
10699     }
10700     DrawPosition(FALSE, boards[currentMove]);
10701     DisplayBothClocks();
10702     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10703 }
10704
10705 void
10706 SendEgtPath (ChessProgramState *cps)
10707 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10708         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10709
10710         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10711
10712         while(*p) {
10713             char c, *q = name+1, *r, *s;
10714
10715             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10716             while(*p && *p != ',') *q++ = *p++;
10717             *q++ = ':'; *q = 0;
10718             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10719                 strcmp(name, ",nalimov:") == 0 ) {
10720                 // take nalimov path from the menu-changeable option first, if it is defined
10721               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10722                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10723             } else
10724             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10725                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10726                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10727                 s = r = StrStr(s, ":") + 1; // beginning of path info
10728                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10729                 c = *r; *r = 0;             // temporarily null-terminate path info
10730                     *--q = 0;               // strip of trailig ':' from name
10731                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10732                 *r = c;
10733                 SendToProgram(buf,cps);     // send egtbpath command for this format
10734             }
10735             if(*p == ',') p++; // read away comma to position for next format name
10736         }
10737 }
10738
10739 static int
10740 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10741 {
10742       int width = 8, height = 8, holdings = 0;             // most common sizes
10743       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10744       // correct the deviations default for each variant
10745       if( v == VariantXiangqi ) width = 9,  height = 10;
10746       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10747       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10748       if( v == VariantCapablanca || v == VariantCapaRandom ||
10749           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10750                                 width = 10;
10751       if( v == VariantCourier ) width = 12;
10752       if( v == VariantSuper )                            holdings = 8;
10753       if( v == VariantGreat )   width = 10,              holdings = 8;
10754       if( v == VariantSChess )                           holdings = 7;
10755       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10756       if( v == VariantChuChess) width = 10, height = 10;
10757       if( v == VariantChu )     width = 12, height = 12;
10758       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10759              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10760              holdingsSize >= 0 && holdingsSize != holdings;
10761 }
10762
10763 char variantError[MSG_SIZ];
10764
10765 char *
10766 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10767 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10768       char *p, *variant = VariantName(v);
10769       static char b[MSG_SIZ];
10770       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10771            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10772                                                holdingsSize, variant); // cook up sized variant name
10773            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10774            if(StrStr(list, b) == NULL) {
10775                // specific sized variant not known, check if general sizing allowed
10776                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10777                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10778                             boardWidth, boardHeight, holdingsSize, engine);
10779                    return NULL;
10780                }
10781                /* [HGM] here we really should compare with the maximum supported board size */
10782            }
10783       } else snprintf(b, MSG_SIZ,"%s", variant);
10784       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10785       p = StrStr(list, b);
10786       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10787       if(p == NULL) {
10788           // occurs not at all in list, or only as sub-string
10789           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10790           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10791               int l = strlen(variantError);
10792               char *q;
10793               while(p != list && p[-1] != ',') p--;
10794               q = strchr(p, ',');
10795               if(q) *q = NULLCHAR;
10796               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10797               if(q) *q= ',';
10798           }
10799           return NULL;
10800       }
10801       return b;
10802 }
10803
10804 void
10805 InitChessProgram (ChessProgramState *cps, int setup)
10806 /* setup needed to setup FRC opening position */
10807 {
10808     char buf[MSG_SIZ], *b;
10809     if (appData.noChessProgram) return;
10810     hintRequested = FALSE;
10811     bookRequested = FALSE;
10812
10813     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10814     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10815     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10816     if(cps->memSize) { /* [HGM] memory */
10817       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10818         SendToProgram(buf, cps);
10819     }
10820     SendEgtPath(cps); /* [HGM] EGT */
10821     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10822       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10823         SendToProgram(buf, cps);
10824     }
10825
10826     setboardSpoiledMachineBlack = FALSE;
10827     SendToProgram(cps->initString, cps);
10828     if (gameInfo.variant != VariantNormal &&
10829         gameInfo.variant != VariantLoadable
10830         /* [HGM] also send variant if board size non-standard */
10831         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10832
10833       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10834                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10835
10836       if (b == NULL) {
10837         VariantClass v;
10838         char c, *q = cps->variants, *p = strchr(q, ',');
10839         if(p) *p = NULLCHAR;
10840         v = StringToVariant(q);
10841         DisplayError(variantError, 0);
10842         if(v != VariantUnknown && cps == &first) {
10843             int w, h, s;
10844             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10845                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10846             ASSIGN(appData.variant, q);
10847             Reset(TRUE, FALSE);
10848         }
10849         if(p) *p = ',';
10850         return;
10851       }
10852
10853       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10854       SendToProgram(buf, cps);
10855     }
10856     currentlyInitializedVariant = gameInfo.variant;
10857
10858     /* [HGM] send opening position in FRC to first engine */
10859     if(setup) {
10860           SendToProgram("force\n", cps);
10861           SendBoard(cps, 0);
10862           /* engine is now in force mode! Set flag to wake it up after first move. */
10863           setboardSpoiledMachineBlack = 1;
10864     }
10865
10866     if (cps->sendICS) {
10867       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10868       SendToProgram(buf, cps);
10869     }
10870     cps->maybeThinking = FALSE;
10871     cps->offeredDraw = 0;
10872     if (!appData.icsActive) {
10873         SendTimeControl(cps, movesPerSession, timeControl,
10874                         timeIncrement, appData.searchDepth,
10875                         searchTime);
10876     }
10877     if (appData.showThinking
10878         // [HGM] thinking: four options require thinking output to be sent
10879         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10880                                 ) {
10881         SendToProgram("post\n", cps);
10882     }
10883     SendToProgram("hard\n", cps);
10884     if (!appData.ponderNextMove) {
10885         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10886            it without being sure what state we are in first.  "hard"
10887            is not a toggle, so that one is OK.
10888          */
10889         SendToProgram("easy\n", cps);
10890     }
10891     if (cps->usePing) {
10892       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10893       SendToProgram(buf, cps);
10894     }
10895     cps->initDone = TRUE;
10896     ClearEngineOutputPane(cps == &second);
10897 }
10898
10899
10900 void
10901 ResendOptions (ChessProgramState *cps)
10902 { // send the stored value of the options
10903   int i;
10904   char buf[MSG_SIZ];
10905   Option *opt = cps->option;
10906   for(i=0; i<cps->nrOptions; i++, opt++) {
10907       switch(opt->type) {
10908         case Spin:
10909         case Slider:
10910         case CheckBox:
10911             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10912           break;
10913         case ComboBox:
10914           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10915           break;
10916         default:
10917             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10918           break;
10919         case Button:
10920         case SaveButton:
10921           continue;
10922       }
10923       SendToProgram(buf, cps);
10924   }
10925 }
10926
10927 void
10928 StartChessProgram (ChessProgramState *cps)
10929 {
10930     char buf[MSG_SIZ];
10931     int err;
10932
10933     if (appData.noChessProgram) return;
10934     cps->initDone = FALSE;
10935
10936     if (strcmp(cps->host, "localhost") == 0) {
10937         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10938     } else if (*appData.remoteShell == NULLCHAR) {
10939         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10940     } else {
10941         if (*appData.remoteUser == NULLCHAR) {
10942           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10943                     cps->program);
10944         } else {
10945           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10946                     cps->host, appData.remoteUser, cps->program);
10947         }
10948         err = StartChildProcess(buf, "", &cps->pr);
10949     }
10950
10951     if (err != 0) {
10952       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10953         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10954         if(cps != &first) return;
10955         appData.noChessProgram = TRUE;
10956         ThawUI();
10957         SetNCPMode();
10958 //      DisplayFatalError(buf, err, 1);
10959 //      cps->pr = NoProc;
10960 //      cps->isr = NULL;
10961         return;
10962     }
10963
10964     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10965     if (cps->protocolVersion > 1) {
10966       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10967       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10968         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10969         cps->comboCnt = 0;  //                and values of combo boxes
10970       }
10971       SendToProgram(buf, cps);
10972       if(cps->reload) ResendOptions(cps);
10973     } else {
10974       SendToProgram("xboard\n", cps);
10975     }
10976 }
10977
10978 void
10979 TwoMachinesEventIfReady P((void))
10980 {
10981   static int curMess = 0;
10982   if (first.lastPing != first.lastPong) {
10983     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10984     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10985     return;
10986   }
10987   if (second.lastPing != second.lastPong) {
10988     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10989     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10990     return;
10991   }
10992   DisplayMessage("", ""); curMess = 0;
10993   TwoMachinesEvent();
10994 }
10995
10996 char *
10997 MakeName (char *template)
10998 {
10999     time_t clock;
11000     struct tm *tm;
11001     static char buf[MSG_SIZ];
11002     char *p = buf;
11003     int i;
11004
11005     clock = time((time_t *)NULL);
11006     tm = localtime(&clock);
11007
11008     while(*p++ = *template++) if(p[-1] == '%') {
11009         switch(*template++) {
11010           case 0:   *p = 0; return buf;
11011           case 'Y': i = tm->tm_year+1900; break;
11012           case 'y': i = tm->tm_year-100; break;
11013           case 'M': i = tm->tm_mon+1; break;
11014           case 'd': i = tm->tm_mday; break;
11015           case 'h': i = tm->tm_hour; break;
11016           case 'm': i = tm->tm_min; break;
11017           case 's': i = tm->tm_sec; break;
11018           default:  i = 0;
11019         }
11020         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11021     }
11022     return buf;
11023 }
11024
11025 int
11026 CountPlayers (char *p)
11027 {
11028     int n = 0;
11029     while(p = strchr(p, '\n')) p++, n++; // count participants
11030     return n;
11031 }
11032
11033 FILE *
11034 WriteTourneyFile (char *results, FILE *f)
11035 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11036     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11037     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11038         // create a file with tournament description
11039         fprintf(f, "-participants {%s}\n", appData.participants);
11040         fprintf(f, "-seedBase %d\n", appData.seedBase);
11041         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11042         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11043         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11044         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11045         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11046         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11047         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11048         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11049         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11050         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11051         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11052         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11053         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11054         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11055         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11056         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11057         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11058         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11059         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11060         fprintf(f, "-smpCores %d\n", appData.smpCores);
11061         if(searchTime > 0)
11062                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11063         else {
11064                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11065                 fprintf(f, "-tc %s\n", appData.timeControl);
11066                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11067         }
11068         fprintf(f, "-results \"%s\"\n", results);
11069     }
11070     return f;
11071 }
11072
11073 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11074
11075 void
11076 Substitute (char *participants, int expunge)
11077 {
11078     int i, changed, changes=0, nPlayers=0;
11079     char *p, *q, *r, buf[MSG_SIZ];
11080     if(participants == NULL) return;
11081     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11082     r = p = participants; q = appData.participants;
11083     while(*p && *p == *q) {
11084         if(*p == '\n') r = p+1, nPlayers++;
11085         p++; q++;
11086     }
11087     if(*p) { // difference
11088         while(*p && *p++ != '\n')
11089                                  ;
11090         while(*q && *q++ != '\n')
11091                                  ;
11092       changed = nPlayers;
11093         changes = 1 + (strcmp(p, q) != 0);
11094     }
11095     if(changes == 1) { // a single engine mnemonic was changed
11096         q = r; while(*q) nPlayers += (*q++ == '\n');
11097         p = buf; while(*r && (*p = *r++) != '\n') p++;
11098         *p = NULLCHAR;
11099         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11100         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11101         if(mnemonic[i]) { // The substitute is valid
11102             FILE *f;
11103             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11104                 flock(fileno(f), LOCK_EX);
11105                 ParseArgsFromFile(f);
11106                 fseek(f, 0, SEEK_SET);
11107                 FREE(appData.participants); appData.participants = participants;
11108                 if(expunge) { // erase results of replaced engine
11109                     int len = strlen(appData.results), w, b, dummy;
11110                     for(i=0; i<len; i++) {
11111                         Pairing(i, nPlayers, &w, &b, &dummy);
11112                         if((w == changed || b == changed) && appData.results[i] == '*') {
11113                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11114                             fclose(f);
11115                             return;
11116                         }
11117                     }
11118                     for(i=0; i<len; i++) {
11119                         Pairing(i, nPlayers, &w, &b, &dummy);
11120                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11121                     }
11122                 }
11123                 WriteTourneyFile(appData.results, f);
11124                 fclose(f); // release lock
11125                 return;
11126             }
11127         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11128     }
11129     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11130     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11131     free(participants);
11132     return;
11133 }
11134
11135 int
11136 CheckPlayers (char *participants)
11137 {
11138         int i;
11139         char buf[MSG_SIZ], *p;
11140         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11141         while(p = strchr(participants, '\n')) {
11142             *p = NULLCHAR;
11143             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11144             if(!mnemonic[i]) {
11145                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11146                 *p = '\n';
11147                 DisplayError(buf, 0);
11148                 return 1;
11149             }
11150             *p = '\n';
11151             participants = p + 1;
11152         }
11153         return 0;
11154 }
11155
11156 int
11157 CreateTourney (char *name)
11158 {
11159         FILE *f;
11160         if(matchMode && strcmp(name, appData.tourneyFile)) {
11161              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11162         }
11163         if(name[0] == NULLCHAR) {
11164             if(appData.participants[0])
11165                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11166             return 0;
11167         }
11168         f = fopen(name, "r");
11169         if(f) { // file exists
11170             ASSIGN(appData.tourneyFile, name);
11171             ParseArgsFromFile(f); // parse it
11172         } else {
11173             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11174             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11175                 DisplayError(_("Not enough participants"), 0);
11176                 return 0;
11177             }
11178             if(CheckPlayers(appData.participants)) return 0;
11179             ASSIGN(appData.tourneyFile, name);
11180             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11181             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11182         }
11183         fclose(f);
11184         appData.noChessProgram = FALSE;
11185         appData.clockMode = TRUE;
11186         SetGNUMode();
11187         return 1;
11188 }
11189
11190 int
11191 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11192 {
11193     char buf[MSG_SIZ], *p, *q;
11194     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11195     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11196     skip = !all && group[0]; // if group requested, we start in skip mode
11197     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11198         p = names; q = buf; header = 0;
11199         while(*p && *p != '\n') *q++ = *p++;
11200         *q = 0;
11201         if(*p == '\n') p++;
11202         if(buf[0] == '#') {
11203             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11204             depth++; // we must be entering a new group
11205             if(all) continue; // suppress printing group headers when complete list requested
11206             header = 1;
11207             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11208         }
11209         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11210         if(engineList[i]) free(engineList[i]);
11211         engineList[i] = strdup(buf);
11212         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11213         if(engineMnemonic[i]) free(engineMnemonic[i]);
11214         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11215             strcat(buf, " (");
11216             sscanf(q + 8, "%s", buf + strlen(buf));
11217             strcat(buf, ")");
11218         }
11219         engineMnemonic[i] = strdup(buf);
11220         i++;
11221     }
11222     engineList[i] = engineMnemonic[i] = NULL;
11223     return i;
11224 }
11225
11226 // following implemented as macro to avoid type limitations
11227 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11228
11229 void
11230 SwapEngines (int n)
11231 {   // swap settings for first engine and other engine (so far only some selected options)
11232     int h;
11233     char *p;
11234     if(n == 0) return;
11235     SWAP(directory, p)
11236     SWAP(chessProgram, p)
11237     SWAP(isUCI, h)
11238     SWAP(hasOwnBookUCI, h)
11239     SWAP(protocolVersion, h)
11240     SWAP(reuse, h)
11241     SWAP(scoreIsAbsolute, h)
11242     SWAP(timeOdds, h)
11243     SWAP(logo, p)
11244     SWAP(pgnName, p)
11245     SWAP(pvSAN, h)
11246     SWAP(engOptions, p)
11247     SWAP(engInitString, p)
11248     SWAP(computerString, p)
11249     SWAP(features, p)
11250     SWAP(fenOverride, p)
11251     SWAP(NPS, h)
11252     SWAP(accumulateTC, h)
11253     SWAP(drawDepth, h)
11254     SWAP(host, p)
11255     SWAP(pseudo, h)
11256 }
11257
11258 int
11259 GetEngineLine (char *s, int n)
11260 {
11261     int i;
11262     char buf[MSG_SIZ];
11263     extern char *icsNames;
11264     if(!s || !*s) return 0;
11265     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11266     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11267     if(!mnemonic[i]) return 0;
11268     if(n == 11) return 1; // just testing if there was a match
11269     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11270     if(n == 1) SwapEngines(n);
11271     ParseArgsFromString(buf);
11272     if(n == 1) SwapEngines(n);
11273     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11274         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11275         ParseArgsFromString(buf);
11276     }
11277     return 1;
11278 }
11279
11280 int
11281 SetPlayer (int player, char *p)
11282 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11283     int i;
11284     char buf[MSG_SIZ], *engineName;
11285     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11286     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11287     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11288     if(mnemonic[i]) {
11289         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11290         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11291         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11292         ParseArgsFromString(buf);
11293     } else { // no engine with this nickname is installed!
11294         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11295         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11296         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11297         ModeHighlight();
11298         DisplayError(buf, 0);
11299         return 0;
11300     }
11301     free(engineName);
11302     return i;
11303 }
11304
11305 char *recentEngines;
11306
11307 void
11308 RecentEngineEvent (int nr)
11309 {
11310     int n;
11311 //    SwapEngines(1); // bump first to second
11312 //    ReplaceEngine(&second, 1); // and load it there
11313     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11314     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11315     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11316         ReplaceEngine(&first, 0);
11317         FloatToFront(&appData.recentEngineList, command[n]);
11318     }
11319 }
11320
11321 int
11322 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11323 {   // determine players from game number
11324     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11325
11326     if(appData.tourneyType == 0) {
11327         roundsPerCycle = (nPlayers - 1) | 1;
11328         pairingsPerRound = nPlayers / 2;
11329     } else if(appData.tourneyType > 0) {
11330         roundsPerCycle = nPlayers - appData.tourneyType;
11331         pairingsPerRound = appData.tourneyType;
11332     }
11333     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11334     gamesPerCycle = gamesPerRound * roundsPerCycle;
11335     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11336     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11337     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11338     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11339     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11340     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11341
11342     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11343     if(appData.roundSync) *syncInterval = gamesPerRound;
11344
11345     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11346
11347     if(appData.tourneyType == 0) {
11348         if(curPairing == (nPlayers-1)/2 ) {
11349             *whitePlayer = curRound;
11350             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11351         } else {
11352             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11353             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11354             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11355             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11356         }
11357     } else if(appData.tourneyType > 1) {
11358         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11359         *whitePlayer = curRound + appData.tourneyType;
11360     } else if(appData.tourneyType > 0) {
11361         *whitePlayer = curPairing;
11362         *blackPlayer = curRound + appData.tourneyType;
11363     }
11364
11365     // take care of white/black alternation per round.
11366     // For cycles and games this is already taken care of by default, derived from matchGame!
11367     return curRound & 1;
11368 }
11369
11370 int
11371 NextTourneyGame (int nr, int *swapColors)
11372 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11373     char *p, *q;
11374     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11375     FILE *tf;
11376     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11377     tf = fopen(appData.tourneyFile, "r");
11378     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11379     ParseArgsFromFile(tf); fclose(tf);
11380     InitTimeControls(); // TC might be altered from tourney file
11381
11382     nPlayers = CountPlayers(appData.participants); // count participants
11383     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11384     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11385
11386     if(syncInterval) {
11387         p = q = appData.results;
11388         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11389         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11390             DisplayMessage(_("Waiting for other game(s)"),"");
11391             waitingForGame = TRUE;
11392             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11393             return 0;
11394         }
11395         waitingForGame = FALSE;
11396     }
11397
11398     if(appData.tourneyType < 0) {
11399         if(nr>=0 && !pairingReceived) {
11400             char buf[1<<16];
11401             if(pairing.pr == NoProc) {
11402                 if(!appData.pairingEngine[0]) {
11403                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11404                     return 0;
11405                 }
11406                 StartChessProgram(&pairing); // starts the pairing engine
11407             }
11408             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11409             SendToProgram(buf, &pairing);
11410             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11411             SendToProgram(buf, &pairing);
11412             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11413         }
11414         pairingReceived = 0;                              // ... so we continue here
11415         *swapColors = 0;
11416         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11417         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11418         matchGame = 1; roundNr = nr / syncInterval + 1;
11419     }
11420
11421     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11422
11423     // redefine engines, engine dir, etc.
11424     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11425     if(first.pr == NoProc) {
11426       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11427       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11428     }
11429     if(second.pr == NoProc) {
11430       SwapEngines(1);
11431       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11432       SwapEngines(1);         // and make that valid for second engine by swapping
11433       InitEngine(&second, 1);
11434     }
11435     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11436     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11437     return OK;
11438 }
11439
11440 void
11441 NextMatchGame ()
11442 {   // performs game initialization that does not invoke engines, and then tries to start the game
11443     int res, firstWhite, swapColors = 0;
11444     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11445     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
11446         char buf[MSG_SIZ];
11447         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11448         if(strcmp(buf, currentDebugFile)) { // name has changed
11449             FILE *f = fopen(buf, "w");
11450             if(f) { // if opening the new file failed, just keep using the old one
11451                 ASSIGN(currentDebugFile, buf);
11452                 fclose(debugFP);
11453                 debugFP = f;
11454             }
11455             if(appData.serverFileName) {
11456                 if(serverFP) fclose(serverFP);
11457                 serverFP = fopen(appData.serverFileName, "w");
11458                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11459                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11460             }
11461         }
11462     }
11463     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11464     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11465     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11466     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11467     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11468     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11469     Reset(FALSE, first.pr != NoProc);
11470     res = LoadGameOrPosition(matchGame); // setup game
11471     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11472     if(!res) return; // abort when bad game/pos file
11473     if(appData.epd) {// in EPD mode we make sure first engine is to move
11474         firstWhite = !(forwardMostMove & 1);
11475         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11476         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11477     }
11478     TwoMachinesEvent();
11479 }
11480
11481 void
11482 UserAdjudicationEvent (int result)
11483 {
11484     ChessMove gameResult = GameIsDrawn;
11485
11486     if( result > 0 ) {
11487         gameResult = WhiteWins;
11488     }
11489     else if( result < 0 ) {
11490         gameResult = BlackWins;
11491     }
11492
11493     if( gameMode == TwoMachinesPlay ) {
11494         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11495     }
11496 }
11497
11498
11499 // [HGM] save: calculate checksum of game to make games easily identifiable
11500 int
11501 StringCheckSum (char *s)
11502 {
11503         int i = 0;
11504         if(s==NULL) return 0;
11505         while(*s) i = i*259 + *s++;
11506         return i;
11507 }
11508
11509 int
11510 GameCheckSum ()
11511 {
11512         int i, sum=0;
11513         for(i=backwardMostMove; i<forwardMostMove; i++) {
11514                 sum += pvInfoList[i].depth;
11515                 sum += StringCheckSum(parseList[i]);
11516                 sum += StringCheckSum(commentList[i]);
11517                 sum *= 261;
11518         }
11519         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11520         return sum + StringCheckSum(commentList[i]);
11521 } // end of save patch
11522
11523 void
11524 GameEnds (ChessMove result, char *resultDetails, int whosays)
11525 {
11526     GameMode nextGameMode;
11527     int isIcsGame;
11528     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11529
11530     if(endingGame) return; /* [HGM] crash: forbid recursion */
11531     endingGame = 1;
11532     if(twoBoards) { // [HGM] dual: switch back to one board
11533         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11534         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11535     }
11536     if (appData.debugMode) {
11537       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11538               result, resultDetails ? resultDetails : "(null)", whosays);
11539     }
11540
11541     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11542
11543     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11544
11545     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11546         /* If we are playing on ICS, the server decides when the
11547            game is over, but the engine can offer to draw, claim
11548            a draw, or resign.
11549          */
11550 #if ZIPPY
11551         if (appData.zippyPlay && first.initDone) {
11552             if (result == GameIsDrawn) {
11553                 /* In case draw still needs to be claimed */
11554                 SendToICS(ics_prefix);
11555                 SendToICS("draw\n");
11556             } else if (StrCaseStr(resultDetails, "resign")) {
11557                 SendToICS(ics_prefix);
11558                 SendToICS("resign\n");
11559             }
11560         }
11561 #endif
11562         endingGame = 0; /* [HGM] crash */
11563         return;
11564     }
11565
11566     /* If we're loading the game from a file, stop */
11567     if (whosays == GE_FILE) {
11568       (void) StopLoadGameTimer();
11569       gameFileFP = NULL;
11570     }
11571
11572     /* Cancel draw offers */
11573     first.offeredDraw = second.offeredDraw = 0;
11574
11575     /* If this is an ICS game, only ICS can really say it's done;
11576        if not, anyone can. */
11577     isIcsGame = (gameMode == IcsPlayingWhite ||
11578                  gameMode == IcsPlayingBlack ||
11579                  gameMode == IcsObserving    ||
11580                  gameMode == IcsExamining);
11581
11582     if (!isIcsGame || whosays == GE_ICS) {
11583         /* OK -- not an ICS game, or ICS said it was done */
11584         StopClocks();
11585         if (!isIcsGame && !appData.noChessProgram)
11586           SetUserThinkingEnables();
11587
11588         /* [HGM] if a machine claims the game end we verify this claim */
11589         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11590             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11591                 char claimer;
11592                 ChessMove trueResult = (ChessMove) -1;
11593
11594                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11595                                             first.twoMachinesColor[0] :
11596                                             second.twoMachinesColor[0] ;
11597
11598                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11599                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11600                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11601                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11602                 } else
11603                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11604                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11605                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11606                 } else
11607                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11608                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11609                 }
11610
11611                 // now verify win claims, but not in drop games, as we don't understand those yet
11612                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11613                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11614                     (result == WhiteWins && claimer == 'w' ||
11615                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11616                       if (appData.debugMode) {
11617                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11618                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11619                       }
11620                       if(result != trueResult) {
11621                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11622                               result = claimer == 'w' ? BlackWins : WhiteWins;
11623                               resultDetails = buf;
11624                       }
11625                 } else
11626                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11627                     && (forwardMostMove <= backwardMostMove ||
11628                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11629                         (claimer=='b')==(forwardMostMove&1))
11630                                                                                   ) {
11631                       /* [HGM] verify: draws that were not flagged are false claims */
11632                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11633                       result = claimer == 'w' ? BlackWins : WhiteWins;
11634                       resultDetails = buf;
11635                 }
11636                 /* (Claiming a loss is accepted no questions asked!) */
11637             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11638                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11639                 result = GameUnfinished;
11640                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11641             }
11642             /* [HGM] bare: don't allow bare King to win */
11643             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11644                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11645                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11646                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11647                && result != GameIsDrawn)
11648             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11649                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11650                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11651                         if(p >= 0 && p <= (int)WhiteKing) k++;
11652                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11653                 }
11654                 if (appData.debugMode) {
11655                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11656                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11657                 }
11658                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11659                         result = GameIsDrawn;
11660                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11661                         resultDetails = buf;
11662                 }
11663             }
11664         }
11665
11666
11667         if(serverMoves != NULL && !loadFlag) { char c = '=';
11668             if(result==WhiteWins) c = '+';
11669             if(result==BlackWins) c = '-';
11670             if(resultDetails != NULL)
11671                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11672         }
11673         if (resultDetails != NULL) {
11674             gameInfo.result = result;
11675             gameInfo.resultDetails = StrSave(resultDetails);
11676
11677             /* display last move only if game was not loaded from file */
11678             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11679                 DisplayMove(currentMove - 1);
11680
11681             if (forwardMostMove != 0) {
11682                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11683                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11684                                                                 ) {
11685                     if (*appData.saveGameFile != NULLCHAR) {
11686                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11687                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11688                         else
11689                         SaveGameToFile(appData.saveGameFile, TRUE);
11690                     } else if (appData.autoSaveGames) {
11691                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11692                     }
11693                     if (*appData.savePositionFile != NULLCHAR) {
11694                         SavePositionToFile(appData.savePositionFile);
11695                     }
11696                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11697                 }
11698             }
11699
11700             /* Tell program how game ended in case it is learning */
11701             /* [HGM] Moved this to after saving the PGN, just in case */
11702             /* engine died and we got here through time loss. In that */
11703             /* case we will get a fatal error writing the pipe, which */
11704             /* would otherwise lose us the PGN.                       */
11705             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11706             /* output during GameEnds should never be fatal anymore   */
11707             if (gameMode == MachinePlaysWhite ||
11708                 gameMode == MachinePlaysBlack ||
11709                 gameMode == TwoMachinesPlay ||
11710                 gameMode == IcsPlayingWhite ||
11711                 gameMode == IcsPlayingBlack ||
11712                 gameMode == BeginningOfGame) {
11713                 char buf[MSG_SIZ];
11714                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11715                         resultDetails);
11716                 if (first.pr != NoProc) {
11717                     SendToProgram(buf, &first);
11718                 }
11719                 if (second.pr != NoProc &&
11720                     gameMode == TwoMachinesPlay) {
11721                     SendToProgram(buf, &second);
11722                 }
11723             }
11724         }
11725
11726         if (appData.icsActive) {
11727             if (appData.quietPlay &&
11728                 (gameMode == IcsPlayingWhite ||
11729                  gameMode == IcsPlayingBlack)) {
11730                 SendToICS(ics_prefix);
11731                 SendToICS("set shout 1\n");
11732             }
11733             nextGameMode = IcsIdle;
11734             ics_user_moved = FALSE;
11735             /* clean up premove.  It's ugly when the game has ended and the
11736              * premove highlights are still on the board.
11737              */
11738             if (gotPremove) {
11739               gotPremove = FALSE;
11740               ClearPremoveHighlights();
11741               DrawPosition(FALSE, boards[currentMove]);
11742             }
11743             if (whosays == GE_ICS) {
11744                 switch (result) {
11745                 case WhiteWins:
11746                     if (gameMode == IcsPlayingWhite)
11747                         PlayIcsWinSound();
11748                     else if(gameMode == IcsPlayingBlack)
11749                         PlayIcsLossSound();
11750                     break;
11751                 case BlackWins:
11752                     if (gameMode == IcsPlayingBlack)
11753                         PlayIcsWinSound();
11754                     else if(gameMode == IcsPlayingWhite)
11755                         PlayIcsLossSound();
11756                     break;
11757                 case GameIsDrawn:
11758                     PlayIcsDrawSound();
11759                     break;
11760                 default:
11761                     PlayIcsUnfinishedSound();
11762                 }
11763             }
11764             if(appData.quitNext) { ExitEvent(0); return; }
11765         } else if (gameMode == EditGame ||
11766                    gameMode == PlayFromGameFile ||
11767                    gameMode == AnalyzeMode ||
11768                    gameMode == AnalyzeFile) {
11769             nextGameMode = gameMode;
11770         } else {
11771             nextGameMode = EndOfGame;
11772         }
11773         pausing = FALSE;
11774         ModeHighlight();
11775     } else {
11776         nextGameMode = gameMode;
11777     }
11778
11779     if (appData.noChessProgram) {
11780         gameMode = nextGameMode;
11781         ModeHighlight();
11782         endingGame = 0; /* [HGM] crash */
11783         return;
11784     }
11785
11786     if (first.reuse) {
11787         /* Put first chess program into idle state */
11788         if (first.pr != NoProc &&
11789             (gameMode == MachinePlaysWhite ||
11790              gameMode == MachinePlaysBlack ||
11791              gameMode == TwoMachinesPlay ||
11792              gameMode == IcsPlayingWhite ||
11793              gameMode == IcsPlayingBlack ||
11794              gameMode == BeginningOfGame)) {
11795             SendToProgram("force\n", &first);
11796             if (first.usePing) {
11797               char buf[MSG_SIZ];
11798               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11799               SendToProgram(buf, &first);
11800             }
11801         }
11802     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11803         /* Kill off first chess program */
11804         if (first.isr != NULL)
11805           RemoveInputSource(first.isr);
11806         first.isr = NULL;
11807
11808         if (first.pr != NoProc) {
11809             ExitAnalyzeMode();
11810             DoSleep( appData.delayBeforeQuit );
11811             SendToProgram("quit\n", &first);
11812             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11813             first.reload = TRUE;
11814         }
11815         first.pr = NoProc;
11816     }
11817     if (second.reuse) {
11818         /* Put second chess program into idle state */
11819         if (second.pr != NoProc &&
11820             gameMode == TwoMachinesPlay) {
11821             SendToProgram("force\n", &second);
11822             if (second.usePing) {
11823               char buf[MSG_SIZ];
11824               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11825               SendToProgram(buf, &second);
11826             }
11827         }
11828     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11829         /* Kill off second chess program */
11830         if (second.isr != NULL)
11831           RemoveInputSource(second.isr);
11832         second.isr = NULL;
11833
11834         if (second.pr != NoProc) {
11835             DoSleep( appData.delayBeforeQuit );
11836             SendToProgram("quit\n", &second);
11837             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11838             second.reload = TRUE;
11839         }
11840         second.pr = NoProc;
11841     }
11842
11843     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11844         char resChar = '=';
11845         switch (result) {
11846         case WhiteWins:
11847           resChar = '+';
11848           if (first.twoMachinesColor[0] == 'w') {
11849             first.matchWins++;
11850           } else {
11851             second.matchWins++;
11852           }
11853           break;
11854         case BlackWins:
11855           resChar = '-';
11856           if (first.twoMachinesColor[0] == 'b') {
11857             first.matchWins++;
11858           } else {
11859             second.matchWins++;
11860           }
11861           break;
11862         case GameUnfinished:
11863           resChar = ' ';
11864         default:
11865           break;
11866         }
11867
11868         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11869         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11870             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11871             ReserveGame(nextGame, resChar); // sets nextGame
11872             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11873             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11874         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11875
11876         if (nextGame <= appData.matchGames && !abortMatch) {
11877             gameMode = nextGameMode;
11878             matchGame = nextGame; // this will be overruled in tourney mode!
11879             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11880             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11881             endingGame = 0; /* [HGM] crash */
11882             return;
11883         } else {
11884             gameMode = nextGameMode;
11885             if(appData.epd) {
11886                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11887                 OutputKibitz(2, buf);
11888                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11889                 OutputKibitz(2, buf);
11890                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11891                 if(second.matchWins) OutputKibitz(2, buf);
11892                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11893                 OutputKibitz(2, buf);
11894             }
11895             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11896                      first.tidy, second.tidy,
11897                      first.matchWins, second.matchWins,
11898                      appData.matchGames - (first.matchWins + second.matchWins));
11899             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11900             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11901             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11902             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11903                 first.twoMachinesColor = "black\n";
11904                 second.twoMachinesColor = "white\n";
11905             } else {
11906                 first.twoMachinesColor = "white\n";
11907                 second.twoMachinesColor = "black\n";
11908             }
11909         }
11910     }
11911     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11912         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11913       ExitAnalyzeMode();
11914     gameMode = nextGameMode;
11915     ModeHighlight();
11916     endingGame = 0;  /* [HGM] crash */
11917     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11918         if(matchMode == TRUE) { // match through command line: exit with or without popup
11919             if(ranking) {
11920                 ToNrEvent(forwardMostMove);
11921                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11922                 else ExitEvent(0);
11923             } else DisplayFatalError(buf, 0, 0);
11924         } else { // match through menu; just stop, with or without popup
11925             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11926             ModeHighlight();
11927             if(ranking){
11928                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11929             } else DisplayNote(buf);
11930       }
11931       if(ranking) free(ranking);
11932     }
11933 }
11934
11935 /* Assumes program was just initialized (initString sent).
11936    Leaves program in force mode. */
11937 void
11938 FeedMovesToProgram (ChessProgramState *cps, int upto)
11939 {
11940     int i;
11941
11942     if (appData.debugMode)
11943       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11944               startedFromSetupPosition ? "position and " : "",
11945               backwardMostMove, upto, cps->which);
11946     if(currentlyInitializedVariant != gameInfo.variant) {
11947       char buf[MSG_SIZ];
11948         // [HGM] variantswitch: make engine aware of new variant
11949         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11950                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11951                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11952         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11953         SendToProgram(buf, cps);
11954         currentlyInitializedVariant = gameInfo.variant;
11955     }
11956     SendToProgram("force\n", cps);
11957     if (startedFromSetupPosition) {
11958         SendBoard(cps, backwardMostMove);
11959     if (appData.debugMode) {
11960         fprintf(debugFP, "feedMoves\n");
11961     }
11962     }
11963     for (i = backwardMostMove; i < upto; i++) {
11964         SendMoveToProgram(i, cps);
11965     }
11966 }
11967
11968
11969 int
11970 ResurrectChessProgram ()
11971 {
11972      /* The chess program may have exited.
11973         If so, restart it and feed it all the moves made so far. */
11974     static int doInit = 0;
11975
11976     if (appData.noChessProgram) return 1;
11977
11978     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11979         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11980         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11981         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11982     } else {
11983         if (first.pr != NoProc) return 1;
11984         StartChessProgram(&first);
11985     }
11986     InitChessProgram(&first, FALSE);
11987     FeedMovesToProgram(&first, currentMove);
11988
11989     if (!first.sendTime) {
11990         /* can't tell gnuchess what its clock should read,
11991            so we bow to its notion. */
11992         ResetClocks();
11993         timeRemaining[0][currentMove] = whiteTimeRemaining;
11994         timeRemaining[1][currentMove] = blackTimeRemaining;
11995     }
11996
11997     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11998                 appData.icsEngineAnalyze) && first.analysisSupport) {
11999       SendToProgram("analyze\n", &first);
12000       first.analyzing = TRUE;
12001     }
12002     return 1;
12003 }
12004
12005 /*
12006  * Button procedures
12007  */
12008 void
12009 Reset (int redraw, int init)
12010 {
12011     int i;
12012
12013     if (appData.debugMode) {
12014         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12015                 redraw, init, gameMode);
12016     }
12017     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12018     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12019     CleanupTail(); // [HGM] vari: delete any stored variations
12020     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12021     pausing = pauseExamInvalid = FALSE;
12022     startedFromSetupPosition = blackPlaysFirst = FALSE;
12023     firstMove = TRUE;
12024     whiteFlag = blackFlag = FALSE;
12025     userOfferedDraw = FALSE;
12026     hintRequested = bookRequested = FALSE;
12027     first.maybeThinking = FALSE;
12028     second.maybeThinking = FALSE;
12029     first.bookSuspend = FALSE; // [HGM] book
12030     second.bookSuspend = FALSE;
12031     thinkOutput[0] = NULLCHAR;
12032     lastHint[0] = NULLCHAR;
12033     ClearGameInfo(&gameInfo);
12034     gameInfo.variant = StringToVariant(appData.variant);
12035     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12036         gameInfo.variant = VariantUnknown;
12037         strncpy(engineVariant, appData.variant, MSG_SIZ);
12038     }
12039     ics_user_moved = ics_clock_paused = FALSE;
12040     ics_getting_history = H_FALSE;
12041     ics_gamenum = -1;
12042     white_holding[0] = black_holding[0] = NULLCHAR;
12043     ClearProgramStats();
12044     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12045
12046     ResetFrontEnd();
12047     ClearHighlights();
12048     flipView = appData.flipView;
12049     ClearPremoveHighlights();
12050     gotPremove = FALSE;
12051     alarmSounded = FALSE;
12052     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12053
12054     GameEnds(EndOfFile, NULL, GE_PLAYER);
12055     if(appData.serverMovesName != NULL) {
12056         /* [HGM] prepare to make moves file for broadcasting */
12057         clock_t t = clock();
12058         if(serverMoves != NULL) fclose(serverMoves);
12059         serverMoves = fopen(appData.serverMovesName, "r");
12060         if(serverMoves != NULL) {
12061             fclose(serverMoves);
12062             /* delay 15 sec before overwriting, so all clients can see end */
12063             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12064         }
12065         serverMoves = fopen(appData.serverMovesName, "w");
12066     }
12067
12068     ExitAnalyzeMode();
12069     gameMode = BeginningOfGame;
12070     ModeHighlight();
12071     if(appData.icsActive) gameInfo.variant = VariantNormal;
12072     currentMove = forwardMostMove = backwardMostMove = 0;
12073     MarkTargetSquares(1);
12074     InitPosition(redraw);
12075     for (i = 0; i < MAX_MOVES; i++) {
12076         if (commentList[i] != NULL) {
12077             free(commentList[i]);
12078             commentList[i] = NULL;
12079         }
12080     }
12081     ResetClocks();
12082     timeRemaining[0][0] = whiteTimeRemaining;
12083     timeRemaining[1][0] = blackTimeRemaining;
12084
12085     if (first.pr == NoProc) {
12086         StartChessProgram(&first);
12087     }
12088     if (init) {
12089             InitChessProgram(&first, startedFromSetupPosition);
12090     }
12091     DisplayTitle("");
12092     DisplayMessage("", "");
12093     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12094     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12095     ClearMap();        // [HGM] exclude: invalidate map
12096 }
12097
12098 void
12099 AutoPlayGameLoop ()
12100 {
12101     for (;;) {
12102         if (!AutoPlayOneMove())
12103           return;
12104         if (matchMode || appData.timeDelay == 0)
12105           continue;
12106         if (appData.timeDelay < 0)
12107           return;
12108         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12109         break;
12110     }
12111 }
12112
12113 void
12114 AnalyzeNextGame()
12115 {
12116     ReloadGame(1); // next game
12117 }
12118
12119 int
12120 AutoPlayOneMove ()
12121 {
12122     int fromX, fromY, toX, toY;
12123
12124     if (appData.debugMode) {
12125       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12126     }
12127
12128     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12129       return FALSE;
12130
12131     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12132       pvInfoList[currentMove].depth = programStats.depth;
12133       pvInfoList[currentMove].score = programStats.score;
12134       pvInfoList[currentMove].time  = 0;
12135       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12136       else { // append analysis of final position as comment
12137         char buf[MSG_SIZ];
12138         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12139         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12140       }
12141       programStats.depth = 0;
12142     }
12143
12144     if (currentMove >= forwardMostMove) {
12145       if(gameMode == AnalyzeFile) {
12146           if(appData.loadGameIndex == -1) {
12147             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12148           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12149           } else {
12150           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12151         }
12152       }
12153 //      gameMode = EndOfGame;
12154 //      ModeHighlight();
12155
12156       /* [AS] Clear current move marker at the end of a game */
12157       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12158
12159       return FALSE;
12160     }
12161
12162     toX = moveList[currentMove][2] - AAA;
12163     toY = moveList[currentMove][3] - ONE;
12164
12165     if (moveList[currentMove][1] == '@') {
12166         if (appData.highlightLastMove) {
12167             SetHighlights(-1, -1, toX, toY);
12168         }
12169     } else {
12170         fromX = moveList[currentMove][0] - AAA;
12171         fromY = moveList[currentMove][1] - ONE;
12172
12173         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12174
12175         if(moveList[currentMove][4] == ';') { // multi-leg
12176             killX = moveList[currentMove][5] - AAA;
12177             killY = moveList[currentMove][6] - ONE;
12178         }
12179         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12180         killX = killY = -1;
12181
12182         if (appData.highlightLastMove) {
12183             SetHighlights(fromX, fromY, toX, toY);
12184         }
12185     }
12186     DisplayMove(currentMove);
12187     SendMoveToProgram(currentMove++, &first);
12188     DisplayBothClocks();
12189     DrawPosition(FALSE, boards[currentMove]);
12190     // [HGM] PV info: always display, routine tests if empty
12191     DisplayComment(currentMove - 1, commentList[currentMove]);
12192     return TRUE;
12193 }
12194
12195
12196 int
12197 LoadGameOneMove (ChessMove readAhead)
12198 {
12199     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12200     char promoChar = NULLCHAR;
12201     ChessMove moveType;
12202     char move[MSG_SIZ];
12203     char *p, *q;
12204
12205     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12206         gameMode != AnalyzeMode && gameMode != Training) {
12207         gameFileFP = NULL;
12208         return FALSE;
12209     }
12210
12211     yyboardindex = forwardMostMove;
12212     if (readAhead != EndOfFile) {
12213       moveType = readAhead;
12214     } else {
12215       if (gameFileFP == NULL)
12216           return FALSE;
12217       moveType = (ChessMove) Myylex();
12218     }
12219
12220     done = FALSE;
12221     switch (moveType) {
12222       case Comment:
12223         if (appData.debugMode)
12224           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12225         p = yy_text;
12226
12227         /* append the comment but don't display it */
12228         AppendComment(currentMove, p, FALSE);
12229         return TRUE;
12230
12231       case WhiteCapturesEnPassant:
12232       case BlackCapturesEnPassant:
12233       case WhitePromotion:
12234       case BlackPromotion:
12235       case WhiteNonPromotion:
12236       case BlackNonPromotion:
12237       case NormalMove:
12238       case FirstLeg:
12239       case WhiteKingSideCastle:
12240       case WhiteQueenSideCastle:
12241       case BlackKingSideCastle:
12242       case BlackQueenSideCastle:
12243       case WhiteKingSideCastleWild:
12244       case WhiteQueenSideCastleWild:
12245       case BlackKingSideCastleWild:
12246       case BlackQueenSideCastleWild:
12247       /* PUSH Fabien */
12248       case WhiteHSideCastleFR:
12249       case WhiteASideCastleFR:
12250       case BlackHSideCastleFR:
12251       case BlackASideCastleFR:
12252       /* POP Fabien */
12253         if (appData.debugMode)
12254           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12255         fromX = currentMoveString[0] - AAA;
12256         fromY = currentMoveString[1] - ONE;
12257         toX = currentMoveString[2] - AAA;
12258         toY = currentMoveString[3] - ONE;
12259         promoChar = currentMoveString[4];
12260         if(promoChar == ';') promoChar = currentMoveString[7];
12261         break;
12262
12263       case WhiteDrop:
12264       case BlackDrop:
12265         if (appData.debugMode)
12266           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12267         fromX = moveType == WhiteDrop ?
12268           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12269         (int) CharToPiece(ToLower(currentMoveString[0]));
12270         fromY = DROP_RANK;
12271         toX = currentMoveString[2] - AAA;
12272         toY = currentMoveString[3] - ONE;
12273         break;
12274
12275       case WhiteWins:
12276       case BlackWins:
12277       case GameIsDrawn:
12278       case GameUnfinished:
12279         if (appData.debugMode)
12280           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12281         p = strchr(yy_text, '{');
12282         if (p == NULL) p = strchr(yy_text, '(');
12283         if (p == NULL) {
12284             p = yy_text;
12285             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12286         } else {
12287             q = strchr(p, *p == '{' ? '}' : ')');
12288             if (q != NULL) *q = NULLCHAR;
12289             p++;
12290         }
12291         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12292         GameEnds(moveType, p, GE_FILE);
12293         done = TRUE;
12294         if (cmailMsgLoaded) {
12295             ClearHighlights();
12296             flipView = WhiteOnMove(currentMove);
12297             if (moveType == GameUnfinished) flipView = !flipView;
12298             if (appData.debugMode)
12299               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12300         }
12301         break;
12302
12303       case EndOfFile:
12304         if (appData.debugMode)
12305           fprintf(debugFP, "Parser hit end of file\n");
12306         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12307           case MT_NONE:
12308           case MT_CHECK:
12309             break;
12310           case MT_CHECKMATE:
12311           case MT_STAINMATE:
12312             if (WhiteOnMove(currentMove)) {
12313                 GameEnds(BlackWins, "Black mates", GE_FILE);
12314             } else {
12315                 GameEnds(WhiteWins, "White mates", GE_FILE);
12316             }
12317             break;
12318           case MT_STALEMATE:
12319             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12320             break;
12321         }
12322         done = TRUE;
12323         break;
12324
12325       case MoveNumberOne:
12326         if (lastLoadGameStart == GNUChessGame) {
12327             /* GNUChessGames have numbers, but they aren't move numbers */
12328             if (appData.debugMode)
12329               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12330                       yy_text, (int) moveType);
12331             return LoadGameOneMove(EndOfFile); /* tail recursion */
12332         }
12333         /* else fall thru */
12334
12335       case XBoardGame:
12336       case GNUChessGame:
12337       case PGNTag:
12338         /* Reached start of next game in file */
12339         if (appData.debugMode)
12340           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12341         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12342           case MT_NONE:
12343           case MT_CHECK:
12344             break;
12345           case MT_CHECKMATE:
12346           case MT_STAINMATE:
12347             if (WhiteOnMove(currentMove)) {
12348                 GameEnds(BlackWins, "Black mates", GE_FILE);
12349             } else {
12350                 GameEnds(WhiteWins, "White mates", GE_FILE);
12351             }
12352             break;
12353           case MT_STALEMATE:
12354             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12355             break;
12356         }
12357         done = TRUE;
12358         break;
12359
12360       case PositionDiagram:     /* should not happen; ignore */
12361       case ElapsedTime:         /* ignore */
12362       case NAG:                 /* ignore */
12363         if (appData.debugMode)
12364           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12365                   yy_text, (int) moveType);
12366         return LoadGameOneMove(EndOfFile); /* tail recursion */
12367
12368       case IllegalMove:
12369         if (appData.testLegality) {
12370             if (appData.debugMode)
12371               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12372             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12373                     (forwardMostMove / 2) + 1,
12374                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12375             DisplayError(move, 0);
12376             done = TRUE;
12377         } else {
12378             if (appData.debugMode)
12379               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12380                       yy_text, currentMoveString);
12381             if(currentMoveString[1] == '@') {
12382                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12383                 fromY = DROP_RANK;
12384             } else {
12385                 fromX = currentMoveString[0] - AAA;
12386                 fromY = currentMoveString[1] - ONE;
12387             }
12388             toX = currentMoveString[2] - AAA;
12389             toY = currentMoveString[3] - ONE;
12390             promoChar = currentMoveString[4];
12391         }
12392         break;
12393
12394       case AmbiguousMove:
12395         if (appData.debugMode)
12396           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12397         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12398                 (forwardMostMove / 2) + 1,
12399                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12400         DisplayError(move, 0);
12401         done = TRUE;
12402         break;
12403
12404       default:
12405       case ImpossibleMove:
12406         if (appData.debugMode)
12407           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12408         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12409                 (forwardMostMove / 2) + 1,
12410                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12411         DisplayError(move, 0);
12412         done = TRUE;
12413         break;
12414     }
12415
12416     if (done) {
12417         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12418             DrawPosition(FALSE, boards[currentMove]);
12419             DisplayBothClocks();
12420             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12421               DisplayComment(currentMove - 1, commentList[currentMove]);
12422         }
12423         (void) StopLoadGameTimer();
12424         gameFileFP = NULL;
12425         cmailOldMove = forwardMostMove;
12426         return FALSE;
12427     } else {
12428         /* currentMoveString is set as a side-effect of yylex */
12429
12430         thinkOutput[0] = NULLCHAR;
12431         MakeMove(fromX, fromY, toX, toY, promoChar);
12432         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12433         currentMove = forwardMostMove;
12434         return TRUE;
12435     }
12436 }
12437
12438 /* Load the nth game from the given file */
12439 int
12440 LoadGameFromFile (char *filename, int n, char *title, int useList)
12441 {
12442     FILE *f;
12443     char buf[MSG_SIZ];
12444
12445     if (strcmp(filename, "-") == 0) {
12446         f = stdin;
12447         title = "stdin";
12448     } else {
12449         f = fopen(filename, "rb");
12450         if (f == NULL) {
12451           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12452             DisplayError(buf, errno);
12453             return FALSE;
12454         }
12455     }
12456     if (fseek(f, 0, 0) == -1) {
12457         /* f is not seekable; probably a pipe */
12458         useList = FALSE;
12459     }
12460     if (useList && n == 0) {
12461         int error = GameListBuild(f);
12462         if (error) {
12463             DisplayError(_("Cannot build game list"), error);
12464         } else if (!ListEmpty(&gameList) &&
12465                    ((ListGame *) gameList.tailPred)->number > 1) {
12466             GameListPopUp(f, title);
12467             return TRUE;
12468         }
12469         GameListDestroy();
12470         n = 1;
12471     }
12472     if (n == 0) n = 1;
12473     return LoadGame(f, n, title, FALSE);
12474 }
12475
12476
12477 void
12478 MakeRegisteredMove ()
12479 {
12480     int fromX, fromY, toX, toY;
12481     char promoChar;
12482     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12483         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12484           case CMAIL_MOVE:
12485           case CMAIL_DRAW:
12486             if (appData.debugMode)
12487               fprintf(debugFP, "Restoring %s for game %d\n",
12488                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12489
12490             thinkOutput[0] = NULLCHAR;
12491             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12492             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12493             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12494             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12495             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12496             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12497             MakeMove(fromX, fromY, toX, toY, promoChar);
12498             ShowMove(fromX, fromY, toX, toY);
12499
12500             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12501               case MT_NONE:
12502               case MT_CHECK:
12503                 break;
12504
12505               case MT_CHECKMATE:
12506               case MT_STAINMATE:
12507                 if (WhiteOnMove(currentMove)) {
12508                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12509                 } else {
12510                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12511                 }
12512                 break;
12513
12514               case MT_STALEMATE:
12515                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12516                 break;
12517             }
12518
12519             break;
12520
12521           case CMAIL_RESIGN:
12522             if (WhiteOnMove(currentMove)) {
12523                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12524             } else {
12525                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12526             }
12527             break;
12528
12529           case CMAIL_ACCEPT:
12530             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12531             break;
12532
12533           default:
12534             break;
12535         }
12536     }
12537
12538     return;
12539 }
12540
12541 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12542 int
12543 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12544 {
12545     int retVal;
12546
12547     if (gameNumber > nCmailGames) {
12548         DisplayError(_("No more games in this message"), 0);
12549         return FALSE;
12550     }
12551     if (f == lastLoadGameFP) {
12552         int offset = gameNumber - lastLoadGameNumber;
12553         if (offset == 0) {
12554             cmailMsg[0] = NULLCHAR;
12555             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12556                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12557                 nCmailMovesRegistered--;
12558             }
12559             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12560             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12561                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12562             }
12563         } else {
12564             if (! RegisterMove()) return FALSE;
12565         }
12566     }
12567
12568     retVal = LoadGame(f, gameNumber, title, useList);
12569
12570     /* Make move registered during previous look at this game, if any */
12571     MakeRegisteredMove();
12572
12573     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12574         commentList[currentMove]
12575           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12576         DisplayComment(currentMove - 1, commentList[currentMove]);
12577     }
12578
12579     return retVal;
12580 }
12581
12582 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12583 int
12584 ReloadGame (int offset)
12585 {
12586     int gameNumber = lastLoadGameNumber + offset;
12587     if (lastLoadGameFP == NULL) {
12588         DisplayError(_("No game has been loaded yet"), 0);
12589         return FALSE;
12590     }
12591     if (gameNumber <= 0) {
12592         DisplayError(_("Can't back up any further"), 0);
12593         return FALSE;
12594     }
12595     if (cmailMsgLoaded) {
12596         return CmailLoadGame(lastLoadGameFP, gameNumber,
12597                              lastLoadGameTitle, lastLoadGameUseList);
12598     } else {
12599         return LoadGame(lastLoadGameFP, gameNumber,
12600                         lastLoadGameTitle, lastLoadGameUseList);
12601     }
12602 }
12603
12604 int keys[EmptySquare+1];
12605
12606 int
12607 PositionMatches (Board b1, Board b2)
12608 {
12609     int r, f, sum=0;
12610     switch(appData.searchMode) {
12611         case 1: return CompareWithRights(b1, b2);
12612         case 2:
12613             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12614                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12615             }
12616             return TRUE;
12617         case 3:
12618             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12619               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12620                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12621             }
12622             return sum==0;
12623         case 4:
12624             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12625                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12626             }
12627             return sum==0;
12628     }
12629     return TRUE;
12630 }
12631
12632 #define Q_PROMO  4
12633 #define Q_EP     3
12634 #define Q_BCASTL 2
12635 #define Q_WCASTL 1
12636
12637 int pieceList[256], quickBoard[256];
12638 ChessSquare pieceType[256] = { EmptySquare };
12639 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12640 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12641 int soughtTotal, turn;
12642 Boolean epOK, flipSearch;
12643
12644 typedef struct {
12645     unsigned char piece, to;
12646 } Move;
12647
12648 #define DSIZE (250000)
12649
12650 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12651 Move *moveDatabase = initialSpace;
12652 unsigned int movePtr, dataSize = DSIZE;
12653
12654 int
12655 MakePieceList (Board board, int *counts)
12656 {
12657     int r, f, n=Q_PROMO, total=0;
12658     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12659     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12660         int sq = f + (r<<4);
12661         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12662             quickBoard[sq] = ++n;
12663             pieceList[n] = sq;
12664             pieceType[n] = board[r][f];
12665             counts[board[r][f]]++;
12666             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12667             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12668             total++;
12669         }
12670     }
12671     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12672     return total;
12673 }
12674
12675 void
12676 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12677 {
12678     int sq = fromX + (fromY<<4);
12679     int piece = quickBoard[sq], rook;
12680     quickBoard[sq] = 0;
12681     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12682     if(piece == pieceList[1] && fromY == toY) {
12683       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12684         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12685         moveDatabase[movePtr++].piece = Q_WCASTL;
12686         quickBoard[sq] = piece;
12687         piece = quickBoard[from]; quickBoard[from] = 0;
12688         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12689       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12690         quickBoard[sq] = 0; // remove Rook
12691         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12692         moveDatabase[movePtr++].piece = Q_WCASTL;
12693         quickBoard[sq] = pieceList[1]; // put King
12694         piece = rook;
12695         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12696       }
12697     } else
12698     if(piece == pieceList[2] && fromY == toY) {
12699       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12700         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12701         moveDatabase[movePtr++].piece = Q_BCASTL;
12702         quickBoard[sq] = piece;
12703         piece = quickBoard[from]; quickBoard[from] = 0;
12704         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12705       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12706         quickBoard[sq] = 0; // remove Rook
12707         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12708         moveDatabase[movePtr++].piece = Q_BCASTL;
12709         quickBoard[sq] = pieceList[2]; // put King
12710         piece = rook;
12711         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12712       }
12713     } else
12714     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12715         quickBoard[(fromY<<4)+toX] = 0;
12716         moveDatabase[movePtr].piece = Q_EP;
12717         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12718         moveDatabase[movePtr].to = sq;
12719     } else
12720     if(promoPiece != pieceType[piece]) {
12721         moveDatabase[movePtr++].piece = Q_PROMO;
12722         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12723     }
12724     moveDatabase[movePtr].piece = piece;
12725     quickBoard[sq] = piece;
12726     movePtr++;
12727 }
12728
12729 int
12730 PackGame (Board board)
12731 {
12732     Move *newSpace = NULL;
12733     moveDatabase[movePtr].piece = 0; // terminate previous game
12734     if(movePtr > dataSize) {
12735         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12736         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12737         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12738         if(newSpace) {
12739             int i;
12740             Move *p = moveDatabase, *q = newSpace;
12741             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12742             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12743             moveDatabase = newSpace;
12744         } else { // calloc failed, we must be out of memory. Too bad...
12745             dataSize = 0; // prevent calloc events for all subsequent games
12746             return 0;     // and signal this one isn't cached
12747         }
12748     }
12749     movePtr++;
12750     MakePieceList(board, counts);
12751     return movePtr;
12752 }
12753
12754 int
12755 QuickCompare (Board board, int *minCounts, int *maxCounts)
12756 {   // compare according to search mode
12757     int r, f;
12758     switch(appData.searchMode)
12759     {
12760       case 1: // exact position match
12761         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12762         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12763             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12764         }
12765         break;
12766       case 2: // can have extra material on empty squares
12767         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12768             if(board[r][f] == EmptySquare) continue;
12769             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12770         }
12771         break;
12772       case 3: // material with exact Pawn structure
12773         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12774             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12775             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12776         } // fall through to material comparison
12777       case 4: // exact material
12778         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12779         break;
12780       case 6: // material range with given imbalance
12781         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12782         // fall through to range comparison
12783       case 5: // material range
12784         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12785     }
12786     return TRUE;
12787 }
12788
12789 int
12790 QuickScan (Board board, Move *move)
12791 {   // reconstruct game,and compare all positions in it
12792     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12793     do {
12794         int piece = move->piece;
12795         int to = move->to, from = pieceList[piece];
12796         if(found < 0) { // if already found just scan to game end for final piece count
12797           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12798            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12799            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12800                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12801             ) {
12802             static int lastCounts[EmptySquare+1];
12803             int i;
12804             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12805             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12806           } else stretch = 0;
12807           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12808           if(found >= 0 && !appData.minPieces) return found;
12809         }
12810         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12811           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12812           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12813             piece = (++move)->piece;
12814             from = pieceList[piece];
12815             counts[pieceType[piece]]--;
12816             pieceType[piece] = (ChessSquare) move->to;
12817             counts[move->to]++;
12818           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12819             counts[pieceType[quickBoard[to]]]--;
12820             quickBoard[to] = 0; total--;
12821             move++;
12822             continue;
12823           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12824             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12825             from  = pieceList[piece]; // so this must be King
12826             quickBoard[from] = 0;
12827             pieceList[piece] = to;
12828             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12829             quickBoard[from] = 0; // rook
12830             quickBoard[to] = piece;
12831             to = move->to; piece = move->piece;
12832             goto aftercastle;
12833           }
12834         }
12835         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12836         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12837         quickBoard[from] = 0;
12838       aftercastle:
12839         quickBoard[to] = piece;
12840         pieceList[piece] = to;
12841         cnt++; turn ^= 3;
12842         move++;
12843     } while(1);
12844 }
12845
12846 void
12847 InitSearch ()
12848 {
12849     int r, f;
12850     flipSearch = FALSE;
12851     CopyBoard(soughtBoard, boards[currentMove]);
12852     soughtTotal = MakePieceList(soughtBoard, maxSought);
12853     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12854     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12855     CopyBoard(reverseBoard, boards[currentMove]);
12856     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12857         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12858         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12859         reverseBoard[r][f] = piece;
12860     }
12861     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12862     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12863     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12864                  || (boards[currentMove][CASTLING][2] == NoRights ||
12865                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12866                  && (boards[currentMove][CASTLING][5] == NoRights ||
12867                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12868       ) {
12869         flipSearch = TRUE;
12870         CopyBoard(flipBoard, soughtBoard);
12871         CopyBoard(rotateBoard, reverseBoard);
12872         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12873             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12874             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12875         }
12876     }
12877     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12878     if(appData.searchMode >= 5) {
12879         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12880         MakePieceList(soughtBoard, minSought);
12881         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12882     }
12883     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12884         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12885 }
12886
12887 GameInfo dummyInfo;
12888 static int creatingBook;
12889
12890 int
12891 GameContainsPosition (FILE *f, ListGame *lg)
12892 {
12893     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12894     int fromX, fromY, toX, toY;
12895     char promoChar;
12896     static int initDone=FALSE;
12897
12898     // weed out games based on numerical tag comparison
12899     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12900     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12901     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12902     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12903     if(!initDone) {
12904         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12905         initDone = TRUE;
12906     }
12907     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12908     else CopyBoard(boards[scratch], initialPosition); // default start position
12909     if(lg->moves) {
12910         turn = btm + 1;
12911         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12912         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12913     }
12914     if(btm) plyNr++;
12915     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12916     fseek(f, lg->offset, 0);
12917     yynewfile(f);
12918     while(1) {
12919         yyboardindex = scratch;
12920         quickFlag = plyNr+1;
12921         next = Myylex();
12922         quickFlag = 0;
12923         switch(next) {
12924             case PGNTag:
12925                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12926             default:
12927                 continue;
12928
12929             case XBoardGame:
12930             case GNUChessGame:
12931                 if(plyNr) return -1; // after we have seen moves, this is for new game
12932               continue;
12933
12934             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12935             case ImpossibleMove:
12936             case WhiteWins: // game ends here with these four
12937             case BlackWins:
12938             case GameIsDrawn:
12939             case GameUnfinished:
12940                 return -1;
12941
12942             case IllegalMove:
12943                 if(appData.testLegality) return -1;
12944             case WhiteCapturesEnPassant:
12945             case BlackCapturesEnPassant:
12946             case WhitePromotion:
12947             case BlackPromotion:
12948             case WhiteNonPromotion:
12949             case BlackNonPromotion:
12950             case NormalMove:
12951             case FirstLeg:
12952             case WhiteKingSideCastle:
12953             case WhiteQueenSideCastle:
12954             case BlackKingSideCastle:
12955             case BlackQueenSideCastle:
12956             case WhiteKingSideCastleWild:
12957             case WhiteQueenSideCastleWild:
12958             case BlackKingSideCastleWild:
12959             case BlackQueenSideCastleWild:
12960             case WhiteHSideCastleFR:
12961             case WhiteASideCastleFR:
12962             case BlackHSideCastleFR:
12963             case BlackASideCastleFR:
12964                 fromX = currentMoveString[0] - AAA;
12965                 fromY = currentMoveString[1] - ONE;
12966                 toX = currentMoveString[2] - AAA;
12967                 toY = currentMoveString[3] - ONE;
12968                 promoChar = currentMoveString[4];
12969                 break;
12970             case WhiteDrop:
12971             case BlackDrop:
12972                 fromX = next == WhiteDrop ?
12973                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12974                   (int) CharToPiece(ToLower(currentMoveString[0]));
12975                 fromY = DROP_RANK;
12976                 toX = currentMoveString[2] - AAA;
12977                 toY = currentMoveString[3] - ONE;
12978                 promoChar = 0;
12979                 break;
12980         }
12981         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12982         plyNr++;
12983         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12984         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12985         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12986         if(appData.findMirror) {
12987             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12988             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12989         }
12990     }
12991 }
12992
12993 /* Load the nth game from open file f */
12994 int
12995 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12996 {
12997     ChessMove cm;
12998     char buf[MSG_SIZ];
12999     int gn = gameNumber;
13000     ListGame *lg = NULL;
13001     int numPGNTags = 0, i;
13002     int err, pos = -1;
13003     GameMode oldGameMode;
13004     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13005     char oldName[MSG_SIZ];
13006
13007     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13008
13009     if (appData.debugMode)
13010         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13011
13012     if (gameMode == Training )
13013         SetTrainingModeOff();
13014
13015     oldGameMode = gameMode;
13016     if (gameMode != BeginningOfGame) {
13017       Reset(FALSE, TRUE);
13018     }
13019     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13020
13021     gameFileFP = f;
13022     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13023         fclose(lastLoadGameFP);
13024     }
13025
13026     if (useList) {
13027         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13028
13029         if (lg) {
13030             fseek(f, lg->offset, 0);
13031             GameListHighlight(gameNumber);
13032             pos = lg->position;
13033             gn = 1;
13034         }
13035         else {
13036             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13037               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13038             else
13039             DisplayError(_("Game number out of range"), 0);
13040             return FALSE;
13041         }
13042     } else {
13043         GameListDestroy();
13044         if (fseek(f, 0, 0) == -1) {
13045             if (f == lastLoadGameFP ?
13046                 gameNumber == lastLoadGameNumber + 1 :
13047                 gameNumber == 1) {
13048                 gn = 1;
13049             } else {
13050                 DisplayError(_("Can't seek on game file"), 0);
13051                 return FALSE;
13052             }
13053         }
13054     }
13055     lastLoadGameFP = f;
13056     lastLoadGameNumber = gameNumber;
13057     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13058     lastLoadGameUseList = useList;
13059
13060     yynewfile(f);
13061
13062     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13063       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13064                 lg->gameInfo.black);
13065             DisplayTitle(buf);
13066     } else if (*title != NULLCHAR) {
13067         if (gameNumber > 1) {
13068           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13069             DisplayTitle(buf);
13070         } else {
13071             DisplayTitle(title);
13072         }
13073     }
13074
13075     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13076         gameMode = PlayFromGameFile;
13077         ModeHighlight();
13078     }
13079
13080     currentMove = forwardMostMove = backwardMostMove = 0;
13081     CopyBoard(boards[0], initialPosition);
13082     StopClocks();
13083
13084     /*
13085      * Skip the first gn-1 games in the file.
13086      * Also skip over anything that precedes an identifiable
13087      * start of game marker, to avoid being confused by
13088      * garbage at the start of the file.  Currently
13089      * recognized start of game markers are the move number "1",
13090      * the pattern "gnuchess .* game", the pattern
13091      * "^[#;%] [^ ]* game file", and a PGN tag block.
13092      * A game that starts with one of the latter two patterns
13093      * will also have a move number 1, possibly
13094      * following a position diagram.
13095      * 5-4-02: Let's try being more lenient and allowing a game to
13096      * start with an unnumbered move.  Does that break anything?
13097      */
13098     cm = lastLoadGameStart = EndOfFile;
13099     while (gn > 0) {
13100         yyboardindex = forwardMostMove;
13101         cm = (ChessMove) Myylex();
13102         switch (cm) {
13103           case EndOfFile:
13104             if (cmailMsgLoaded) {
13105                 nCmailGames = CMAIL_MAX_GAMES - gn;
13106             } else {
13107                 Reset(TRUE, TRUE);
13108                 DisplayError(_("Game not found in file"), 0);
13109             }
13110             return FALSE;
13111
13112           case GNUChessGame:
13113           case XBoardGame:
13114             gn--;
13115             lastLoadGameStart = cm;
13116             break;
13117
13118           case MoveNumberOne:
13119             switch (lastLoadGameStart) {
13120               case GNUChessGame:
13121               case XBoardGame:
13122               case PGNTag:
13123                 break;
13124               case MoveNumberOne:
13125               case EndOfFile:
13126                 gn--;           /* count this game */
13127                 lastLoadGameStart = cm;
13128                 break;
13129               default:
13130                 /* impossible */
13131                 break;
13132             }
13133             break;
13134
13135           case PGNTag:
13136             switch (lastLoadGameStart) {
13137               case GNUChessGame:
13138               case PGNTag:
13139               case MoveNumberOne:
13140               case EndOfFile:
13141                 gn--;           /* count this game */
13142                 lastLoadGameStart = cm;
13143                 break;
13144               case XBoardGame:
13145                 lastLoadGameStart = cm; /* game counted already */
13146                 break;
13147               default:
13148                 /* impossible */
13149                 break;
13150             }
13151             if (gn > 0) {
13152                 do {
13153                     yyboardindex = forwardMostMove;
13154                     cm = (ChessMove) Myylex();
13155                 } while (cm == PGNTag || cm == Comment);
13156             }
13157             break;
13158
13159           case WhiteWins:
13160           case BlackWins:
13161           case GameIsDrawn:
13162             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13163                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13164                     != CMAIL_OLD_RESULT) {
13165                     nCmailResults ++ ;
13166                     cmailResult[  CMAIL_MAX_GAMES
13167                                 - gn - 1] = CMAIL_OLD_RESULT;
13168                 }
13169             }
13170             break;
13171
13172           case NormalMove:
13173           case FirstLeg:
13174             /* Only a NormalMove can be at the start of a game
13175              * without a position diagram. */
13176             if (lastLoadGameStart == EndOfFile ) {
13177               gn--;
13178               lastLoadGameStart = MoveNumberOne;
13179             }
13180             break;
13181
13182           default:
13183             break;
13184         }
13185     }
13186
13187     if (appData.debugMode)
13188       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13189
13190     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13191
13192     if (cm == XBoardGame) {
13193         /* Skip any header junk before position diagram and/or move 1 */
13194         for (;;) {
13195             yyboardindex = forwardMostMove;
13196             cm = (ChessMove) Myylex();
13197
13198             if (cm == EndOfFile ||
13199                 cm == GNUChessGame || cm == XBoardGame) {
13200                 /* Empty game; pretend end-of-file and handle later */
13201                 cm = EndOfFile;
13202                 break;
13203             }
13204
13205             if (cm == MoveNumberOne || cm == PositionDiagram ||
13206                 cm == PGNTag || cm == Comment)
13207               break;
13208         }
13209     } else if (cm == GNUChessGame) {
13210         if (gameInfo.event != NULL) {
13211             free(gameInfo.event);
13212         }
13213         gameInfo.event = StrSave(yy_text);
13214     }
13215
13216     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13217     while (cm == PGNTag) {
13218         if (appData.debugMode)
13219           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13220         err = ParsePGNTag(yy_text, &gameInfo);
13221         if (!err) numPGNTags++;
13222
13223         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13224         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13225             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13226             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13227             InitPosition(TRUE);
13228             oldVariant = gameInfo.variant;
13229             if (appData.debugMode)
13230               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13231         }
13232
13233
13234         if (gameInfo.fen != NULL) {
13235           Board initial_position;
13236           startedFromSetupPosition = TRUE;
13237           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13238             Reset(TRUE, TRUE);
13239             DisplayError(_("Bad FEN position in file"), 0);
13240             return FALSE;
13241           }
13242           CopyBoard(boards[0], initial_position);
13243           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13244             CopyBoard(initialPosition, initial_position);
13245           if (blackPlaysFirst) {
13246             currentMove = forwardMostMove = backwardMostMove = 1;
13247             CopyBoard(boards[1], initial_position);
13248             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13249             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13250             timeRemaining[0][1] = whiteTimeRemaining;
13251             timeRemaining[1][1] = blackTimeRemaining;
13252             if (commentList[0] != NULL) {
13253               commentList[1] = commentList[0];
13254               commentList[0] = NULL;
13255             }
13256           } else {
13257             currentMove = forwardMostMove = backwardMostMove = 0;
13258           }
13259           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13260           {   int i;
13261               initialRulePlies = FENrulePlies;
13262               for( i=0; i< nrCastlingRights; i++ )
13263                   initialRights[i] = initial_position[CASTLING][i];
13264           }
13265           yyboardindex = forwardMostMove;
13266           free(gameInfo.fen);
13267           gameInfo.fen = NULL;
13268         }
13269
13270         yyboardindex = forwardMostMove;
13271         cm = (ChessMove) Myylex();
13272
13273         /* Handle comments interspersed among the tags */
13274         while (cm == Comment) {
13275             char *p;
13276             if (appData.debugMode)
13277               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13278             p = yy_text;
13279             AppendComment(currentMove, p, FALSE);
13280             yyboardindex = forwardMostMove;
13281             cm = (ChessMove) Myylex();
13282         }
13283     }
13284
13285     /* don't rely on existence of Event tag since if game was
13286      * pasted from clipboard the Event tag may not exist
13287      */
13288     if (numPGNTags > 0){
13289         char *tags;
13290         if (gameInfo.variant == VariantNormal) {
13291           VariantClass v = StringToVariant(gameInfo.event);
13292           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13293           if(v < VariantShogi) gameInfo.variant = v;
13294         }
13295         if (!matchMode) {
13296           if( appData.autoDisplayTags ) {
13297             tags = PGNTags(&gameInfo);
13298             TagsPopUp(tags, CmailMsg());
13299             free(tags);
13300           }
13301         }
13302     } else {
13303         /* Make something up, but don't display it now */
13304         SetGameInfo();
13305         TagsPopDown();
13306     }
13307
13308     if (cm == PositionDiagram) {
13309         int i, j;
13310         char *p;
13311         Board initial_position;
13312
13313         if (appData.debugMode)
13314           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13315
13316         if (!startedFromSetupPosition) {
13317             p = yy_text;
13318             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13319               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13320                 switch (*p) {
13321                   case '{':
13322                   case '[':
13323                   case '-':
13324                   case ' ':
13325                   case '\t':
13326                   case '\n':
13327                   case '\r':
13328                     break;
13329                   default:
13330                     initial_position[i][j++] = CharToPiece(*p);
13331                     break;
13332                 }
13333             while (*p == ' ' || *p == '\t' ||
13334                    *p == '\n' || *p == '\r') p++;
13335
13336             if (strncmp(p, "black", strlen("black"))==0)
13337               blackPlaysFirst = TRUE;
13338             else
13339               blackPlaysFirst = FALSE;
13340             startedFromSetupPosition = TRUE;
13341
13342             CopyBoard(boards[0], initial_position);
13343             if (blackPlaysFirst) {
13344                 currentMove = forwardMostMove = backwardMostMove = 1;
13345                 CopyBoard(boards[1], initial_position);
13346                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13347                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13348                 timeRemaining[0][1] = whiteTimeRemaining;
13349                 timeRemaining[1][1] = blackTimeRemaining;
13350                 if (commentList[0] != NULL) {
13351                     commentList[1] = commentList[0];
13352                     commentList[0] = NULL;
13353                 }
13354             } else {
13355                 currentMove = forwardMostMove = backwardMostMove = 0;
13356             }
13357         }
13358         yyboardindex = forwardMostMove;
13359         cm = (ChessMove) Myylex();
13360     }
13361
13362   if(!creatingBook) {
13363     if (first.pr == NoProc) {
13364         StartChessProgram(&first);
13365     }
13366     InitChessProgram(&first, FALSE);
13367     if(gameInfo.variant == VariantUnknown && *oldName) {
13368         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13369         gameInfo.variant = v;
13370     }
13371     SendToProgram("force\n", &first);
13372     if (startedFromSetupPosition) {
13373         SendBoard(&first, forwardMostMove);
13374     if (appData.debugMode) {
13375         fprintf(debugFP, "Load Game\n");
13376     }
13377         DisplayBothClocks();
13378     }
13379   }
13380
13381     /* [HGM] server: flag to write setup moves in broadcast file as one */
13382     loadFlag = appData.suppressLoadMoves;
13383
13384     while (cm == Comment) {
13385         char *p;
13386         if (appData.debugMode)
13387           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13388         p = yy_text;
13389         AppendComment(currentMove, p, FALSE);
13390         yyboardindex = forwardMostMove;
13391         cm = (ChessMove) Myylex();
13392     }
13393
13394     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13395         cm == WhiteWins || cm == BlackWins ||
13396         cm == GameIsDrawn || cm == GameUnfinished) {
13397         DisplayMessage("", _("No moves in game"));
13398         if (cmailMsgLoaded) {
13399             if (appData.debugMode)
13400               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13401             ClearHighlights();
13402             flipView = FALSE;
13403         }
13404         DrawPosition(FALSE, boards[currentMove]);
13405         DisplayBothClocks();
13406         gameMode = EditGame;
13407         ModeHighlight();
13408         gameFileFP = NULL;
13409         cmailOldMove = 0;
13410         return TRUE;
13411     }
13412
13413     // [HGM] PV info: routine tests if comment empty
13414     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13415         DisplayComment(currentMove - 1, commentList[currentMove]);
13416     }
13417     if (!matchMode && appData.timeDelay != 0)
13418       DrawPosition(FALSE, boards[currentMove]);
13419
13420     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13421       programStats.ok_to_send = 1;
13422     }
13423
13424     /* if the first token after the PGN tags is a move
13425      * and not move number 1, retrieve it from the parser
13426      */
13427     if (cm != MoveNumberOne)
13428         LoadGameOneMove(cm);
13429
13430     /* load the remaining moves from the file */
13431     while (LoadGameOneMove(EndOfFile)) {
13432       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13433       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13434     }
13435
13436     /* rewind to the start of the game */
13437     currentMove = backwardMostMove;
13438
13439     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13440
13441     if (oldGameMode == AnalyzeFile) {
13442       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13443       AnalyzeFileEvent();
13444     } else
13445     if (oldGameMode == AnalyzeMode) {
13446       AnalyzeFileEvent();
13447     }
13448
13449     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13450         long int w, b; // [HGM] adjourn: restore saved clock times
13451         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13452         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13453             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13454             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13455         }
13456     }
13457
13458     if(creatingBook) return TRUE;
13459     if (!matchMode && pos > 0) {
13460         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13461     } else
13462     if (matchMode || appData.timeDelay == 0) {
13463       ToEndEvent();
13464     } else if (appData.timeDelay > 0) {
13465       AutoPlayGameLoop();
13466     }
13467
13468     if (appData.debugMode)
13469         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13470
13471     loadFlag = 0; /* [HGM] true game starts */
13472     return TRUE;
13473 }
13474
13475 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13476 int
13477 ReloadPosition (int offset)
13478 {
13479     int positionNumber = lastLoadPositionNumber + offset;
13480     if (lastLoadPositionFP == NULL) {
13481         DisplayError(_("No position has been loaded yet"), 0);
13482         return FALSE;
13483     }
13484     if (positionNumber <= 0) {
13485         DisplayError(_("Can't back up any further"), 0);
13486         return FALSE;
13487     }
13488     return LoadPosition(lastLoadPositionFP, positionNumber,
13489                         lastLoadPositionTitle);
13490 }
13491
13492 /* Load the nth position from the given file */
13493 int
13494 LoadPositionFromFile (char *filename, int n, char *title)
13495 {
13496     FILE *f;
13497     char buf[MSG_SIZ];
13498
13499     if (strcmp(filename, "-") == 0) {
13500         return LoadPosition(stdin, n, "stdin");
13501     } else {
13502         f = fopen(filename, "rb");
13503         if (f == NULL) {
13504             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13505             DisplayError(buf, errno);
13506             return FALSE;
13507         } else {
13508             return LoadPosition(f, n, title);
13509         }
13510     }
13511 }
13512
13513 /* Load the nth position from the given open file, and close it */
13514 int
13515 LoadPosition (FILE *f, int positionNumber, char *title)
13516 {
13517     char *p, line[MSG_SIZ];
13518     Board initial_position;
13519     int i, j, fenMode, pn;
13520
13521     if (gameMode == Training )
13522         SetTrainingModeOff();
13523
13524     if (gameMode != BeginningOfGame) {
13525         Reset(FALSE, TRUE);
13526     }
13527     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13528         fclose(lastLoadPositionFP);
13529     }
13530     if (positionNumber == 0) positionNumber = 1;
13531     lastLoadPositionFP = f;
13532     lastLoadPositionNumber = positionNumber;
13533     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13534     if (first.pr == NoProc && !appData.noChessProgram) {
13535       StartChessProgram(&first);
13536       InitChessProgram(&first, FALSE);
13537     }
13538     pn = positionNumber;
13539     if (positionNumber < 0) {
13540         /* Negative position number means to seek to that byte offset */
13541         if (fseek(f, -positionNumber, 0) == -1) {
13542             DisplayError(_("Can't seek on position file"), 0);
13543             return FALSE;
13544         };
13545         pn = 1;
13546     } else {
13547         if (fseek(f, 0, 0) == -1) {
13548             if (f == lastLoadPositionFP ?
13549                 positionNumber == lastLoadPositionNumber + 1 :
13550                 positionNumber == 1) {
13551                 pn = 1;
13552             } else {
13553                 DisplayError(_("Can't seek on position file"), 0);
13554                 return FALSE;
13555             }
13556         }
13557     }
13558     /* See if this file is FEN or old-style xboard */
13559     if (fgets(line, MSG_SIZ, f) == NULL) {
13560         DisplayError(_("Position not found in file"), 0);
13561         return FALSE;
13562     }
13563     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13564     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13565
13566     if (pn >= 2) {
13567         if (fenMode || line[0] == '#') pn--;
13568         while (pn > 0) {
13569             /* skip positions before number pn */
13570             if (fgets(line, MSG_SIZ, f) == NULL) {
13571                 Reset(TRUE, TRUE);
13572                 DisplayError(_("Position not found in file"), 0);
13573                 return FALSE;
13574             }
13575             if (fenMode || line[0] == '#') pn--;
13576         }
13577     }
13578
13579     if (fenMode) {
13580         char *p;
13581         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13582             DisplayError(_("Bad FEN position in file"), 0);
13583             return FALSE;
13584         }
13585         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13586             sscanf(p+4, "%[^;]", bestMove);
13587         } else *bestMove = NULLCHAR;
13588         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13589             sscanf(p+4, "%[^;]", avoidMove);
13590         } else *avoidMove = NULLCHAR;
13591     } else {
13592         (void) fgets(line, MSG_SIZ, f);
13593         (void) fgets(line, MSG_SIZ, f);
13594
13595         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13596             (void) fgets(line, MSG_SIZ, f);
13597             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13598                 if (*p == ' ')
13599                   continue;
13600                 initial_position[i][j++] = CharToPiece(*p);
13601             }
13602         }
13603
13604         blackPlaysFirst = FALSE;
13605         if (!feof(f)) {
13606             (void) fgets(line, MSG_SIZ, f);
13607             if (strncmp(line, "black", strlen("black"))==0)
13608               blackPlaysFirst = TRUE;
13609         }
13610     }
13611     startedFromSetupPosition = TRUE;
13612
13613     CopyBoard(boards[0], initial_position);
13614     if (blackPlaysFirst) {
13615         currentMove = forwardMostMove = backwardMostMove = 1;
13616         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13617         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13618         CopyBoard(boards[1], initial_position);
13619         DisplayMessage("", _("Black to play"));
13620     } else {
13621         currentMove = forwardMostMove = backwardMostMove = 0;
13622         DisplayMessage("", _("White to play"));
13623     }
13624     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13625     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13626         SendToProgram("force\n", &first);
13627         SendBoard(&first, forwardMostMove);
13628     }
13629     if (appData.debugMode) {
13630 int i, j;
13631   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13632   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13633         fprintf(debugFP, "Load Position\n");
13634     }
13635
13636     if (positionNumber > 1) {
13637       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13638         DisplayTitle(line);
13639     } else {
13640         DisplayTitle(title);
13641     }
13642     gameMode = EditGame;
13643     ModeHighlight();
13644     ResetClocks();
13645     timeRemaining[0][1] = whiteTimeRemaining;
13646     timeRemaining[1][1] = blackTimeRemaining;
13647     DrawPosition(FALSE, boards[currentMove]);
13648
13649     return TRUE;
13650 }
13651
13652
13653 void
13654 CopyPlayerNameIntoFileName (char **dest, char *src)
13655 {
13656     while (*src != NULLCHAR && *src != ',') {
13657         if (*src == ' ') {
13658             *(*dest)++ = '_';
13659             src++;
13660         } else {
13661             *(*dest)++ = *src++;
13662         }
13663     }
13664 }
13665
13666 char *
13667 DefaultFileName (char *ext)
13668 {
13669     static char def[MSG_SIZ];
13670     char *p;
13671
13672     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13673         p = def;
13674         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13675         *p++ = '-';
13676         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13677         *p++ = '.';
13678         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13679     } else {
13680         def[0] = NULLCHAR;
13681     }
13682     return def;
13683 }
13684
13685 /* Save the current game to the given file */
13686 int
13687 SaveGameToFile (char *filename, int append)
13688 {
13689     FILE *f;
13690     char buf[MSG_SIZ];
13691     int result, i, t,tot=0;
13692
13693     if (strcmp(filename, "-") == 0) {
13694         return SaveGame(stdout, 0, NULL);
13695     } else {
13696         for(i=0; i<10; i++) { // upto 10 tries
13697              f = fopen(filename, append ? "a" : "w");
13698              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13699              if(f || errno != 13) break;
13700              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13701              tot += t;
13702         }
13703         if (f == NULL) {
13704             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13705             DisplayError(buf, errno);
13706             return FALSE;
13707         } else {
13708             safeStrCpy(buf, lastMsg, MSG_SIZ);
13709             DisplayMessage(_("Waiting for access to save file"), "");
13710             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13711             DisplayMessage(_("Saving game"), "");
13712             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13713             result = SaveGame(f, 0, NULL);
13714             DisplayMessage(buf, "");
13715             return result;
13716         }
13717     }
13718 }
13719
13720 char *
13721 SavePart (char *str)
13722 {
13723     static char buf[MSG_SIZ];
13724     char *p;
13725
13726     p = strchr(str, ' ');
13727     if (p == NULL) return str;
13728     strncpy(buf, str, p - str);
13729     buf[p - str] = NULLCHAR;
13730     return buf;
13731 }
13732
13733 #define PGN_MAX_LINE 75
13734
13735 #define PGN_SIDE_WHITE  0
13736 #define PGN_SIDE_BLACK  1
13737
13738 static int
13739 FindFirstMoveOutOfBook (int side)
13740 {
13741     int result = -1;
13742
13743     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13744         int index = backwardMostMove;
13745         int has_book_hit = 0;
13746
13747         if( (index % 2) != side ) {
13748             index++;
13749         }
13750
13751         while( index < forwardMostMove ) {
13752             /* Check to see if engine is in book */
13753             int depth = pvInfoList[index].depth;
13754             int score = pvInfoList[index].score;
13755             int in_book = 0;
13756
13757             if( depth <= 2 ) {
13758                 in_book = 1;
13759             }
13760             else if( score == 0 && depth == 63 ) {
13761                 in_book = 1; /* Zappa */
13762             }
13763             else if( score == 2 && depth == 99 ) {
13764                 in_book = 1; /* Abrok */
13765             }
13766
13767             has_book_hit += in_book;
13768
13769             if( ! in_book ) {
13770                 result = index;
13771
13772                 break;
13773             }
13774
13775             index += 2;
13776         }
13777     }
13778
13779     return result;
13780 }
13781
13782 void
13783 GetOutOfBookInfo (char * buf)
13784 {
13785     int oob[2];
13786     int i;
13787     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13788
13789     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13790     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13791
13792     *buf = '\0';
13793
13794     if( oob[0] >= 0 || oob[1] >= 0 ) {
13795         for( i=0; i<2; i++ ) {
13796             int idx = oob[i];
13797
13798             if( idx >= 0 ) {
13799                 if( i > 0 && oob[0] >= 0 ) {
13800                     strcat( buf, "   " );
13801                 }
13802
13803                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13804                 sprintf( buf+strlen(buf), "%s%.2f",
13805                     pvInfoList[idx].score >= 0 ? "+" : "",
13806                     pvInfoList[idx].score / 100.0 );
13807             }
13808         }
13809     }
13810 }
13811
13812 /* Save game in PGN style */
13813 static void
13814 SaveGamePGN2 (FILE *f)
13815 {
13816     int i, offset, linelen, newblock;
13817 //    char *movetext;
13818     char numtext[32];
13819     int movelen, numlen, blank;
13820     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13821
13822     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13823
13824     PrintPGNTags(f, &gameInfo);
13825
13826     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13827
13828     if (backwardMostMove > 0 || startedFromSetupPosition) {
13829         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13830         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13831         fprintf(f, "\n{--------------\n");
13832         PrintPosition(f, backwardMostMove);
13833         fprintf(f, "--------------}\n");
13834         free(fen);
13835     }
13836     else {
13837         /* [AS] Out of book annotation */
13838         if( appData.saveOutOfBookInfo ) {
13839             char buf[64];
13840
13841             GetOutOfBookInfo( buf );
13842
13843             if( buf[0] != '\0' ) {
13844                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13845             }
13846         }
13847
13848         fprintf(f, "\n");
13849     }
13850
13851     i = backwardMostMove;
13852     linelen = 0;
13853     newblock = TRUE;
13854
13855     while (i < forwardMostMove) {
13856         /* Print comments preceding this move */
13857         if (commentList[i] != NULL) {
13858             if (linelen > 0) fprintf(f, "\n");
13859             fprintf(f, "%s", commentList[i]);
13860             linelen = 0;
13861             newblock = TRUE;
13862         }
13863
13864         /* Format move number */
13865         if ((i % 2) == 0)
13866           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13867         else
13868           if (newblock)
13869             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13870           else
13871             numtext[0] = NULLCHAR;
13872
13873         numlen = strlen(numtext);
13874         newblock = FALSE;
13875
13876         /* Print move number */
13877         blank = linelen > 0 && numlen > 0;
13878         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13879             fprintf(f, "\n");
13880             linelen = 0;
13881             blank = 0;
13882         }
13883         if (blank) {
13884             fprintf(f, " ");
13885             linelen++;
13886         }
13887         fprintf(f, "%s", numtext);
13888         linelen += numlen;
13889
13890         /* Get move */
13891         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13892         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13893
13894         /* Print move */
13895         blank = linelen > 0 && movelen > 0;
13896         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13897             fprintf(f, "\n");
13898             linelen = 0;
13899             blank = 0;
13900         }
13901         if (blank) {
13902             fprintf(f, " ");
13903             linelen++;
13904         }
13905         fprintf(f, "%s", move_buffer);
13906         linelen += movelen;
13907
13908         /* [AS] Add PV info if present */
13909         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13910             /* [HGM] add time */
13911             char buf[MSG_SIZ]; int seconds;
13912
13913             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13914
13915             if( seconds <= 0)
13916               buf[0] = 0;
13917             else
13918               if( seconds < 30 )
13919                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13920               else
13921                 {
13922                   seconds = (seconds + 4)/10; // round to full seconds
13923                   if( seconds < 60 )
13924                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13925                   else
13926                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13927                 }
13928
13929             if(appData.cumulativeTimePGN) {
13930                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
13931             }
13932
13933             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13934                       pvInfoList[i].score >= 0 ? "+" : "",
13935                       pvInfoList[i].score / 100.0,
13936                       pvInfoList[i].depth,
13937                       buf );
13938
13939             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13940
13941             /* Print score/depth */
13942             blank = linelen > 0 && movelen > 0;
13943             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13944                 fprintf(f, "\n");
13945                 linelen = 0;
13946                 blank = 0;
13947             }
13948             if (blank) {
13949                 fprintf(f, " ");
13950                 linelen++;
13951             }
13952             fprintf(f, "%s", move_buffer);
13953             linelen += movelen;
13954         }
13955
13956         i++;
13957     }
13958
13959     /* Start a new line */
13960     if (linelen > 0) fprintf(f, "\n");
13961
13962     /* Print comments after last move */
13963     if (commentList[i] != NULL) {
13964         fprintf(f, "%s\n", commentList[i]);
13965     }
13966
13967     /* Print result */
13968     if (gameInfo.resultDetails != NULL &&
13969         gameInfo.resultDetails[0] != NULLCHAR) {
13970         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13971         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13972            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13973             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13974         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13975     } else {
13976         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13977     }
13978 }
13979
13980 /* Save game in PGN style and close the file */
13981 int
13982 SaveGamePGN (FILE *f)
13983 {
13984     SaveGamePGN2(f);
13985     fclose(f);
13986     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13987     return TRUE;
13988 }
13989
13990 /* Save game in old style and close the file */
13991 int
13992 SaveGameOldStyle (FILE *f)
13993 {
13994     int i, offset;
13995     time_t tm;
13996
13997     tm = time((time_t *) NULL);
13998
13999     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14000     PrintOpponents(f);
14001
14002     if (backwardMostMove > 0 || startedFromSetupPosition) {
14003         fprintf(f, "\n[--------------\n");
14004         PrintPosition(f, backwardMostMove);
14005         fprintf(f, "--------------]\n");
14006     } else {
14007         fprintf(f, "\n");
14008     }
14009
14010     i = backwardMostMove;
14011     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14012
14013     while (i < forwardMostMove) {
14014         if (commentList[i] != NULL) {
14015             fprintf(f, "[%s]\n", commentList[i]);
14016         }
14017
14018         if ((i % 2) == 1) {
14019             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14020             i++;
14021         } else {
14022             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14023             i++;
14024             if (commentList[i] != NULL) {
14025                 fprintf(f, "\n");
14026                 continue;
14027             }
14028             if (i >= forwardMostMove) {
14029                 fprintf(f, "\n");
14030                 break;
14031             }
14032             fprintf(f, "%s\n", parseList[i]);
14033             i++;
14034         }
14035     }
14036
14037     if (commentList[i] != NULL) {
14038         fprintf(f, "[%s]\n", commentList[i]);
14039     }
14040
14041     /* This isn't really the old style, but it's close enough */
14042     if (gameInfo.resultDetails != NULL &&
14043         gameInfo.resultDetails[0] != NULLCHAR) {
14044         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14045                 gameInfo.resultDetails);
14046     } else {
14047         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14048     }
14049
14050     fclose(f);
14051     return TRUE;
14052 }
14053
14054 /* Save the current game to open file f and close the file */
14055 int
14056 SaveGame (FILE *f, int dummy, char *dummy2)
14057 {
14058     if (gameMode == EditPosition) EditPositionDone(TRUE);
14059     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14060     if (appData.oldSaveStyle)
14061       return SaveGameOldStyle(f);
14062     else
14063       return SaveGamePGN(f);
14064 }
14065
14066 /* Save the current position to the given file */
14067 int
14068 SavePositionToFile (char *filename)
14069 {
14070     FILE *f;
14071     char buf[MSG_SIZ];
14072
14073     if (strcmp(filename, "-") == 0) {
14074         return SavePosition(stdout, 0, NULL);
14075     } else {
14076         f = fopen(filename, "a");
14077         if (f == NULL) {
14078             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14079             DisplayError(buf, errno);
14080             return FALSE;
14081         } else {
14082             safeStrCpy(buf, lastMsg, MSG_SIZ);
14083             DisplayMessage(_("Waiting for access to save file"), "");
14084             flock(fileno(f), LOCK_EX); // [HGM] lock
14085             DisplayMessage(_("Saving position"), "");
14086             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14087             SavePosition(f, 0, NULL);
14088             DisplayMessage(buf, "");
14089             return TRUE;
14090         }
14091     }
14092 }
14093
14094 /* Save the current position to the given open file and close the file */
14095 int
14096 SavePosition (FILE *f, int dummy, char *dummy2)
14097 {
14098     time_t tm;
14099     char *fen;
14100
14101     if (gameMode == EditPosition) EditPositionDone(TRUE);
14102     if (appData.oldSaveStyle) {
14103         tm = time((time_t *) NULL);
14104
14105         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14106         PrintOpponents(f);
14107         fprintf(f, "[--------------\n");
14108         PrintPosition(f, currentMove);
14109         fprintf(f, "--------------]\n");
14110     } else {
14111         fen = PositionToFEN(currentMove, NULL, 1);
14112         fprintf(f, "%s\n", fen);
14113         free(fen);
14114     }
14115     fclose(f);
14116     return TRUE;
14117 }
14118
14119 void
14120 ReloadCmailMsgEvent (int unregister)
14121 {
14122 #if !WIN32
14123     static char *inFilename = NULL;
14124     static char *outFilename;
14125     int i;
14126     struct stat inbuf, outbuf;
14127     int status;
14128
14129     /* Any registered moves are unregistered if unregister is set, */
14130     /* i.e. invoked by the signal handler */
14131     if (unregister) {
14132         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14133             cmailMoveRegistered[i] = FALSE;
14134             if (cmailCommentList[i] != NULL) {
14135                 free(cmailCommentList[i]);
14136                 cmailCommentList[i] = NULL;
14137             }
14138         }
14139         nCmailMovesRegistered = 0;
14140     }
14141
14142     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14143         cmailResult[i] = CMAIL_NOT_RESULT;
14144     }
14145     nCmailResults = 0;
14146
14147     if (inFilename == NULL) {
14148         /* Because the filenames are static they only get malloced once  */
14149         /* and they never get freed                                      */
14150         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14151         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14152
14153         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14154         sprintf(outFilename, "%s.out", appData.cmailGameName);
14155     }
14156
14157     status = stat(outFilename, &outbuf);
14158     if (status < 0) {
14159         cmailMailedMove = FALSE;
14160     } else {
14161         status = stat(inFilename, &inbuf);
14162         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14163     }
14164
14165     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14166        counts the games, notes how each one terminated, etc.
14167
14168        It would be nice to remove this kludge and instead gather all
14169        the information while building the game list.  (And to keep it
14170        in the game list nodes instead of having a bunch of fixed-size
14171        parallel arrays.)  Note this will require getting each game's
14172        termination from the PGN tags, as the game list builder does
14173        not process the game moves.  --mann
14174        */
14175     cmailMsgLoaded = TRUE;
14176     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14177
14178     /* Load first game in the file or popup game menu */
14179     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14180
14181 #endif /* !WIN32 */
14182     return;
14183 }
14184
14185 int
14186 RegisterMove ()
14187 {
14188     FILE *f;
14189     char string[MSG_SIZ];
14190
14191     if (   cmailMailedMove
14192         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14193         return TRUE;            /* Allow free viewing  */
14194     }
14195
14196     /* Unregister move to ensure that we don't leave RegisterMove        */
14197     /* with the move registered when the conditions for registering no   */
14198     /* longer hold                                                       */
14199     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14200         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14201         nCmailMovesRegistered --;
14202
14203         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14204           {
14205               free(cmailCommentList[lastLoadGameNumber - 1]);
14206               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14207           }
14208     }
14209
14210     if (cmailOldMove == -1) {
14211         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14212         return FALSE;
14213     }
14214
14215     if (currentMove > cmailOldMove + 1) {
14216         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14217         return FALSE;
14218     }
14219
14220     if (currentMove < cmailOldMove) {
14221         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14222         return FALSE;
14223     }
14224
14225     if (forwardMostMove > currentMove) {
14226         /* Silently truncate extra moves */
14227         TruncateGame();
14228     }
14229
14230     if (   (currentMove == cmailOldMove + 1)
14231         || (   (currentMove == cmailOldMove)
14232             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14233                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14234         if (gameInfo.result != GameUnfinished) {
14235             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14236         }
14237
14238         if (commentList[currentMove] != NULL) {
14239             cmailCommentList[lastLoadGameNumber - 1]
14240               = StrSave(commentList[currentMove]);
14241         }
14242         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14243
14244         if (appData.debugMode)
14245           fprintf(debugFP, "Saving %s for game %d\n",
14246                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14247
14248         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14249
14250         f = fopen(string, "w");
14251         if (appData.oldSaveStyle) {
14252             SaveGameOldStyle(f); /* also closes the file */
14253
14254             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14255             f = fopen(string, "w");
14256             SavePosition(f, 0, NULL); /* also closes the file */
14257         } else {
14258             fprintf(f, "{--------------\n");
14259             PrintPosition(f, currentMove);
14260             fprintf(f, "--------------}\n\n");
14261
14262             SaveGame(f, 0, NULL); /* also closes the file*/
14263         }
14264
14265         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14266         nCmailMovesRegistered ++;
14267     } else if (nCmailGames == 1) {
14268         DisplayError(_("You have not made a move yet"), 0);
14269         return FALSE;
14270     }
14271
14272     return TRUE;
14273 }
14274
14275 void
14276 MailMoveEvent ()
14277 {
14278 #if !WIN32
14279     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14280     FILE *commandOutput;
14281     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14282     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14283     int nBuffers;
14284     int i;
14285     int archived;
14286     char *arcDir;
14287
14288     if (! cmailMsgLoaded) {
14289         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14290         return;
14291     }
14292
14293     if (nCmailGames == nCmailResults) {
14294         DisplayError(_("No unfinished games"), 0);
14295         return;
14296     }
14297
14298 #if CMAIL_PROHIBIT_REMAIL
14299     if (cmailMailedMove) {
14300       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);
14301         DisplayError(msg, 0);
14302         return;
14303     }
14304 #endif
14305
14306     if (! (cmailMailedMove || RegisterMove())) return;
14307
14308     if (   cmailMailedMove
14309         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14310       snprintf(string, MSG_SIZ, partCommandString,
14311                appData.debugMode ? " -v" : "", appData.cmailGameName);
14312         commandOutput = popen(string, "r");
14313
14314         if (commandOutput == NULL) {
14315             DisplayError(_("Failed to invoke cmail"), 0);
14316         } else {
14317             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14318                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14319             }
14320             if (nBuffers > 1) {
14321                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14322                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14323                 nBytes = MSG_SIZ - 1;
14324             } else {
14325                 (void) memcpy(msg, buffer, nBytes);
14326             }
14327             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14328
14329             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14330                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14331
14332                 archived = TRUE;
14333                 for (i = 0; i < nCmailGames; i ++) {
14334                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14335                         archived = FALSE;
14336                     }
14337                 }
14338                 if (   archived
14339                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14340                         != NULL)) {
14341                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14342                            arcDir,
14343                            appData.cmailGameName,
14344                            gameInfo.date);
14345                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14346                     cmailMsgLoaded = FALSE;
14347                 }
14348             }
14349
14350             DisplayInformation(msg);
14351             pclose(commandOutput);
14352         }
14353     } else {
14354         if ((*cmailMsg) != '\0') {
14355             DisplayInformation(cmailMsg);
14356         }
14357     }
14358
14359     return;
14360 #endif /* !WIN32 */
14361 }
14362
14363 char *
14364 CmailMsg ()
14365 {
14366 #if WIN32
14367     return NULL;
14368 #else
14369     int  prependComma = 0;
14370     char number[5];
14371     char string[MSG_SIZ];       /* Space for game-list */
14372     int  i;
14373
14374     if (!cmailMsgLoaded) return "";
14375
14376     if (cmailMailedMove) {
14377       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14378     } else {
14379         /* Create a list of games left */
14380       snprintf(string, MSG_SIZ, "[");
14381         for (i = 0; i < nCmailGames; i ++) {
14382             if (! (   cmailMoveRegistered[i]
14383                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14384                 if (prependComma) {
14385                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14386                 } else {
14387                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14388                     prependComma = 1;
14389                 }
14390
14391                 strcat(string, number);
14392             }
14393         }
14394         strcat(string, "]");
14395
14396         if (nCmailMovesRegistered + nCmailResults == 0) {
14397             switch (nCmailGames) {
14398               case 1:
14399                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14400                 break;
14401
14402               case 2:
14403                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14404                 break;
14405
14406               default:
14407                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14408                          nCmailGames);
14409                 break;
14410             }
14411         } else {
14412             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14413               case 1:
14414                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14415                          string);
14416                 break;
14417
14418               case 0:
14419                 if (nCmailResults == nCmailGames) {
14420                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14421                 } else {
14422                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14423                 }
14424                 break;
14425
14426               default:
14427                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14428                          string);
14429             }
14430         }
14431     }
14432     return cmailMsg;
14433 #endif /* WIN32 */
14434 }
14435
14436 void
14437 ResetGameEvent ()
14438 {
14439     if (gameMode == Training)
14440       SetTrainingModeOff();
14441
14442     Reset(TRUE, TRUE);
14443     cmailMsgLoaded = FALSE;
14444     if (appData.icsActive) {
14445       SendToICS(ics_prefix);
14446       SendToICS("refresh\n");
14447     }
14448 }
14449
14450 void
14451 ExitEvent (int status)
14452 {
14453     exiting++;
14454     if (exiting > 2) {
14455       /* Give up on clean exit */
14456       exit(status);
14457     }
14458     if (exiting > 1) {
14459       /* Keep trying for clean exit */
14460       return;
14461     }
14462
14463     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14464     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14465
14466     if (telnetISR != NULL) {
14467       RemoveInputSource(telnetISR);
14468     }
14469     if (icsPR != NoProc) {
14470       DestroyChildProcess(icsPR, TRUE);
14471     }
14472
14473     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14474     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14475
14476     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14477     /* make sure this other one finishes before killing it!                  */
14478     if(endingGame) { int count = 0;
14479         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14480         while(endingGame && count++ < 10) DoSleep(1);
14481         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14482     }
14483
14484     /* Kill off chess programs */
14485     if (first.pr != NoProc) {
14486         ExitAnalyzeMode();
14487
14488         DoSleep( appData.delayBeforeQuit );
14489         SendToProgram("quit\n", &first);
14490         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14491     }
14492     if (second.pr != NoProc) {
14493         DoSleep( appData.delayBeforeQuit );
14494         SendToProgram("quit\n", &second);
14495         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14496     }
14497     if (first.isr != NULL) {
14498         RemoveInputSource(first.isr);
14499     }
14500     if (second.isr != NULL) {
14501         RemoveInputSource(second.isr);
14502     }
14503
14504     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14505     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14506
14507     ShutDownFrontEnd();
14508     exit(status);
14509 }
14510
14511 void
14512 PauseEngine (ChessProgramState *cps)
14513 {
14514     SendToProgram("pause\n", cps);
14515     cps->pause = 2;
14516 }
14517
14518 void
14519 UnPauseEngine (ChessProgramState *cps)
14520 {
14521     SendToProgram("resume\n", cps);
14522     cps->pause = 1;
14523 }
14524
14525 void
14526 PauseEvent ()
14527 {
14528     if (appData.debugMode)
14529         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14530     if (pausing) {
14531         pausing = FALSE;
14532         ModeHighlight();
14533         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14534             StartClocks();
14535             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14536                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14537                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14538             }
14539             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14540             HandleMachineMove(stashedInputMove, stalledEngine);
14541             stalledEngine = NULL;
14542             return;
14543         }
14544         if (gameMode == MachinePlaysWhite ||
14545             gameMode == TwoMachinesPlay   ||
14546             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14547             if(first.pause)  UnPauseEngine(&first);
14548             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14549             if(second.pause) UnPauseEngine(&second);
14550             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14551             StartClocks();
14552         } else {
14553             DisplayBothClocks();
14554         }
14555         if (gameMode == PlayFromGameFile) {
14556             if (appData.timeDelay >= 0)
14557                 AutoPlayGameLoop();
14558         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14559             Reset(FALSE, TRUE);
14560             SendToICS(ics_prefix);
14561             SendToICS("refresh\n");
14562         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14563             ForwardInner(forwardMostMove);
14564         }
14565         pauseExamInvalid = FALSE;
14566     } else {
14567         switch (gameMode) {
14568           default:
14569             return;
14570           case IcsExamining:
14571             pauseExamForwardMostMove = forwardMostMove;
14572             pauseExamInvalid = FALSE;
14573             /* fall through */
14574           case IcsObserving:
14575           case IcsPlayingWhite:
14576           case IcsPlayingBlack:
14577             pausing = TRUE;
14578             ModeHighlight();
14579             return;
14580           case PlayFromGameFile:
14581             (void) StopLoadGameTimer();
14582             pausing = TRUE;
14583             ModeHighlight();
14584             break;
14585           case BeginningOfGame:
14586             if (appData.icsActive) return;
14587             /* else fall through */
14588           case MachinePlaysWhite:
14589           case MachinePlaysBlack:
14590           case TwoMachinesPlay:
14591             if (forwardMostMove == 0)
14592               return;           /* don't pause if no one has moved */
14593             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14594                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14595                 if(onMove->pause) {           // thinking engine can be paused
14596                     PauseEngine(onMove);      // do it
14597                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14598                         PauseEngine(onMove->other);
14599                     else
14600                         SendToProgram("easy\n", onMove->other);
14601                     StopClocks();
14602                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14603             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14604                 if(first.pause) {
14605                     PauseEngine(&first);
14606                     StopClocks();
14607                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14608             } else { // human on move, pause pondering by either method
14609                 if(first.pause)
14610                     PauseEngine(&first);
14611                 else if(appData.ponderNextMove)
14612                     SendToProgram("easy\n", &first);
14613                 StopClocks();
14614             }
14615             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14616           case AnalyzeMode:
14617             pausing = TRUE;
14618             ModeHighlight();
14619             break;
14620         }
14621     }
14622 }
14623
14624 void
14625 EditCommentEvent ()
14626 {
14627     char title[MSG_SIZ];
14628
14629     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14630       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14631     } else {
14632       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14633                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14634                parseList[currentMove - 1]);
14635     }
14636
14637     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14638 }
14639
14640
14641 void
14642 EditTagsEvent ()
14643 {
14644     char *tags = PGNTags(&gameInfo);
14645     bookUp = FALSE;
14646     EditTagsPopUp(tags, NULL);
14647     free(tags);
14648 }
14649
14650 void
14651 ToggleSecond ()
14652 {
14653   if(second.analyzing) {
14654     SendToProgram("exit\n", &second);
14655     second.analyzing = FALSE;
14656   } else {
14657     if (second.pr == NoProc) StartChessProgram(&second);
14658     InitChessProgram(&second, FALSE);
14659     FeedMovesToProgram(&second, currentMove);
14660
14661     SendToProgram("analyze\n", &second);
14662     second.analyzing = TRUE;
14663   }
14664 }
14665
14666 /* Toggle ShowThinking */
14667 void
14668 ToggleShowThinking()
14669 {
14670   appData.showThinking = !appData.showThinking;
14671   ShowThinkingEvent();
14672 }
14673
14674 int
14675 AnalyzeModeEvent ()
14676 {
14677     char buf[MSG_SIZ];
14678
14679     if (!first.analysisSupport) {
14680       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14681       DisplayError(buf, 0);
14682       return 0;
14683     }
14684     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14685     if (appData.icsActive) {
14686         if (gameMode != IcsObserving) {
14687           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14688             DisplayError(buf, 0);
14689             /* secure check */
14690             if (appData.icsEngineAnalyze) {
14691                 if (appData.debugMode)
14692                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14693                 ExitAnalyzeMode();
14694                 ModeHighlight();
14695             }
14696             return 0;
14697         }
14698         /* if enable, user wants to disable icsEngineAnalyze */
14699         if (appData.icsEngineAnalyze) {
14700                 ExitAnalyzeMode();
14701                 ModeHighlight();
14702                 return 0;
14703         }
14704         appData.icsEngineAnalyze = TRUE;
14705         if (appData.debugMode)
14706             fprintf(debugFP, "ICS engine analyze starting... \n");
14707     }
14708
14709     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14710     if (appData.noChessProgram || gameMode == AnalyzeMode)
14711       return 0;
14712
14713     if (gameMode != AnalyzeFile) {
14714         if (!appData.icsEngineAnalyze) {
14715                EditGameEvent();
14716                if (gameMode != EditGame) return 0;
14717         }
14718         if (!appData.showThinking) ToggleShowThinking();
14719         ResurrectChessProgram();
14720         SendToProgram("analyze\n", &first);
14721         first.analyzing = TRUE;
14722         /*first.maybeThinking = TRUE;*/
14723         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14724         EngineOutputPopUp();
14725     }
14726     if (!appData.icsEngineAnalyze) {
14727         gameMode = AnalyzeMode;
14728         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14729     }
14730     pausing = FALSE;
14731     ModeHighlight();
14732     SetGameInfo();
14733
14734     StartAnalysisClock();
14735     GetTimeMark(&lastNodeCountTime);
14736     lastNodeCount = 0;
14737     return 1;
14738 }
14739
14740 void
14741 AnalyzeFileEvent ()
14742 {
14743     if (appData.noChessProgram || gameMode == AnalyzeFile)
14744       return;
14745
14746     if (!first.analysisSupport) {
14747       char buf[MSG_SIZ];
14748       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14749       DisplayError(buf, 0);
14750       return;
14751     }
14752
14753     if (gameMode != AnalyzeMode) {
14754         keepInfo = 1; // mere annotating should not alter PGN tags
14755         EditGameEvent();
14756         keepInfo = 0;
14757         if (gameMode != EditGame) return;
14758         if (!appData.showThinking) ToggleShowThinking();
14759         ResurrectChessProgram();
14760         SendToProgram("analyze\n", &first);
14761         first.analyzing = TRUE;
14762         /*first.maybeThinking = TRUE;*/
14763         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14764         EngineOutputPopUp();
14765     }
14766     gameMode = AnalyzeFile;
14767     pausing = FALSE;
14768     ModeHighlight();
14769
14770     StartAnalysisClock();
14771     GetTimeMark(&lastNodeCountTime);
14772     lastNodeCount = 0;
14773     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14774     AnalysisPeriodicEvent(1);
14775 }
14776
14777 void
14778 MachineWhiteEvent ()
14779 {
14780     char buf[MSG_SIZ];
14781     char *bookHit = NULL;
14782
14783     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14784       return;
14785
14786
14787     if (gameMode == PlayFromGameFile ||
14788         gameMode == TwoMachinesPlay  ||
14789         gameMode == Training         ||
14790         gameMode == AnalyzeMode      ||
14791         gameMode == EndOfGame)
14792         EditGameEvent();
14793
14794     if (gameMode == EditPosition)
14795         EditPositionDone(TRUE);
14796
14797     if (!WhiteOnMove(currentMove)) {
14798         DisplayError(_("It is not White's turn"), 0);
14799         return;
14800     }
14801
14802     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14803       ExitAnalyzeMode();
14804
14805     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14806         gameMode == AnalyzeFile)
14807         TruncateGame();
14808
14809     ResurrectChessProgram();    /* in case it isn't running */
14810     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14811         gameMode = MachinePlaysWhite;
14812         ResetClocks();
14813     } else
14814     gameMode = MachinePlaysWhite;
14815     pausing = FALSE;
14816     ModeHighlight();
14817     SetGameInfo();
14818     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14819     DisplayTitle(buf);
14820     if (first.sendName) {
14821       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14822       SendToProgram(buf, &first);
14823     }
14824     if (first.sendTime) {
14825       if (first.useColors) {
14826         SendToProgram("black\n", &first); /*gnu kludge*/
14827       }
14828       SendTimeRemaining(&first, TRUE);
14829     }
14830     if (first.useColors) {
14831       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14832     }
14833     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14834     SetMachineThinkingEnables();
14835     first.maybeThinking = TRUE;
14836     StartClocks();
14837     firstMove = FALSE;
14838
14839     if (appData.autoFlipView && !flipView) {
14840       flipView = !flipView;
14841       DrawPosition(FALSE, NULL);
14842       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14843     }
14844
14845     if(bookHit) { // [HGM] book: simulate book reply
14846         static char bookMove[MSG_SIZ]; // a bit generous?
14847
14848         programStats.nodes = programStats.depth = programStats.time =
14849         programStats.score = programStats.got_only_move = 0;
14850         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14851
14852         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14853         strcat(bookMove, bookHit);
14854         HandleMachineMove(bookMove, &first);
14855     }
14856 }
14857
14858 void
14859 MachineBlackEvent ()
14860 {
14861   char buf[MSG_SIZ];
14862   char *bookHit = NULL;
14863
14864     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14865         return;
14866
14867
14868     if (gameMode == PlayFromGameFile ||
14869         gameMode == TwoMachinesPlay  ||
14870         gameMode == Training         ||
14871         gameMode == AnalyzeMode      ||
14872         gameMode == EndOfGame)
14873         EditGameEvent();
14874
14875     if (gameMode == EditPosition)
14876         EditPositionDone(TRUE);
14877
14878     if (WhiteOnMove(currentMove)) {
14879         DisplayError(_("It is not Black's turn"), 0);
14880         return;
14881     }
14882
14883     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14884       ExitAnalyzeMode();
14885
14886     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14887         gameMode == AnalyzeFile)
14888         TruncateGame();
14889
14890     ResurrectChessProgram();    /* in case it isn't running */
14891     gameMode = MachinePlaysBlack;
14892     pausing = FALSE;
14893     ModeHighlight();
14894     SetGameInfo();
14895     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14896     DisplayTitle(buf);
14897     if (first.sendName) {
14898       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14899       SendToProgram(buf, &first);
14900     }
14901     if (first.sendTime) {
14902       if (first.useColors) {
14903         SendToProgram("white\n", &first); /*gnu kludge*/
14904       }
14905       SendTimeRemaining(&first, FALSE);
14906     }
14907     if (first.useColors) {
14908       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14909     }
14910     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14911     SetMachineThinkingEnables();
14912     first.maybeThinking = TRUE;
14913     StartClocks();
14914
14915     if (appData.autoFlipView && flipView) {
14916       flipView = !flipView;
14917       DrawPosition(FALSE, NULL);
14918       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14919     }
14920     if(bookHit) { // [HGM] book: simulate book reply
14921         static char bookMove[MSG_SIZ]; // a bit generous?
14922
14923         programStats.nodes = programStats.depth = programStats.time =
14924         programStats.score = programStats.got_only_move = 0;
14925         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14926
14927         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14928         strcat(bookMove, bookHit);
14929         HandleMachineMove(bookMove, &first);
14930     }
14931 }
14932
14933
14934 void
14935 DisplayTwoMachinesTitle ()
14936 {
14937     char buf[MSG_SIZ];
14938     if (appData.matchGames > 0) {
14939         if(appData.tourneyFile[0]) {
14940           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14941                    gameInfo.white, _("vs."), gameInfo.black,
14942                    nextGame+1, appData.matchGames+1,
14943                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14944         } else
14945         if (first.twoMachinesColor[0] == 'w') {
14946           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14947                    gameInfo.white, _("vs."),  gameInfo.black,
14948                    first.matchWins, second.matchWins,
14949                    matchGame - 1 - (first.matchWins + second.matchWins));
14950         } else {
14951           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14952                    gameInfo.white, _("vs."), gameInfo.black,
14953                    second.matchWins, first.matchWins,
14954                    matchGame - 1 - (first.matchWins + second.matchWins));
14955         }
14956     } else {
14957       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14958     }
14959     DisplayTitle(buf);
14960 }
14961
14962 void
14963 SettingsMenuIfReady ()
14964 {
14965   if (second.lastPing != second.lastPong) {
14966     DisplayMessage("", _("Waiting for second chess program"));
14967     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14968     return;
14969   }
14970   ThawUI();
14971   DisplayMessage("", "");
14972   SettingsPopUp(&second);
14973 }
14974
14975 int
14976 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14977 {
14978     char buf[MSG_SIZ];
14979     if (cps->pr == NoProc) {
14980         StartChessProgram(cps);
14981         if (cps->protocolVersion == 1) {
14982           retry();
14983           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14984         } else {
14985           /* kludge: allow timeout for initial "feature" command */
14986           if(retry != TwoMachinesEventIfReady) FreezeUI();
14987           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14988           DisplayMessage("", buf);
14989           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14990         }
14991         return 1;
14992     }
14993     return 0;
14994 }
14995
14996 void
14997 TwoMachinesEvent P((void))
14998 {
14999     int i;
15000     char buf[MSG_SIZ];
15001     ChessProgramState *onmove;
15002     char *bookHit = NULL;
15003     static int stalling = 0;
15004     TimeMark now;
15005     long wait;
15006
15007     if (appData.noChessProgram) return;
15008
15009     switch (gameMode) {
15010       case TwoMachinesPlay:
15011         return;
15012       case MachinePlaysWhite:
15013       case MachinePlaysBlack:
15014         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15015             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15016             return;
15017         }
15018         /* fall through */
15019       case BeginningOfGame:
15020       case PlayFromGameFile:
15021       case EndOfGame:
15022         EditGameEvent();
15023         if (gameMode != EditGame) return;
15024         break;
15025       case EditPosition:
15026         EditPositionDone(TRUE);
15027         break;
15028       case AnalyzeMode:
15029       case AnalyzeFile:
15030         ExitAnalyzeMode();
15031         break;
15032       case EditGame:
15033       default:
15034         break;
15035     }
15036
15037 //    forwardMostMove = currentMove;
15038     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15039     startingEngine = TRUE;
15040
15041     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15042
15043     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15044     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15045       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15046       return;
15047     }
15048   if(!appData.epd) {
15049     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15050
15051     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15052                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15053         startingEngine = matchMode = FALSE;
15054         DisplayError("second engine does not play this", 0);
15055         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15056         EditGameEvent(); // switch back to EditGame mode
15057         return;
15058     }
15059
15060     if(!stalling) {
15061       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15062       SendToProgram("force\n", &second);
15063       stalling = 1;
15064       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15065       return;
15066     }
15067   }
15068     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15069     if(appData.matchPause>10000 || appData.matchPause<10)
15070                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15071     wait = SubtractTimeMarks(&now, &pauseStart);
15072     if(wait < appData.matchPause) {
15073         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15074         return;
15075     }
15076     // we are now committed to starting the game
15077     stalling = 0;
15078     DisplayMessage("", "");
15079   if(!appData.epd) {
15080     if (startedFromSetupPosition) {
15081         SendBoard(&second, backwardMostMove);
15082     if (appData.debugMode) {
15083         fprintf(debugFP, "Two Machines\n");
15084     }
15085     }
15086     for (i = backwardMostMove; i < forwardMostMove; i++) {
15087         SendMoveToProgram(i, &second);
15088     }
15089   }
15090
15091     gameMode = TwoMachinesPlay;
15092     pausing = startingEngine = FALSE;
15093     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15094     SetGameInfo();
15095     DisplayTwoMachinesTitle();
15096     firstMove = TRUE;
15097     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15098         onmove = &first;
15099     } else {
15100         onmove = &second;
15101     }
15102     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15103     SendToProgram(first.computerString, &first);
15104     if (first.sendName) {
15105       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15106       SendToProgram(buf, &first);
15107     }
15108   if(!appData.epd) {
15109     SendToProgram(second.computerString, &second);
15110     if (second.sendName) {
15111       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15112       SendToProgram(buf, &second);
15113     }
15114   }
15115
15116     ResetClocks();
15117     if (!first.sendTime || !second.sendTime) {
15118         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15119         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15120     }
15121     if (onmove->sendTime) {
15122       if (onmove->useColors) {
15123         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15124       }
15125       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15126     }
15127     if (onmove->useColors) {
15128       SendToProgram(onmove->twoMachinesColor, onmove);
15129     }
15130     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15131 //    SendToProgram("go\n", onmove);
15132     onmove->maybeThinking = TRUE;
15133     SetMachineThinkingEnables();
15134
15135     StartClocks();
15136
15137     if(bookHit) { // [HGM] book: simulate book reply
15138         static char bookMove[MSG_SIZ]; // a bit generous?
15139
15140         programStats.nodes = programStats.depth = programStats.time =
15141         programStats.score = programStats.got_only_move = 0;
15142         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15143
15144         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15145         strcat(bookMove, bookHit);
15146         savedMessage = bookMove; // args for deferred call
15147         savedState = onmove;
15148         ScheduleDelayedEvent(DeferredBookMove, 1);
15149     }
15150 }
15151
15152 void
15153 TrainingEvent ()
15154 {
15155     if (gameMode == Training) {
15156       SetTrainingModeOff();
15157       gameMode = PlayFromGameFile;
15158       DisplayMessage("", _("Training mode off"));
15159     } else {
15160       gameMode = Training;
15161       animateTraining = appData.animate;
15162
15163       /* make sure we are not already at the end of the game */
15164       if (currentMove < forwardMostMove) {
15165         SetTrainingModeOn();
15166         DisplayMessage("", _("Training mode on"));
15167       } else {
15168         gameMode = PlayFromGameFile;
15169         DisplayError(_("Already at end of game"), 0);
15170       }
15171     }
15172     ModeHighlight();
15173 }
15174
15175 void
15176 IcsClientEvent ()
15177 {
15178     if (!appData.icsActive) return;
15179     switch (gameMode) {
15180       case IcsPlayingWhite:
15181       case IcsPlayingBlack:
15182       case IcsObserving:
15183       case IcsIdle:
15184       case BeginningOfGame:
15185       case IcsExamining:
15186         return;
15187
15188       case EditGame:
15189         break;
15190
15191       case EditPosition:
15192         EditPositionDone(TRUE);
15193         break;
15194
15195       case AnalyzeMode:
15196       case AnalyzeFile:
15197         ExitAnalyzeMode();
15198         break;
15199
15200       default:
15201         EditGameEvent();
15202         break;
15203     }
15204
15205     gameMode = IcsIdle;
15206     ModeHighlight();
15207     return;
15208 }
15209
15210 void
15211 EditGameEvent ()
15212 {
15213     int i;
15214
15215     switch (gameMode) {
15216       case Training:
15217         SetTrainingModeOff();
15218         break;
15219       case MachinePlaysWhite:
15220       case MachinePlaysBlack:
15221       case BeginningOfGame:
15222         SendToProgram("force\n", &first);
15223         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15224             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15225                 char buf[MSG_SIZ];
15226                 abortEngineThink = TRUE;
15227                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15228                 SendToProgram(buf, &first);
15229                 DisplayMessage("Aborting engine think", "");
15230                 FreezeUI();
15231             }
15232         }
15233         SetUserThinkingEnables();
15234         break;
15235       case PlayFromGameFile:
15236         (void) StopLoadGameTimer();
15237         if (gameFileFP != NULL) {
15238             gameFileFP = NULL;
15239         }
15240         break;
15241       case EditPosition:
15242         EditPositionDone(TRUE);
15243         break;
15244       case AnalyzeMode:
15245       case AnalyzeFile:
15246         ExitAnalyzeMode();
15247         SendToProgram("force\n", &first);
15248         break;
15249       case TwoMachinesPlay:
15250         GameEnds(EndOfFile, NULL, GE_PLAYER);
15251         ResurrectChessProgram();
15252         SetUserThinkingEnables();
15253         break;
15254       case EndOfGame:
15255         ResurrectChessProgram();
15256         break;
15257       case IcsPlayingBlack:
15258       case IcsPlayingWhite:
15259         DisplayError(_("Warning: You are still playing a game"), 0);
15260         break;
15261       case IcsObserving:
15262         DisplayError(_("Warning: You are still observing a game"), 0);
15263         break;
15264       case IcsExamining:
15265         DisplayError(_("Warning: You are still examining a game"), 0);
15266         break;
15267       case IcsIdle:
15268         break;
15269       case EditGame:
15270       default:
15271         return;
15272     }
15273
15274     pausing = FALSE;
15275     StopClocks();
15276     first.offeredDraw = second.offeredDraw = 0;
15277
15278     if (gameMode == PlayFromGameFile) {
15279         whiteTimeRemaining = timeRemaining[0][currentMove];
15280         blackTimeRemaining = timeRemaining[1][currentMove];
15281         DisplayTitle("");
15282     }
15283
15284     if (gameMode == MachinePlaysWhite ||
15285         gameMode == MachinePlaysBlack ||
15286         gameMode == TwoMachinesPlay ||
15287         gameMode == EndOfGame) {
15288         i = forwardMostMove;
15289         while (i > currentMove) {
15290             SendToProgram("undo\n", &first);
15291             i--;
15292         }
15293         if(!adjustedClock) {
15294         whiteTimeRemaining = timeRemaining[0][currentMove];
15295         blackTimeRemaining = timeRemaining[1][currentMove];
15296         DisplayBothClocks();
15297         }
15298         if (whiteFlag || blackFlag) {
15299             whiteFlag = blackFlag = 0;
15300         }
15301         DisplayTitle("");
15302     }
15303
15304     gameMode = EditGame;
15305     ModeHighlight();
15306     SetGameInfo();
15307 }
15308
15309 void
15310 EditPositionEvent ()
15311 {
15312     int i;
15313     if (gameMode == EditPosition) {
15314         EditGameEvent();
15315         return;
15316     }
15317
15318     EditGameEvent();
15319     if (gameMode != EditGame) return;
15320
15321     gameMode = EditPosition;
15322     ModeHighlight();
15323     SetGameInfo();
15324     CopyBoard(rightsBoard, nullBoard);
15325     if (currentMove > 0)
15326       CopyBoard(boards[0], boards[currentMove]);
15327     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15328       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15329
15330     blackPlaysFirst = !WhiteOnMove(currentMove);
15331     ResetClocks();
15332     currentMove = forwardMostMove = backwardMostMove = 0;
15333     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15334     DisplayMove(-1);
15335     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15336 }
15337
15338 void
15339 ExitAnalyzeMode ()
15340 {
15341     /* [DM] icsEngineAnalyze - possible call from other functions */
15342     if (appData.icsEngineAnalyze) {
15343         appData.icsEngineAnalyze = FALSE;
15344
15345         DisplayMessage("",_("Close ICS engine analyze..."));
15346     }
15347     if (first.analysisSupport && first.analyzing) {
15348       SendToBoth("exit\n");
15349       first.analyzing = second.analyzing = FALSE;
15350     }
15351     thinkOutput[0] = NULLCHAR;
15352 }
15353
15354 void
15355 EditPositionDone (Boolean fakeRights)
15356 {
15357     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15358
15359     startedFromSetupPosition = TRUE;
15360     InitChessProgram(&first, FALSE);
15361     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15362       int r, f;
15363       boards[0][EP_STATUS] = EP_NONE;
15364       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15365       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15366         if(rightsBoard[r][f]) {
15367           ChessSquare p = boards[0][r][f];
15368           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15369           else if(p == king) boards[0][CASTLING][2] = f;
15370           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15371           else rightsBoard[r][f] = 2; // mark for second pass
15372         }
15373       }
15374       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15375         if(rightsBoard[r][f] == 2) {
15376           ChessSquare p = boards[0][r][f];
15377           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15378           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15379         }
15380       }
15381     }
15382     SendToProgram("force\n", &first);
15383     if (blackPlaysFirst) {
15384         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15385         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15386         currentMove = forwardMostMove = backwardMostMove = 1;
15387         CopyBoard(boards[1], boards[0]);
15388     } else {
15389         currentMove = forwardMostMove = backwardMostMove = 0;
15390     }
15391     SendBoard(&first, forwardMostMove);
15392     if (appData.debugMode) {
15393         fprintf(debugFP, "EditPosDone\n");
15394     }
15395     DisplayTitle("");
15396     DisplayMessage("", "");
15397     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15398     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15399     gameMode = EditGame;
15400     ModeHighlight();
15401     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15402     ClearHighlights(); /* [AS] */
15403 }
15404
15405 /* Pause for `ms' milliseconds */
15406 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15407 void
15408 TimeDelay (long ms)
15409 {
15410     TimeMark m1, m2;
15411
15412     GetTimeMark(&m1);
15413     do {
15414         GetTimeMark(&m2);
15415     } while (SubtractTimeMarks(&m2, &m1) < ms);
15416 }
15417
15418 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15419 void
15420 SendMultiLineToICS (char *buf)
15421 {
15422     char temp[MSG_SIZ+1], *p;
15423     int len;
15424
15425     len = strlen(buf);
15426     if (len > MSG_SIZ)
15427       len = MSG_SIZ;
15428
15429     strncpy(temp, buf, len);
15430     temp[len] = 0;
15431
15432     p = temp;
15433     while (*p) {
15434         if (*p == '\n' || *p == '\r')
15435           *p = ' ';
15436         ++p;
15437     }
15438
15439     strcat(temp, "\n");
15440     SendToICS(temp);
15441     SendToPlayer(temp, strlen(temp));
15442 }
15443
15444 void
15445 SetWhiteToPlayEvent ()
15446 {
15447     if (gameMode == EditPosition) {
15448         blackPlaysFirst = FALSE;
15449         DisplayBothClocks();    /* works because currentMove is 0 */
15450     } else if (gameMode == IcsExamining) {
15451         SendToICS(ics_prefix);
15452         SendToICS("tomove white\n");
15453     }
15454 }
15455
15456 void
15457 SetBlackToPlayEvent ()
15458 {
15459     if (gameMode == EditPosition) {
15460         blackPlaysFirst = TRUE;
15461         currentMove = 1;        /* kludge */
15462         DisplayBothClocks();
15463         currentMove = 0;
15464     } else if (gameMode == IcsExamining) {
15465         SendToICS(ics_prefix);
15466         SendToICS("tomove black\n");
15467     }
15468 }
15469
15470 void
15471 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15472 {
15473     char buf[MSG_SIZ];
15474     ChessSquare piece = boards[0][y][x];
15475     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15476     static int lastVariant;
15477     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15478
15479     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15480
15481     switch (selection) {
15482       case ClearBoard:
15483         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15484         MarkTargetSquares(1);
15485         CopyBoard(currentBoard, boards[0]);
15486         CopyBoard(menuBoard, initialPosition);
15487         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15488             SendToICS(ics_prefix);
15489             SendToICS("bsetup clear\n");
15490         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15491             SendToICS(ics_prefix);
15492             SendToICS("clearboard\n");
15493         } else {
15494             int nonEmpty = 0;
15495             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15496                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15497                 for (y = 0; y < BOARD_HEIGHT; y++) {
15498                     if (gameMode == IcsExamining) {
15499                         if (boards[currentMove][y][x] != EmptySquare) {
15500                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15501                                     AAA + x, ONE + y);
15502                             SendToICS(buf);
15503                         }
15504                     } else if(boards[0][y][x] != DarkSquare) {
15505                         if(boards[0][y][x] != p) nonEmpty++;
15506                         boards[0][y][x] = p;
15507                     }
15508                 }
15509             }
15510             CopyBoard(rightsBoard, nullBoard);
15511             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15512                 int r, i;
15513                 for(r = 0; r < BOARD_HEIGHT; r++) {
15514                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15515                     ChessSquare p = menuBoard[r][x];
15516                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15517                   }
15518                 }
15519                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15520                 DisplayMessage("Clicking clock again restores position", "");
15521                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15522                 if(!nonEmpty) { // asked to clear an empty board
15523                     CopyBoard(boards[0], menuBoard);
15524                 } else
15525                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15526                     CopyBoard(boards[0], initialPosition);
15527                 } else
15528                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15529                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15530                     CopyBoard(boards[0], erasedBoard);
15531                 } else
15532                     CopyBoard(erasedBoard, currentBoard);
15533
15534                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15535                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15536             }
15537         }
15538         if (gameMode == EditPosition) {
15539             DrawPosition(FALSE, boards[0]);
15540         }
15541         break;
15542
15543       case WhitePlay:
15544         SetWhiteToPlayEvent();
15545         break;
15546
15547       case BlackPlay:
15548         SetBlackToPlayEvent();
15549         break;
15550
15551       case EmptySquare:
15552         if (gameMode == IcsExamining) {
15553             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15554             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15555             SendToICS(buf);
15556         } else {
15557             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15558                 if(x == BOARD_LEFT-2) {
15559                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15560                     boards[0][y][1] = 0;
15561                 } else
15562                 if(x == BOARD_RGHT+1) {
15563                     if(y >= gameInfo.holdingsSize) break;
15564                     boards[0][y][BOARD_WIDTH-2] = 0;
15565                 } else break;
15566             }
15567             boards[0][y][x] = EmptySquare;
15568             DrawPosition(FALSE, boards[0]);
15569         }
15570         break;
15571
15572       case PromotePiece:
15573         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15574            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15575             selection = (ChessSquare) (PROMOTED(piece));
15576         } else if(piece == EmptySquare) selection = WhiteSilver;
15577         else selection = (ChessSquare)((int)piece - 1);
15578         goto defaultlabel;
15579
15580       case DemotePiece:
15581         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15582            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15583             selection = (ChessSquare) (DEMOTED(piece));
15584         } else if(piece == EmptySquare) selection = BlackSilver;
15585         else selection = (ChessSquare)((int)piece + 1);
15586         goto defaultlabel;
15587
15588       case WhiteQueen:
15589       case BlackQueen:
15590         if(gameInfo.variant == VariantShatranj ||
15591            gameInfo.variant == VariantXiangqi  ||
15592            gameInfo.variant == VariantCourier  ||
15593            gameInfo.variant == VariantASEAN    ||
15594            gameInfo.variant == VariantMakruk     )
15595             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15596         goto defaultlabel;
15597
15598       case WhiteRook:
15599         baseRank = 0;
15600       case BlackRook:
15601         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15602         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15603         goto defaultlabel;
15604
15605       case WhiteKing:
15606         baseRank = 0;
15607       case BlackKing:
15608         if(gameInfo.variant == VariantXiangqi)
15609             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15610         if(gameInfo.variant == VariantKnightmate)
15611             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15612         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15613       default:
15614         defaultlabel:
15615         if (gameMode == IcsExamining) {
15616             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15617             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15618                      PieceToChar(selection), AAA + x, ONE + y);
15619             SendToICS(buf);
15620         } else {
15621             rightsBoard[y][x] = hasRights;
15622             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15623                 int n;
15624                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15625                     n = PieceToNumber(selection - BlackPawn);
15626                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15627                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15628                     boards[0][BOARD_HEIGHT-1-n][1]++;
15629                 } else
15630                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15631                     n = PieceToNumber(selection);
15632                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15633                     boards[0][n][BOARD_WIDTH-1] = selection;
15634                     boards[0][n][BOARD_WIDTH-2]++;
15635                 }
15636             } else
15637             boards[0][y][x] = selection;
15638             DrawPosition(TRUE, boards[0]);
15639             ClearHighlights();
15640             fromX = fromY = -1;
15641         }
15642         break;
15643     }
15644 }
15645
15646
15647 void
15648 DropMenuEvent (ChessSquare selection, int x, int y)
15649 {
15650     ChessMove moveType;
15651
15652     switch (gameMode) {
15653       case IcsPlayingWhite:
15654       case MachinePlaysBlack:
15655         if (!WhiteOnMove(currentMove)) {
15656             DisplayMoveError(_("It is Black's turn"));
15657             return;
15658         }
15659         moveType = WhiteDrop;
15660         break;
15661       case IcsPlayingBlack:
15662       case MachinePlaysWhite:
15663         if (WhiteOnMove(currentMove)) {
15664             DisplayMoveError(_("It is White's turn"));
15665             return;
15666         }
15667         moveType = BlackDrop;
15668         break;
15669       case EditGame:
15670         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15671         break;
15672       default:
15673         return;
15674     }
15675
15676     if (moveType == BlackDrop && selection < BlackPawn) {
15677       selection = (ChessSquare) ((int) selection
15678                                  + (int) BlackPawn - (int) WhitePawn);
15679     }
15680     if (boards[currentMove][y][x] != EmptySquare) {
15681         DisplayMoveError(_("That square is occupied"));
15682         return;
15683     }
15684
15685     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15686 }
15687
15688 void
15689 AcceptEvent ()
15690 {
15691     /* Accept a pending offer of any kind from opponent */
15692
15693     if (appData.icsActive) {
15694         SendToICS(ics_prefix);
15695         SendToICS("accept\n");
15696     } else if (cmailMsgLoaded) {
15697         if (currentMove == cmailOldMove &&
15698             commentList[cmailOldMove] != NULL &&
15699             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15700                    "Black offers a draw" : "White offers a draw")) {
15701             TruncateGame();
15702             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15703             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15704         } else {
15705             DisplayError(_("There is no pending offer on this move"), 0);
15706             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15707         }
15708     } else {
15709         /* Not used for offers from chess program */
15710     }
15711 }
15712
15713 void
15714 DeclineEvent ()
15715 {
15716     /* Decline a pending offer of any kind from opponent */
15717
15718     if (appData.icsActive) {
15719         SendToICS(ics_prefix);
15720         SendToICS("decline\n");
15721     } else if (cmailMsgLoaded) {
15722         if (currentMove == cmailOldMove &&
15723             commentList[cmailOldMove] != NULL &&
15724             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15725                    "Black offers a draw" : "White offers a draw")) {
15726 #ifdef NOTDEF
15727             AppendComment(cmailOldMove, "Draw declined", TRUE);
15728             DisplayComment(cmailOldMove - 1, "Draw declined");
15729 #endif /*NOTDEF*/
15730         } else {
15731             DisplayError(_("There is no pending offer on this move"), 0);
15732         }
15733     } else {
15734         /* Not used for offers from chess program */
15735     }
15736 }
15737
15738 void
15739 RematchEvent ()
15740 {
15741     /* Issue ICS rematch command */
15742     if (appData.icsActive) {
15743         SendToICS(ics_prefix);
15744         SendToICS("rematch\n");
15745     }
15746 }
15747
15748 void
15749 CallFlagEvent ()
15750 {
15751     /* Call your opponent's flag (claim a win on time) */
15752     if (appData.icsActive) {
15753         SendToICS(ics_prefix);
15754         SendToICS("flag\n");
15755     } else {
15756         switch (gameMode) {
15757           default:
15758             return;
15759           case MachinePlaysWhite:
15760             if (whiteFlag) {
15761                 if (blackFlag)
15762                   GameEnds(GameIsDrawn, "Both players ran out of time",
15763                            GE_PLAYER);
15764                 else
15765                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15766             } else {
15767                 DisplayError(_("Your opponent is not out of time"), 0);
15768             }
15769             break;
15770           case MachinePlaysBlack:
15771             if (blackFlag) {
15772                 if (whiteFlag)
15773                   GameEnds(GameIsDrawn, "Both players ran out of time",
15774                            GE_PLAYER);
15775                 else
15776                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15777             } else {
15778                 DisplayError(_("Your opponent is not out of time"), 0);
15779             }
15780             break;
15781         }
15782     }
15783 }
15784
15785 void
15786 ClockClick (int which)
15787 {       // [HGM] code moved to back-end from winboard.c
15788         if(which) { // black clock
15789           if (gameMode == EditPosition || gameMode == IcsExamining) {
15790             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15791             SetBlackToPlayEvent();
15792           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15793                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15794           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15795           } else if (shiftKey) {
15796             AdjustClock(which, -1);
15797           } else if (gameMode == IcsPlayingWhite ||
15798                      gameMode == MachinePlaysBlack) {
15799             CallFlagEvent();
15800           }
15801         } else { // white clock
15802           if (gameMode == EditPosition || gameMode == IcsExamining) {
15803             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15804             SetWhiteToPlayEvent();
15805           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15806                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15807           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15808           } else if (shiftKey) {
15809             AdjustClock(which, -1);
15810           } else if (gameMode == IcsPlayingBlack ||
15811                    gameMode == MachinePlaysWhite) {
15812             CallFlagEvent();
15813           }
15814         }
15815 }
15816
15817 void
15818 DrawEvent ()
15819 {
15820     /* Offer draw or accept pending draw offer from opponent */
15821
15822     if (appData.icsActive) {
15823         /* Note: tournament rules require draw offers to be
15824            made after you make your move but before you punch
15825            your clock.  Currently ICS doesn't let you do that;
15826            instead, you immediately punch your clock after making
15827            a move, but you can offer a draw at any time. */
15828
15829         SendToICS(ics_prefix);
15830         SendToICS("draw\n");
15831         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15832     } else if (cmailMsgLoaded) {
15833         if (currentMove == cmailOldMove &&
15834             commentList[cmailOldMove] != NULL &&
15835             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15836                    "Black offers a draw" : "White offers a draw")) {
15837             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15838             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15839         } else if (currentMove == cmailOldMove + 1) {
15840             char *offer = WhiteOnMove(cmailOldMove) ?
15841               "White offers a draw" : "Black offers a draw";
15842             AppendComment(currentMove, offer, TRUE);
15843             DisplayComment(currentMove - 1, offer);
15844             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15845         } else {
15846             DisplayError(_("You must make your move before offering a draw"), 0);
15847             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15848         }
15849     } else if (first.offeredDraw) {
15850         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15851     } else {
15852         if (first.sendDrawOffers) {
15853             SendToProgram("draw\n", &first);
15854             userOfferedDraw = TRUE;
15855         }
15856     }
15857 }
15858
15859 void
15860 AdjournEvent ()
15861 {
15862     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15863
15864     if (appData.icsActive) {
15865         SendToICS(ics_prefix);
15866         SendToICS("adjourn\n");
15867     } else {
15868         /* Currently GNU Chess doesn't offer or accept Adjourns */
15869     }
15870 }
15871
15872
15873 void
15874 AbortEvent ()
15875 {
15876     /* Offer Abort or accept pending Abort offer from opponent */
15877
15878     if (appData.icsActive) {
15879         SendToICS(ics_prefix);
15880         SendToICS("abort\n");
15881     } else {
15882         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15883     }
15884 }
15885
15886 void
15887 ResignEvent ()
15888 {
15889     /* Resign.  You can do this even if it's not your turn. */
15890
15891     if (appData.icsActive) {
15892         SendToICS(ics_prefix);
15893         SendToICS("resign\n");
15894     } else {
15895         switch (gameMode) {
15896           case MachinePlaysWhite:
15897             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15898             break;
15899           case MachinePlaysBlack:
15900             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15901             break;
15902           case EditGame:
15903             if (cmailMsgLoaded) {
15904                 TruncateGame();
15905                 if (WhiteOnMove(cmailOldMove)) {
15906                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15907                 } else {
15908                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15909                 }
15910                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15911             }
15912             break;
15913           default:
15914             break;
15915         }
15916     }
15917 }
15918
15919
15920 void
15921 StopObservingEvent ()
15922 {
15923     /* Stop observing current games */
15924     SendToICS(ics_prefix);
15925     SendToICS("unobserve\n");
15926 }
15927
15928 void
15929 StopExaminingEvent ()
15930 {
15931     /* Stop observing current game */
15932     SendToICS(ics_prefix);
15933     SendToICS("unexamine\n");
15934 }
15935
15936 void
15937 ForwardInner (int target)
15938 {
15939     int limit; int oldSeekGraphUp = seekGraphUp;
15940
15941     if (appData.debugMode)
15942         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15943                 target, currentMove, forwardMostMove);
15944
15945     if (gameMode == EditPosition)
15946       return;
15947
15948     seekGraphUp = FALSE;
15949     MarkTargetSquares(1);
15950     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15951
15952     if (gameMode == PlayFromGameFile && !pausing)
15953       PauseEvent();
15954
15955     if (gameMode == IcsExamining && pausing)
15956       limit = pauseExamForwardMostMove;
15957     else
15958       limit = forwardMostMove;
15959
15960     if (target > limit) target = limit;
15961
15962     if (target > 0 && moveList[target - 1][0]) {
15963         int fromX, fromY, toX, toY;
15964         toX = moveList[target - 1][2] - AAA;
15965         toY = moveList[target - 1][3] - ONE;
15966         if (moveList[target - 1][1] == '@') {
15967             if (appData.highlightLastMove) {
15968                 SetHighlights(-1, -1, toX, toY);
15969             }
15970         } else {
15971             fromX = moveList[target - 1][0] - AAA;
15972             fromY = moveList[target - 1][1] - ONE;
15973             if (target == currentMove + 1) {
15974                 if(moveList[target - 1][4] == ';') { // multi-leg
15975                     killX = moveList[target - 1][5] - AAA;
15976                     killY = moveList[target - 1][6] - ONE;
15977                 }
15978                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15979                 killX = killY = -1;
15980             }
15981             if (appData.highlightLastMove) {
15982                 SetHighlights(fromX, fromY, toX, toY);
15983             }
15984         }
15985     }
15986     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15987         gameMode == Training || gameMode == PlayFromGameFile ||
15988         gameMode == AnalyzeFile) {
15989         while (currentMove < target) {
15990             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15991             SendMoveToProgram(currentMove++, &first);
15992         }
15993     } else {
15994         currentMove = target;
15995     }
15996
15997     if (gameMode == EditGame || gameMode == EndOfGame) {
15998         whiteTimeRemaining = timeRemaining[0][currentMove];
15999         blackTimeRemaining = timeRemaining[1][currentMove];
16000     }
16001     DisplayBothClocks();
16002     DisplayMove(currentMove - 1);
16003     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16004     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16005     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16006         DisplayComment(currentMove - 1, commentList[currentMove]);
16007     }
16008     ClearMap(); // [HGM] exclude: invalidate map
16009 }
16010
16011
16012 void
16013 ForwardEvent ()
16014 {
16015     if (gameMode == IcsExamining && !pausing) {
16016         SendToICS(ics_prefix);
16017         SendToICS("forward\n");
16018     } else {
16019         ForwardInner(currentMove + 1);
16020     }
16021 }
16022
16023 void
16024 ToEndEvent ()
16025 {
16026     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16027         /* to optimze, we temporarily turn off analysis mode while we feed
16028          * the remaining moves to the engine. Otherwise we get analysis output
16029          * after each move.
16030          */
16031         if (first.analysisSupport) {
16032           SendToProgram("exit\nforce\n", &first);
16033           first.analyzing = FALSE;
16034         }
16035     }
16036
16037     if (gameMode == IcsExamining && !pausing) {
16038         SendToICS(ics_prefix);
16039         SendToICS("forward 999999\n");
16040     } else {
16041         ForwardInner(forwardMostMove);
16042     }
16043
16044     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16045         /* we have fed all the moves, so reactivate analysis mode */
16046         SendToProgram("analyze\n", &first);
16047         first.analyzing = TRUE;
16048         /*first.maybeThinking = TRUE;*/
16049         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16050     }
16051 }
16052
16053 void
16054 BackwardInner (int target)
16055 {
16056     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16057
16058     if (appData.debugMode)
16059         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16060                 target, currentMove, forwardMostMove);
16061
16062     if (gameMode == EditPosition) return;
16063     seekGraphUp = FALSE;
16064     MarkTargetSquares(1);
16065     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16066     if (currentMove <= backwardMostMove) {
16067         ClearHighlights();
16068         DrawPosition(full_redraw, boards[currentMove]);
16069         return;
16070     }
16071     if (gameMode == PlayFromGameFile && !pausing)
16072       PauseEvent();
16073
16074     if (moveList[target][0]) {
16075         int fromX, fromY, toX, toY;
16076         toX = moveList[target][2] - AAA;
16077         toY = moveList[target][3] - ONE;
16078         if (moveList[target][1] == '@') {
16079             if (appData.highlightLastMove) {
16080                 SetHighlights(-1, -1, toX, toY);
16081             }
16082         } else {
16083             fromX = moveList[target][0] - AAA;
16084             fromY = moveList[target][1] - ONE;
16085             if (target == currentMove - 1) {
16086                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16087             }
16088             if (appData.highlightLastMove) {
16089                 SetHighlights(fromX, fromY, toX, toY);
16090             }
16091         }
16092     }
16093     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16094         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16095         while (currentMove > target) {
16096             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16097                 // null move cannot be undone. Reload program with move history before it.
16098                 int i;
16099                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16100                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16101                 }
16102                 SendBoard(&first, i);
16103               if(second.analyzing) SendBoard(&second, i);
16104                 for(currentMove=i; currentMove<target; currentMove++) {
16105                     SendMoveToProgram(currentMove, &first);
16106                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16107                 }
16108                 break;
16109             }
16110             SendToBoth("undo\n");
16111             currentMove--;
16112         }
16113     } else {
16114         currentMove = target;
16115     }
16116
16117     if (gameMode == EditGame || gameMode == EndOfGame) {
16118         whiteTimeRemaining = timeRemaining[0][currentMove];
16119         blackTimeRemaining = timeRemaining[1][currentMove];
16120     }
16121     DisplayBothClocks();
16122     DisplayMove(currentMove - 1);
16123     DrawPosition(full_redraw, boards[currentMove]);
16124     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16125     // [HGM] PV info: routine tests if comment empty
16126     DisplayComment(currentMove - 1, commentList[currentMove]);
16127     ClearMap(); // [HGM] exclude: invalidate map
16128 }
16129
16130 void
16131 BackwardEvent ()
16132 {
16133     if (gameMode == IcsExamining && !pausing) {
16134         SendToICS(ics_prefix);
16135         SendToICS("backward\n");
16136     } else {
16137         BackwardInner(currentMove - 1);
16138     }
16139 }
16140
16141 void
16142 ToStartEvent ()
16143 {
16144     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16145         /* to optimize, we temporarily turn off analysis mode while we undo
16146          * all the moves. Otherwise we get analysis output after each undo.
16147          */
16148         if (first.analysisSupport) {
16149           SendToProgram("exit\nforce\n", &first);
16150           first.analyzing = FALSE;
16151         }
16152     }
16153
16154     if (gameMode == IcsExamining && !pausing) {
16155         SendToICS(ics_prefix);
16156         SendToICS("backward 999999\n");
16157     } else {
16158         BackwardInner(backwardMostMove);
16159     }
16160
16161     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16162         /* we have fed all the moves, so reactivate analysis mode */
16163         SendToProgram("analyze\n", &first);
16164         first.analyzing = TRUE;
16165         /*first.maybeThinking = TRUE;*/
16166         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16167     }
16168 }
16169
16170 void
16171 ToNrEvent (int to)
16172 {
16173   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16174   if (to >= forwardMostMove) to = forwardMostMove;
16175   if (to <= backwardMostMove) to = backwardMostMove;
16176   if (to < currentMove) {
16177     BackwardInner(to);
16178   } else {
16179     ForwardInner(to);
16180   }
16181 }
16182
16183 void
16184 RevertEvent (Boolean annotate)
16185 {
16186     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16187         return;
16188     }
16189     if (gameMode != IcsExamining) {
16190         DisplayError(_("You are not examining a game"), 0);
16191         return;
16192     }
16193     if (pausing) {
16194         DisplayError(_("You can't revert while pausing"), 0);
16195         return;
16196     }
16197     SendToICS(ics_prefix);
16198     SendToICS("revert\n");
16199 }
16200
16201 void
16202 RetractMoveEvent ()
16203 {
16204     switch (gameMode) {
16205       case MachinePlaysWhite:
16206       case MachinePlaysBlack:
16207         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16208             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16209             return;
16210         }
16211         if (forwardMostMove < 2) return;
16212         currentMove = forwardMostMove = forwardMostMove - 2;
16213         whiteTimeRemaining = timeRemaining[0][currentMove];
16214         blackTimeRemaining = timeRemaining[1][currentMove];
16215         DisplayBothClocks();
16216         DisplayMove(currentMove - 1);
16217         ClearHighlights();/*!! could figure this out*/
16218         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16219         SendToProgram("remove\n", &first);
16220         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16221         break;
16222
16223       case BeginningOfGame:
16224       default:
16225         break;
16226
16227       case IcsPlayingWhite:
16228       case IcsPlayingBlack:
16229         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16230             SendToICS(ics_prefix);
16231             SendToICS("takeback 2\n");
16232         } else {
16233             SendToICS(ics_prefix);
16234             SendToICS("takeback 1\n");
16235         }
16236         break;
16237     }
16238 }
16239
16240 void
16241 MoveNowEvent ()
16242 {
16243     ChessProgramState *cps;
16244
16245     switch (gameMode) {
16246       case MachinePlaysWhite:
16247         if (!WhiteOnMove(forwardMostMove)) {
16248             DisplayError(_("It is your turn"), 0);
16249             return;
16250         }
16251         cps = &first;
16252         break;
16253       case MachinePlaysBlack:
16254         if (WhiteOnMove(forwardMostMove)) {
16255             DisplayError(_("It is your turn"), 0);
16256             return;
16257         }
16258         cps = &first;
16259         break;
16260       case TwoMachinesPlay:
16261         if (WhiteOnMove(forwardMostMove) ==
16262             (first.twoMachinesColor[0] == 'w')) {
16263             cps = &first;
16264         } else {
16265             cps = &second;
16266         }
16267         break;
16268       case BeginningOfGame:
16269       default:
16270         return;
16271     }
16272     SendToProgram("?\n", cps);
16273 }
16274
16275 void
16276 TruncateGameEvent ()
16277 {
16278     EditGameEvent();
16279     if (gameMode != EditGame) return;
16280     TruncateGame();
16281 }
16282
16283 void
16284 TruncateGame ()
16285 {
16286     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16287     if (forwardMostMove > currentMove) {
16288         if (gameInfo.resultDetails != NULL) {
16289             free(gameInfo.resultDetails);
16290             gameInfo.resultDetails = NULL;
16291             gameInfo.result = GameUnfinished;
16292         }
16293         forwardMostMove = currentMove;
16294         HistorySet(parseList, backwardMostMove, forwardMostMove,
16295                    currentMove-1);
16296     }
16297 }
16298
16299 void
16300 HintEvent ()
16301 {
16302     if (appData.noChessProgram) return;
16303     switch (gameMode) {
16304       case MachinePlaysWhite:
16305         if (WhiteOnMove(forwardMostMove)) {
16306             DisplayError(_("Wait until your turn."), 0);
16307             return;
16308         }
16309         break;
16310       case BeginningOfGame:
16311       case MachinePlaysBlack:
16312         if (!WhiteOnMove(forwardMostMove)) {
16313             DisplayError(_("Wait until your turn."), 0);
16314             return;
16315         }
16316         break;
16317       default:
16318         DisplayError(_("No hint available"), 0);
16319         return;
16320     }
16321     SendToProgram("hint\n", &first);
16322     hintRequested = TRUE;
16323 }
16324
16325 int
16326 SaveSelected (FILE *g, int dummy, char *dummy2)
16327 {
16328     ListGame * lg = (ListGame *) gameList.head;
16329     int nItem, cnt=0;
16330     FILE *f;
16331
16332     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16333         DisplayError(_("Game list not loaded or empty"), 0);
16334         return 0;
16335     }
16336
16337     creatingBook = TRUE; // suppresses stuff during load game
16338
16339     /* Get list size */
16340     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16341         if(lg->position >= 0) { // selected?
16342             LoadGame(f, nItem, "", TRUE);
16343             SaveGamePGN2(g); // leaves g open
16344             cnt++; DoEvents();
16345         }
16346         lg = (ListGame *) lg->node.succ;
16347     }
16348
16349     fclose(g);
16350     creatingBook = FALSE;
16351
16352     return cnt;
16353 }
16354
16355 void
16356 CreateBookEvent ()
16357 {
16358     ListGame * lg = (ListGame *) gameList.head;
16359     FILE *f, *g;
16360     int nItem;
16361     static int secondTime = FALSE;
16362
16363     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16364         DisplayError(_("Game list not loaded or empty"), 0);
16365         return;
16366     }
16367
16368     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16369         fclose(g);
16370         secondTime++;
16371         DisplayNote(_("Book file exists! Try again for overwrite."));
16372         return;
16373     }
16374
16375     creatingBook = TRUE;
16376     secondTime = FALSE;
16377
16378     /* Get list size */
16379     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16380         if(lg->position >= 0) {
16381             LoadGame(f, nItem, "", TRUE);
16382             AddGameToBook(TRUE);
16383             DoEvents();
16384         }
16385         lg = (ListGame *) lg->node.succ;
16386     }
16387
16388     creatingBook = FALSE;
16389     FlushBook();
16390 }
16391
16392 void
16393 BookEvent ()
16394 {
16395     if (appData.noChessProgram) return;
16396     switch (gameMode) {
16397       case MachinePlaysWhite:
16398         if (WhiteOnMove(forwardMostMove)) {
16399             DisplayError(_("Wait until your turn."), 0);
16400             return;
16401         }
16402         break;
16403       case BeginningOfGame:
16404       case MachinePlaysBlack:
16405         if (!WhiteOnMove(forwardMostMove)) {
16406             DisplayError(_("Wait until your turn."), 0);
16407             return;
16408         }
16409         break;
16410       case EditPosition:
16411         EditPositionDone(TRUE);
16412         break;
16413       case TwoMachinesPlay:
16414         return;
16415       default:
16416         break;
16417     }
16418     SendToProgram("bk\n", &first);
16419     bookOutput[0] = NULLCHAR;
16420     bookRequested = TRUE;
16421 }
16422
16423 void
16424 AboutGameEvent ()
16425 {
16426     char *tags = PGNTags(&gameInfo);
16427     TagsPopUp(tags, CmailMsg());
16428     free(tags);
16429 }
16430
16431 /* end button procedures */
16432
16433 void
16434 PrintPosition (FILE *fp, int move)
16435 {
16436     int i, j;
16437
16438     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16439         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16440             char c = PieceToChar(boards[move][i][j]);
16441             fputc(c == '?' ? '.' : c, fp);
16442             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16443         }
16444     }
16445     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16446       fprintf(fp, "white to play\n");
16447     else
16448       fprintf(fp, "black to play\n");
16449 }
16450
16451 void
16452 PrintOpponents (FILE *fp)
16453 {
16454     if (gameInfo.white != NULL) {
16455         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16456     } else {
16457         fprintf(fp, "\n");
16458     }
16459 }
16460
16461 /* Find last component of program's own name, using some heuristics */
16462 void
16463 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16464 {
16465     char *p, *q, c;
16466     int local = (strcmp(host, "localhost") == 0);
16467     while (!local && (p = strchr(prog, ';')) != NULL) {
16468         p++;
16469         while (*p == ' ') p++;
16470         prog = p;
16471     }
16472     if (*prog == '"' || *prog == '\'') {
16473         q = strchr(prog + 1, *prog);
16474     } else {
16475         q = strchr(prog, ' ');
16476     }
16477     if (q == NULL) q = prog + strlen(prog);
16478     p = q;
16479     while (p >= prog && *p != '/' && *p != '\\') p--;
16480     p++;
16481     if(p == prog && *p == '"') p++;
16482     c = *q; *q = 0;
16483     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16484     memcpy(buf, p, q - p);
16485     buf[q - p] = NULLCHAR;
16486     if (!local) {
16487         strcat(buf, "@");
16488         strcat(buf, host);
16489     }
16490 }
16491
16492 char *
16493 TimeControlTagValue ()
16494 {
16495     char buf[MSG_SIZ];
16496     if (!appData.clockMode) {
16497       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16498     } else if (movesPerSession > 0) {
16499       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16500     } else if (timeIncrement == 0) {
16501       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16502     } else {
16503       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16504     }
16505     return StrSave(buf);
16506 }
16507
16508 void
16509 SetGameInfo ()
16510 {
16511     /* This routine is used only for certain modes */
16512     VariantClass v = gameInfo.variant;
16513     ChessMove r = GameUnfinished;
16514     char *p = NULL;
16515
16516     if(keepInfo) return;
16517
16518     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16519         r = gameInfo.result;
16520         p = gameInfo.resultDetails;
16521         gameInfo.resultDetails = NULL;
16522     }
16523     ClearGameInfo(&gameInfo);
16524     gameInfo.variant = v;
16525
16526     switch (gameMode) {
16527       case MachinePlaysWhite:
16528         gameInfo.event = StrSave( appData.pgnEventHeader );
16529         gameInfo.site = StrSave(HostName());
16530         gameInfo.date = PGNDate();
16531         gameInfo.round = StrSave("-");
16532         gameInfo.white = StrSave(first.tidy);
16533         gameInfo.black = StrSave(UserName());
16534         gameInfo.timeControl = TimeControlTagValue();
16535         break;
16536
16537       case MachinePlaysBlack:
16538         gameInfo.event = StrSave( appData.pgnEventHeader );
16539         gameInfo.site = StrSave(HostName());
16540         gameInfo.date = PGNDate();
16541         gameInfo.round = StrSave("-");
16542         gameInfo.white = StrSave(UserName());
16543         gameInfo.black = StrSave(first.tidy);
16544         gameInfo.timeControl = TimeControlTagValue();
16545         break;
16546
16547       case TwoMachinesPlay:
16548         gameInfo.event = StrSave( appData.pgnEventHeader );
16549         gameInfo.site = StrSave(HostName());
16550         gameInfo.date = PGNDate();
16551         if (roundNr > 0) {
16552             char buf[MSG_SIZ];
16553             snprintf(buf, MSG_SIZ, "%d", roundNr);
16554             gameInfo.round = StrSave(buf);
16555         } else {
16556             gameInfo.round = StrSave("-");
16557         }
16558         if (first.twoMachinesColor[0] == 'w') {
16559             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16560             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16561         } else {
16562             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16563             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16564         }
16565         gameInfo.timeControl = TimeControlTagValue();
16566         break;
16567
16568       case EditGame:
16569         gameInfo.event = StrSave("Edited game");
16570         gameInfo.site = StrSave(HostName());
16571         gameInfo.date = PGNDate();
16572         gameInfo.round = StrSave("-");
16573         gameInfo.white = StrSave("-");
16574         gameInfo.black = StrSave("-");
16575         gameInfo.result = r;
16576         gameInfo.resultDetails = p;
16577         break;
16578
16579       case EditPosition:
16580         gameInfo.event = StrSave("Edited position");
16581         gameInfo.site = StrSave(HostName());
16582         gameInfo.date = PGNDate();
16583         gameInfo.round = StrSave("-");
16584         gameInfo.white = StrSave("-");
16585         gameInfo.black = StrSave("-");
16586         break;
16587
16588       case IcsPlayingWhite:
16589       case IcsPlayingBlack:
16590       case IcsObserving:
16591       case IcsExamining:
16592         break;
16593
16594       case PlayFromGameFile:
16595         gameInfo.event = StrSave("Game from non-PGN file");
16596         gameInfo.site = StrSave(HostName());
16597         gameInfo.date = PGNDate();
16598         gameInfo.round = StrSave("-");
16599         gameInfo.white = StrSave("?");
16600         gameInfo.black = StrSave("?");
16601         break;
16602
16603       default:
16604         break;
16605     }
16606 }
16607
16608 void
16609 ReplaceComment (int index, char *text)
16610 {
16611     int len;
16612     char *p;
16613     float score;
16614
16615     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16616        pvInfoList[index-1].depth == len &&
16617        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16618        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16619     while (*text == '\n') text++;
16620     len = strlen(text);
16621     while (len > 0 && text[len - 1] == '\n') len--;
16622
16623     if (commentList[index] != NULL)
16624       free(commentList[index]);
16625
16626     if (len == 0) {
16627         commentList[index] = NULL;
16628         return;
16629     }
16630   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16631       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16632       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16633     commentList[index] = (char *) malloc(len + 2);
16634     strncpy(commentList[index], text, len);
16635     commentList[index][len] = '\n';
16636     commentList[index][len + 1] = NULLCHAR;
16637   } else {
16638     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16639     char *p;
16640     commentList[index] = (char *) malloc(len + 7);
16641     safeStrCpy(commentList[index], "{\n", 3);
16642     safeStrCpy(commentList[index]+2, text, len+1);
16643     commentList[index][len+2] = NULLCHAR;
16644     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16645     strcat(commentList[index], "\n}\n");
16646   }
16647 }
16648
16649 void
16650 CrushCRs (char *text)
16651 {
16652   char *p = text;
16653   char *q = text;
16654   char ch;
16655
16656   do {
16657     ch = *p++;
16658     if (ch == '\r') continue;
16659     *q++ = ch;
16660   } while (ch != '\0');
16661 }
16662
16663 void
16664 AppendComment (int index, char *text, Boolean addBraces)
16665 /* addBraces  tells if we should add {} */
16666 {
16667     int oldlen, len;
16668     char *old;
16669
16670 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16671     if(addBraces == 3) addBraces = 0; else // force appending literally
16672     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16673
16674     CrushCRs(text);
16675     while (*text == '\n') text++;
16676     len = strlen(text);
16677     while (len > 0 && text[len - 1] == '\n') len--;
16678     text[len] = NULLCHAR;
16679
16680     if (len == 0) return;
16681
16682     if (commentList[index] != NULL) {
16683       Boolean addClosingBrace = addBraces;
16684         old = commentList[index];
16685         oldlen = strlen(old);
16686         while(commentList[index][oldlen-1] ==  '\n')
16687           commentList[index][--oldlen] = NULLCHAR;
16688         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16689         safeStrCpy(commentList[index], old, oldlen + len + 6);
16690         free(old);
16691         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16692         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16693           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16694           while (*text == '\n') { text++; len--; }
16695           commentList[index][--oldlen] = NULLCHAR;
16696       }
16697         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16698         else          strcat(commentList[index], "\n");
16699         strcat(commentList[index], text);
16700         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16701         else          strcat(commentList[index], "\n");
16702     } else {
16703         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16704         if(addBraces)
16705           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16706         else commentList[index][0] = NULLCHAR;
16707         strcat(commentList[index], text);
16708         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16709         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16710     }
16711 }
16712
16713 static char *
16714 FindStr (char * text, char * sub_text)
16715 {
16716     char * result = strstr( text, sub_text );
16717
16718     if( result != NULL ) {
16719         result += strlen( sub_text );
16720     }
16721
16722     return result;
16723 }
16724
16725 /* [AS] Try to extract PV info from PGN comment */
16726 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16727 char *
16728 GetInfoFromComment (int index, char * text)
16729 {
16730     char * sep = text, *p;
16731
16732     if( text != NULL && index > 0 ) {
16733         int score = 0;
16734         int depth = 0;
16735         int time = -1, sec = 0, deci;
16736         char * s_eval = FindStr( text, "[%eval " );
16737         char * s_emt = FindStr( text, "[%emt " );
16738 #if 0
16739         if( s_eval != NULL || s_emt != NULL ) {
16740 #else
16741         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16742 #endif
16743             /* New style */
16744             char delim;
16745
16746             if( s_eval != NULL ) {
16747                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16748                     return text;
16749                 }
16750
16751                 if( delim != ']' ) {
16752                     return text;
16753                 }
16754             }
16755
16756             if( s_emt != NULL ) {
16757             }
16758                 return text;
16759         }
16760         else {
16761             /* We expect something like: [+|-]nnn.nn/dd */
16762             int score_lo = 0;
16763
16764             if(*text != '{') return text; // [HGM] braces: must be normal comment
16765
16766             sep = strchr( text, '/' );
16767             if( sep == NULL || sep < (text+4) ) {
16768                 return text;
16769             }
16770
16771             p = text;
16772             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16773             if(p[1] == '(') { // comment starts with PV
16774                p = strchr(p, ')'); // locate end of PV
16775                if(p == NULL || sep < p+5) return text;
16776                // at this point we have something like "{(.*) +0.23/6 ..."
16777                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16778                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16779                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16780             }
16781             time = -1; sec = -1; deci = -1;
16782             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16783                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16784                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16785                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16786                 return text;
16787             }
16788
16789             if( score_lo < 0 || score_lo >= 100 ) {
16790                 return text;
16791             }
16792
16793             if(sec >= 0) time = 600*time + 10*sec; else
16794             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16795
16796             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16797
16798             /* [HGM] PV time: now locate end of PV info */
16799             while( *++sep >= '0' && *sep <= '9'); // strip depth
16800             if(time >= 0)
16801             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16802             if(sec >= 0)
16803             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16804             if(deci >= 0)
16805             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16806             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16807         }
16808
16809         if( depth <= 0 ) {
16810             return text;
16811         }
16812
16813         if( time < 0 ) {
16814             time = -1;
16815         }
16816
16817         pvInfoList[index-1].depth = depth;
16818         pvInfoList[index-1].score = score;
16819         pvInfoList[index-1].time  = 10*time; // centi-sec
16820         if(*sep == '}') *sep = 0; else *--sep = '{';
16821         if(p != text) {
16822             while(*p++ = *sep++)
16823                                 ;
16824             sep = text;
16825         } // squeeze out space between PV and comment, and return both
16826     }
16827     return sep;
16828 }
16829
16830 void
16831 SendToProgram (char *message, ChessProgramState *cps)
16832 {
16833     int count, outCount, error;
16834     char buf[MSG_SIZ];
16835
16836     if (cps->pr == NoProc) return;
16837     Attention(cps);
16838
16839     if (appData.debugMode) {
16840         TimeMark now;
16841         GetTimeMark(&now);
16842         fprintf(debugFP, "%ld >%-6s: %s",
16843                 SubtractTimeMarks(&now, &programStartTime),
16844                 cps->which, message);
16845         if(serverFP)
16846             fprintf(serverFP, "%ld >%-6s: %s",
16847                 SubtractTimeMarks(&now, &programStartTime),
16848                 cps->which, message), fflush(serverFP);
16849     }
16850
16851     count = strlen(message);
16852     outCount = OutputToProcess(cps->pr, message, count, &error);
16853     if (outCount < count && !exiting
16854                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16855       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16856       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16857         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16858             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16859                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16860                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16861                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16862             } else {
16863                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16864                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16865                 gameInfo.result = res;
16866             }
16867             gameInfo.resultDetails = StrSave(buf);
16868         }
16869         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16870         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16871     }
16872 }
16873
16874 void
16875 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16876 {
16877     char *end_str;
16878     char buf[MSG_SIZ];
16879     ChessProgramState *cps = (ChessProgramState *)closure;
16880
16881     if (isr != cps->isr) return; /* Killed intentionally */
16882     if (count <= 0) {
16883         if (count == 0) {
16884             RemoveInputSource(cps->isr);
16885             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16886                     _(cps->which), cps->program);
16887             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16888             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16889                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16890                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16891                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16892                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16893                 } else {
16894                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16895                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16896                     gameInfo.result = res;
16897                 }
16898                 gameInfo.resultDetails = StrSave(buf);
16899             }
16900             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16901             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16902         } else {
16903             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16904                     _(cps->which), cps->program);
16905             RemoveInputSource(cps->isr);
16906
16907             /* [AS] Program is misbehaving badly... kill it */
16908             if( count == -2 ) {
16909                 DestroyChildProcess( cps->pr, 9 );
16910                 cps->pr = NoProc;
16911             }
16912
16913             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16914         }
16915         return;
16916     }
16917
16918     if ((end_str = strchr(message, '\r')) != NULL)
16919       *end_str = NULLCHAR;
16920     if ((end_str = strchr(message, '\n')) != NULL)
16921       *end_str = NULLCHAR;
16922
16923     if (appData.debugMode) {
16924         TimeMark now; int print = 1;
16925         char *quote = ""; char c; int i;
16926
16927         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16928                 char start = message[0];
16929                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16930                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16931                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16932                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16933                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16934                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16935                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16936                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16937                    sscanf(message, "hint: %c", &c)!=1 &&
16938                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16939                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16940                     print = (appData.engineComments >= 2);
16941                 }
16942                 message[0] = start; // restore original message
16943         }
16944         if(print) {
16945                 GetTimeMark(&now);
16946                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16947                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16948                         quote,
16949                         message);
16950                 if(serverFP)
16951                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16952                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16953                         quote,
16954                         message), fflush(serverFP);
16955         }
16956     }
16957
16958     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16959     if (appData.icsEngineAnalyze) {
16960         if (strstr(message, "whisper") != NULL ||
16961              strstr(message, "kibitz") != NULL ||
16962             strstr(message, "tellics") != NULL) return;
16963     }
16964
16965     HandleMachineMove(message, cps);
16966 }
16967
16968
16969 void
16970 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16971 {
16972     char buf[MSG_SIZ];
16973     int seconds;
16974
16975     if( timeControl_2 > 0 ) {
16976         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16977             tc = timeControl_2;
16978         }
16979     }
16980     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16981     inc /= cps->timeOdds;
16982     st  /= cps->timeOdds;
16983
16984     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16985
16986     if (st > 0) {
16987       /* Set exact time per move, normally using st command */
16988       if (cps->stKludge) {
16989         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16990         seconds = st % 60;
16991         if (seconds == 0) {
16992           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16993         } else {
16994           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16995         }
16996       } else {
16997         snprintf(buf, MSG_SIZ, "st %d\n", st);
16998       }
16999     } else {
17000       /* Set conventional or incremental time control, using level command */
17001       if (seconds == 0) {
17002         /* Note old gnuchess bug -- minutes:seconds used to not work.
17003            Fixed in later versions, but still avoid :seconds
17004            when seconds is 0. */
17005         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17006       } else {
17007         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17008                  seconds, inc/1000.);
17009       }
17010     }
17011     SendToProgram(buf, cps);
17012
17013     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17014     /* Orthogonally, limit search to given depth */
17015     if (sd > 0) {
17016       if (cps->sdKludge) {
17017         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17018       } else {
17019         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17020       }
17021       SendToProgram(buf, cps);
17022     }
17023
17024     if(cps->nps >= 0) { /* [HGM] nps */
17025         if(cps->supportsNPS == FALSE)
17026           cps->nps = -1; // don't use if engine explicitly says not supported!
17027         else {
17028           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17029           SendToProgram(buf, cps);
17030         }
17031     }
17032 }
17033
17034 ChessProgramState *
17035 WhitePlayer ()
17036 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17037 {
17038     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17039        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17040         return &second;
17041     return &first;
17042 }
17043
17044 void
17045 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17046 {
17047     char message[MSG_SIZ];
17048     long time, otime;
17049
17050     /* Note: this routine must be called when the clocks are stopped
17051        or when they have *just* been set or switched; otherwise
17052        it will be off by the time since the current tick started.
17053     */
17054     if (machineWhite) {
17055         time = whiteTimeRemaining / 10;
17056         otime = blackTimeRemaining / 10;
17057     } else {
17058         time = blackTimeRemaining / 10;
17059         otime = whiteTimeRemaining / 10;
17060     }
17061     /* [HGM] translate opponent's time by time-odds factor */
17062     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17063
17064     if (time <= 0) time = 1;
17065     if (otime <= 0) otime = 1;
17066
17067     snprintf(message, MSG_SIZ, "time %ld\n", time);
17068     SendToProgram(message, cps);
17069
17070     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17071     SendToProgram(message, cps);
17072 }
17073
17074 char *
17075 EngineDefinedVariant (ChessProgramState *cps, int n)
17076 {   // return name of n-th unknown variant that engine supports
17077     static char buf[MSG_SIZ];
17078     char *p, *s = cps->variants;
17079     if(!s) return NULL;
17080     do { // parse string from variants feature
17081       VariantClass v;
17082         p = strchr(s, ',');
17083         if(p) *p = NULLCHAR;
17084       v = StringToVariant(s);
17085       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17086         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17087             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17088                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17089                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17090                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17091             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17092         }
17093         if(p) *p++ = ',';
17094         if(n < 0) return buf;
17095     } while(s = p);
17096     return NULL;
17097 }
17098
17099 int
17100 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17101 {
17102   char buf[MSG_SIZ];
17103   int len = strlen(name);
17104   int val;
17105
17106   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17107     (*p) += len + 1;
17108     sscanf(*p, "%d", &val);
17109     *loc = (val != 0);
17110     while (**p && **p != ' ')
17111       (*p)++;
17112     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17113     SendToProgram(buf, cps);
17114     return TRUE;
17115   }
17116   return FALSE;
17117 }
17118
17119 int
17120 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17121 {
17122   char buf[MSG_SIZ];
17123   int len = strlen(name);
17124   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17125     (*p) += len + 1;
17126     sscanf(*p, "%d", loc);
17127     while (**p && **p != ' ') (*p)++;
17128     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17129     SendToProgram(buf, cps);
17130     return TRUE;
17131   }
17132   return FALSE;
17133 }
17134
17135 int
17136 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17137 {
17138   char buf[MSG_SIZ];
17139   int len = strlen(name);
17140   if (strncmp((*p), name, len) == 0
17141       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17142     (*p) += len + 2;
17143     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17144     sscanf(*p, "%[^\"]", *loc);
17145     while (**p && **p != '\"') (*p)++;
17146     if (**p == '\"') (*p)++;
17147     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17148     SendToProgram(buf, cps);
17149     return TRUE;
17150   }
17151   return FALSE;
17152 }
17153
17154 int
17155 ParseOption (Option *opt, ChessProgramState *cps)
17156 // [HGM] options: process the string that defines an engine option, and determine
17157 // name, type, default value, and allowed value range
17158 {
17159         char *p, *q, buf[MSG_SIZ];
17160         int n, min = (-1)<<31, max = 1<<31, def;
17161
17162         opt->target = &opt->value;   // OK for spin/slider and checkbox
17163         if(p = strstr(opt->name, " -spin ")) {
17164             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17165             if(max < min) max = min; // enforce consistency
17166             if(def < min) def = min;
17167             if(def > max) def = max;
17168             opt->value = def;
17169             opt->min = min;
17170             opt->max = max;
17171             opt->type = Spin;
17172         } else if((p = strstr(opt->name, " -slider "))) {
17173             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17174             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17175             if(max < min) max = min; // enforce consistency
17176             if(def < min) def = min;
17177             if(def > max) def = max;
17178             opt->value = def;
17179             opt->min = min;
17180             opt->max = max;
17181             opt->type = Spin; // Slider;
17182         } else if((p = strstr(opt->name, " -string "))) {
17183             opt->textValue = p+9;
17184             opt->type = TextBox;
17185             opt->target = &opt->textValue;
17186         } else if((p = strstr(opt->name, " -file "))) {
17187             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17188             opt->target = opt->textValue = p+7;
17189             opt->type = FileName; // FileName;
17190             opt->target = &opt->textValue;
17191         } else if((p = strstr(opt->name, " -path "))) {
17192             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17193             opt->target = opt->textValue = p+7;
17194             opt->type = PathName; // PathName;
17195             opt->target = &opt->textValue;
17196         } else if(p = strstr(opt->name, " -check ")) {
17197             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17198             opt->value = (def != 0);
17199             opt->type = CheckBox;
17200         } else if(p = strstr(opt->name, " -combo ")) {
17201             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17202             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17203             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17204             opt->value = n = 0;
17205             while(q = StrStr(q, " /// ")) {
17206                 n++; *q = 0;    // count choices, and null-terminate each of them
17207                 q += 5;
17208                 if(*q == '*') { // remember default, which is marked with * prefix
17209                     q++;
17210                     opt->value = n;
17211                 }
17212                 cps->comboList[cps->comboCnt++] = q;
17213             }
17214             cps->comboList[cps->comboCnt++] = NULL;
17215             opt->max = n + 1;
17216             opt->type = ComboBox;
17217         } else if(p = strstr(opt->name, " -button")) {
17218             opt->type = Button;
17219         } else if(p = strstr(opt->name, " -save")) {
17220             opt->type = SaveButton;
17221         } else return FALSE;
17222         *p = 0; // terminate option name
17223         // now look if the command-line options define a setting for this engine option.
17224         if(cps->optionSettings && cps->optionSettings[0])
17225             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17226         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17227           snprintf(buf, MSG_SIZ, "option %s", p);
17228                 if(p = strstr(buf, ",")) *p = 0;
17229                 if(q = strchr(buf, '=')) switch(opt->type) {
17230                     case ComboBox:
17231                         for(n=0; n<opt->max; n++)
17232                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17233                         break;
17234                     case TextBox:
17235                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17236                         break;
17237                     case Spin:
17238                     case CheckBox:
17239                         opt->value = atoi(q+1);
17240                     default:
17241                         break;
17242                 }
17243                 strcat(buf, "\n");
17244                 SendToProgram(buf, cps);
17245         }
17246         return TRUE;
17247 }
17248
17249 void
17250 FeatureDone (ChessProgramState *cps, int val)
17251 {
17252   DelayedEventCallback cb = GetDelayedEvent();
17253   if ((cb == InitBackEnd3 && cps == &first) ||
17254       (cb == SettingsMenuIfReady && cps == &second) ||
17255       (cb == LoadEngine) ||
17256       (cb == TwoMachinesEventIfReady)) {
17257     CancelDelayedEvent();
17258     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17259   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17260   cps->initDone = val;
17261   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17262 }
17263
17264 /* Parse feature command from engine */
17265 void
17266 ParseFeatures (char *args, ChessProgramState *cps)
17267 {
17268   char *p = args;
17269   char *q = NULL;
17270   int val;
17271   char buf[MSG_SIZ];
17272
17273   for (;;) {
17274     while (*p == ' ') p++;
17275     if (*p == NULLCHAR) return;
17276
17277     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17278     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17279     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17280     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17281     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17282     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17283     if (BoolFeature(&p, "reuse", &val, cps)) {
17284       /* Engine can disable reuse, but can't enable it if user said no */
17285       if (!val) cps->reuse = FALSE;
17286       continue;
17287     }
17288     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17289     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17290       if (gameMode == TwoMachinesPlay) {
17291         DisplayTwoMachinesTitle();
17292       } else {
17293         DisplayTitle("");
17294       }
17295       continue;
17296     }
17297     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17298     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17299     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17300     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17301     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17302     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17303     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17304     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17305     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17306     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17307     if (IntFeature(&p, "done", &val, cps)) {
17308       FeatureDone(cps, val);
17309       continue;
17310     }
17311     /* Added by Tord: */
17312     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17313     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17314     /* End of additions by Tord */
17315
17316     /* [HGM] added features: */
17317     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17318     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17319     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17320     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17321     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17322     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17323     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17324     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17325         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17326         FREE(cps->option[cps->nrOptions].name);
17327         cps->option[cps->nrOptions].name = q; q = NULL;
17328         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17329           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17330             SendToProgram(buf, cps);
17331             continue;
17332         }
17333         if(cps->nrOptions >= MAX_OPTIONS) {
17334             cps->nrOptions--;
17335             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17336             DisplayError(buf, 0);
17337         }
17338         continue;
17339     }
17340     /* End of additions by HGM */
17341
17342     /* unknown feature: complain and skip */
17343     q = p;
17344     while (*q && *q != '=') q++;
17345     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17346     SendToProgram(buf, cps);
17347     p = q;
17348     if (*p == '=') {
17349       p++;
17350       if (*p == '\"') {
17351         p++;
17352         while (*p && *p != '\"') p++;
17353         if (*p == '\"') p++;
17354       } else {
17355         while (*p && *p != ' ') p++;
17356       }
17357     }
17358   }
17359
17360 }
17361
17362 void
17363 PeriodicUpdatesEvent (int newState)
17364 {
17365     if (newState == appData.periodicUpdates)
17366       return;
17367
17368     appData.periodicUpdates=newState;
17369
17370     /* Display type changes, so update it now */
17371 //    DisplayAnalysis();
17372
17373     /* Get the ball rolling again... */
17374     if (newState) {
17375         AnalysisPeriodicEvent(1);
17376         StartAnalysisClock();
17377     }
17378 }
17379
17380 void
17381 PonderNextMoveEvent (int newState)
17382 {
17383     if (newState == appData.ponderNextMove) return;
17384     if (gameMode == EditPosition) EditPositionDone(TRUE);
17385     if (newState) {
17386         SendToProgram("hard\n", &first);
17387         if (gameMode == TwoMachinesPlay) {
17388             SendToProgram("hard\n", &second);
17389         }
17390     } else {
17391         SendToProgram("easy\n", &first);
17392         thinkOutput[0] = NULLCHAR;
17393         if (gameMode == TwoMachinesPlay) {
17394             SendToProgram("easy\n", &second);
17395         }
17396     }
17397     appData.ponderNextMove = newState;
17398 }
17399
17400 void
17401 NewSettingEvent (int option, int *feature, char *command, int value)
17402 {
17403     char buf[MSG_SIZ];
17404
17405     if (gameMode == EditPosition) EditPositionDone(TRUE);
17406     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17407     if(feature == NULL || *feature) SendToProgram(buf, &first);
17408     if (gameMode == TwoMachinesPlay) {
17409         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17410     }
17411 }
17412
17413 void
17414 ShowThinkingEvent ()
17415 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17416 {
17417     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17418     int newState = appData.showThinking
17419         // [HGM] thinking: other features now need thinking output as well
17420         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17421
17422     if (oldState == newState) return;
17423     oldState = newState;
17424     if (gameMode == EditPosition) EditPositionDone(TRUE);
17425     if (oldState) {
17426         SendToProgram("post\n", &first);
17427         if (gameMode == TwoMachinesPlay) {
17428             SendToProgram("post\n", &second);
17429         }
17430     } else {
17431         SendToProgram("nopost\n", &first);
17432         thinkOutput[0] = NULLCHAR;
17433         if (gameMode == TwoMachinesPlay) {
17434             SendToProgram("nopost\n", &second);
17435         }
17436     }
17437 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17438 }
17439
17440 void
17441 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17442 {
17443   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17444   if (pr == NoProc) return;
17445   AskQuestion(title, question, replyPrefix, pr);
17446 }
17447
17448 void
17449 TypeInEvent (char firstChar)
17450 {
17451     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17452         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17453         gameMode == AnalyzeMode || gameMode == EditGame ||
17454         gameMode == EditPosition || gameMode == IcsExamining ||
17455         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17456         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17457                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17458                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17459         gameMode == Training) PopUpMoveDialog(firstChar);
17460 }
17461
17462 void
17463 TypeInDoneEvent (char *move)
17464 {
17465         Board board;
17466         int n, fromX, fromY, toX, toY;
17467         char promoChar;
17468         ChessMove moveType;
17469
17470         // [HGM] FENedit
17471         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17472                 EditPositionPasteFEN(move);
17473                 return;
17474         }
17475         // [HGM] movenum: allow move number to be typed in any mode
17476         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17477           ToNrEvent(2*n-1);
17478           return;
17479         }
17480         // undocumented kludge: allow command-line option to be typed in!
17481         // (potentially fatal, and does not implement the effect of the option.)
17482         // should only be used for options that are values on which future decisions will be made,
17483         // and definitely not on options that would be used during initialization.
17484         if(strstr(move, "!!! -") == move) {
17485             ParseArgsFromString(move+4);
17486             return;
17487         }
17488
17489       if (gameMode != EditGame && currentMove != forwardMostMove &&
17490         gameMode != Training) {
17491         DisplayMoveError(_("Displayed move is not current"));
17492       } else {
17493         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17494           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17495         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17496         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17497           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17498           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17499         } else {
17500           DisplayMoveError(_("Could not parse move"));
17501         }
17502       }
17503 }
17504
17505 void
17506 DisplayMove (int moveNumber)
17507 {
17508     char message[MSG_SIZ];
17509     char res[MSG_SIZ];
17510     char cpThinkOutput[MSG_SIZ];
17511
17512     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17513
17514     if (moveNumber == forwardMostMove - 1 ||
17515         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17516
17517         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17518
17519         if (strchr(cpThinkOutput, '\n')) {
17520             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17521         }
17522     } else {
17523         *cpThinkOutput = NULLCHAR;
17524     }
17525
17526     /* [AS] Hide thinking from human user */
17527     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17528         *cpThinkOutput = NULLCHAR;
17529         if( thinkOutput[0] != NULLCHAR ) {
17530             int i;
17531
17532             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17533                 cpThinkOutput[i] = '.';
17534             }
17535             cpThinkOutput[i] = NULLCHAR;
17536             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17537         }
17538     }
17539
17540     if (moveNumber == forwardMostMove - 1 &&
17541         gameInfo.resultDetails != NULL) {
17542         if (gameInfo.resultDetails[0] == NULLCHAR) {
17543           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17544         } else {
17545           snprintf(res, MSG_SIZ, " {%s} %s",
17546                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17547         }
17548     } else {
17549         res[0] = NULLCHAR;
17550     }
17551
17552     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17553         DisplayMessage(res, cpThinkOutput);
17554     } else {
17555       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17556                 WhiteOnMove(moveNumber) ? " " : ".. ",
17557                 parseList[moveNumber], res);
17558         DisplayMessage(message, cpThinkOutput);
17559     }
17560 }
17561
17562 void
17563 DisplayComment (int moveNumber, char *text)
17564 {
17565     char title[MSG_SIZ];
17566
17567     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17568       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17569     } else {
17570       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17571               WhiteOnMove(moveNumber) ? " " : ".. ",
17572               parseList[moveNumber]);
17573     }
17574     if (text != NULL && (appData.autoDisplayComment || commentUp))
17575         CommentPopUp(title, text);
17576 }
17577
17578 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17579  * might be busy thinking or pondering.  It can be omitted if your
17580  * gnuchess is configured to stop thinking immediately on any user
17581  * input.  However, that gnuchess feature depends on the FIONREAD
17582  * ioctl, which does not work properly on some flavors of Unix.
17583  */
17584 void
17585 Attention (ChessProgramState *cps)
17586 {
17587 #if ATTENTION
17588     if (!cps->useSigint) return;
17589     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17590     switch (gameMode) {
17591       case MachinePlaysWhite:
17592       case MachinePlaysBlack:
17593       case TwoMachinesPlay:
17594       case IcsPlayingWhite:
17595       case IcsPlayingBlack:
17596       case AnalyzeMode:
17597       case AnalyzeFile:
17598         /* Skip if we know it isn't thinking */
17599         if (!cps->maybeThinking) return;
17600         if (appData.debugMode)
17601           fprintf(debugFP, "Interrupting %s\n", cps->which);
17602         InterruptChildProcess(cps->pr);
17603         cps->maybeThinking = FALSE;
17604         break;
17605       default:
17606         break;
17607     }
17608 #endif /*ATTENTION*/
17609 }
17610
17611 int
17612 CheckFlags ()
17613 {
17614     if (whiteTimeRemaining <= 0) {
17615         if (!whiteFlag) {
17616             whiteFlag = TRUE;
17617             if (appData.icsActive) {
17618                 if (appData.autoCallFlag &&
17619                     gameMode == IcsPlayingBlack && !blackFlag) {
17620                   SendToICS(ics_prefix);
17621                   SendToICS("flag\n");
17622                 }
17623             } else {
17624                 if (blackFlag) {
17625                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17626                 } else {
17627                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17628                     if (appData.autoCallFlag) {
17629                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17630                         return TRUE;
17631                     }
17632                 }
17633             }
17634         }
17635     }
17636     if (blackTimeRemaining <= 0) {
17637         if (!blackFlag) {
17638             blackFlag = TRUE;
17639             if (appData.icsActive) {
17640                 if (appData.autoCallFlag &&
17641                     gameMode == IcsPlayingWhite && !whiteFlag) {
17642                   SendToICS(ics_prefix);
17643                   SendToICS("flag\n");
17644                 }
17645             } else {
17646                 if (whiteFlag) {
17647                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17648                 } else {
17649                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17650                     if (appData.autoCallFlag) {
17651                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17652                         return TRUE;
17653                     }
17654                 }
17655             }
17656         }
17657     }
17658     return FALSE;
17659 }
17660
17661 void
17662 CheckTimeControl ()
17663 {
17664     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17665         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17666
17667     /*
17668      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17669      */
17670     if ( !WhiteOnMove(forwardMostMove) ) {
17671         /* White made time control */
17672         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17673         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17674         /* [HGM] time odds: correct new time quota for time odds! */
17675                                             / WhitePlayer()->timeOdds;
17676         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17677     } else {
17678         lastBlack -= blackTimeRemaining;
17679         /* Black made time control */
17680         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17681                                             / WhitePlayer()->other->timeOdds;
17682         lastWhite = whiteTimeRemaining;
17683     }
17684 }
17685
17686 void
17687 DisplayBothClocks ()
17688 {
17689     int wom = gameMode == EditPosition ?
17690       !blackPlaysFirst : WhiteOnMove(currentMove);
17691     DisplayWhiteClock(whiteTimeRemaining, wom);
17692     DisplayBlackClock(blackTimeRemaining, !wom);
17693 }
17694
17695
17696 /* Timekeeping seems to be a portability nightmare.  I think everyone
17697    has ftime(), but I'm really not sure, so I'm including some ifdefs
17698    to use other calls if you don't.  Clocks will be less accurate if
17699    you have neither ftime nor gettimeofday.
17700 */
17701
17702 /* VS 2008 requires the #include outside of the function */
17703 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17704 #include <sys/timeb.h>
17705 #endif
17706
17707 /* Get the current time as a TimeMark */
17708 void
17709 GetTimeMark (TimeMark *tm)
17710 {
17711 #if HAVE_GETTIMEOFDAY
17712
17713     struct timeval timeVal;
17714     struct timezone timeZone;
17715
17716     gettimeofday(&timeVal, &timeZone);
17717     tm->sec = (long) timeVal.tv_sec;
17718     tm->ms = (int) (timeVal.tv_usec / 1000L);
17719
17720 #else /*!HAVE_GETTIMEOFDAY*/
17721 #if HAVE_FTIME
17722
17723 // include <sys/timeb.h> / moved to just above start of function
17724     struct timeb timeB;
17725
17726     ftime(&timeB);
17727     tm->sec = (long) timeB.time;
17728     tm->ms = (int) timeB.millitm;
17729
17730 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17731     tm->sec = (long) time(NULL);
17732     tm->ms = 0;
17733 #endif
17734 #endif
17735 }
17736
17737 /* Return the difference in milliseconds between two
17738    time marks.  We assume the difference will fit in a long!
17739 */
17740 long
17741 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17742 {
17743     return 1000L*(tm2->sec - tm1->sec) +
17744            (long) (tm2->ms - tm1->ms);
17745 }
17746
17747
17748 /*
17749  * Code to manage the game clocks.
17750  *
17751  * In tournament play, black starts the clock and then white makes a move.
17752  * We give the human user a slight advantage if he is playing white---the
17753  * clocks don't run until he makes his first move, so it takes zero time.
17754  * Also, we don't account for network lag, so we could get out of sync
17755  * with GNU Chess's clock -- but then, referees are always right.
17756  */
17757
17758 static TimeMark tickStartTM;
17759 static long intendedTickLength;
17760
17761 long
17762 NextTickLength (long timeRemaining)
17763 {
17764     long nominalTickLength, nextTickLength;
17765
17766     if (timeRemaining > 0L && timeRemaining <= 10000L)
17767       nominalTickLength = 100L;
17768     else
17769       nominalTickLength = 1000L;
17770     nextTickLength = timeRemaining % nominalTickLength;
17771     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17772
17773     return nextTickLength;
17774 }
17775
17776 /* Adjust clock one minute up or down */
17777 void
17778 AdjustClock (Boolean which, int dir)
17779 {
17780     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17781     if(which) blackTimeRemaining += 60000*dir;
17782     else      whiteTimeRemaining += 60000*dir;
17783     DisplayBothClocks();
17784     adjustedClock = TRUE;
17785 }
17786
17787 /* Stop clocks and reset to a fresh time control */
17788 void
17789 ResetClocks ()
17790 {
17791     (void) StopClockTimer();
17792     if (appData.icsActive) {
17793         whiteTimeRemaining = blackTimeRemaining = 0;
17794     } else if (searchTime) {
17795         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17796         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17797     } else { /* [HGM] correct new time quote for time odds */
17798         whiteTC = blackTC = fullTimeControlString;
17799         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17800         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17801     }
17802     if (whiteFlag || blackFlag) {
17803         DisplayTitle("");
17804         whiteFlag = blackFlag = FALSE;
17805     }
17806     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17807     DisplayBothClocks();
17808     adjustedClock = FALSE;
17809 }
17810
17811 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17812
17813 /* Decrement running clock by amount of time that has passed */
17814 void
17815 DecrementClocks ()
17816 {
17817     long timeRemaining;
17818     long lastTickLength, fudge;
17819     TimeMark now;
17820
17821     if (!appData.clockMode) return;
17822     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17823
17824     GetTimeMark(&now);
17825
17826     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17827
17828     /* Fudge if we woke up a little too soon */
17829     fudge = intendedTickLength - lastTickLength;
17830     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17831
17832     if (WhiteOnMove(forwardMostMove)) {
17833         if(whiteNPS >= 0) lastTickLength = 0;
17834         timeRemaining = whiteTimeRemaining -= lastTickLength;
17835         if(timeRemaining < 0 && !appData.icsActive) {
17836             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17837             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17838                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17839                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17840             }
17841         }
17842         DisplayWhiteClock(whiteTimeRemaining - fudge,
17843                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17844     } else {
17845         if(blackNPS >= 0) lastTickLength = 0;
17846         timeRemaining = blackTimeRemaining -= lastTickLength;
17847         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17848             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17849             if(suddenDeath) {
17850                 blackStartMove = forwardMostMove;
17851                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17852             }
17853         }
17854         DisplayBlackClock(blackTimeRemaining - fudge,
17855                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17856     }
17857     if (CheckFlags()) return;
17858
17859     if(twoBoards) { // count down secondary board's clocks as well
17860         activePartnerTime -= lastTickLength;
17861         partnerUp = 1;
17862         if(activePartner == 'W')
17863             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17864         else
17865             DisplayBlackClock(activePartnerTime, TRUE);
17866         partnerUp = 0;
17867     }
17868
17869     tickStartTM = now;
17870     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17871     StartClockTimer(intendedTickLength);
17872
17873     /* if the time remaining has fallen below the alarm threshold, sound the
17874      * alarm. if the alarm has sounded and (due to a takeback or time control
17875      * with increment) the time remaining has increased to a level above the
17876      * threshold, reset the alarm so it can sound again.
17877      */
17878
17879     if (appData.icsActive && appData.icsAlarm) {
17880
17881         /* make sure we are dealing with the user's clock */
17882         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17883                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17884            )) return;
17885
17886         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17887             alarmSounded = FALSE;
17888         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17889             PlayAlarmSound();
17890             alarmSounded = TRUE;
17891         }
17892     }
17893 }
17894
17895
17896 /* A player has just moved, so stop the previously running
17897    clock and (if in clock mode) start the other one.
17898    We redisplay both clocks in case we're in ICS mode, because
17899    ICS gives us an update to both clocks after every move.
17900    Note that this routine is called *after* forwardMostMove
17901    is updated, so the last fractional tick must be subtracted
17902    from the color that is *not* on move now.
17903 */
17904 void
17905 SwitchClocks (int newMoveNr)
17906 {
17907     long lastTickLength;
17908     TimeMark now;
17909     int flagged = FALSE;
17910
17911     GetTimeMark(&now);
17912
17913     if (StopClockTimer() && appData.clockMode) {
17914         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17915         if (!WhiteOnMove(forwardMostMove)) {
17916             if(blackNPS >= 0) lastTickLength = 0;
17917             blackTimeRemaining -= lastTickLength;
17918            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17919 //         if(pvInfoList[forwardMostMove].time == -1)
17920                  pvInfoList[forwardMostMove].time =               // use GUI time
17921                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17922         } else {
17923            if(whiteNPS >= 0) lastTickLength = 0;
17924            whiteTimeRemaining -= lastTickLength;
17925            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17926 //         if(pvInfoList[forwardMostMove].time == -1)
17927                  pvInfoList[forwardMostMove].time =
17928                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17929         }
17930         flagged = CheckFlags();
17931     }
17932     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17933     CheckTimeControl();
17934
17935     if (flagged || !appData.clockMode) return;
17936
17937     switch (gameMode) {
17938       case MachinePlaysBlack:
17939       case MachinePlaysWhite:
17940       case BeginningOfGame:
17941         if (pausing) return;
17942         break;
17943
17944       case EditGame:
17945       case PlayFromGameFile:
17946       case IcsExamining:
17947         return;
17948
17949       default:
17950         break;
17951     }
17952
17953     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17954         if(WhiteOnMove(forwardMostMove))
17955              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17956         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17957     }
17958
17959     tickStartTM = now;
17960     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17961       whiteTimeRemaining : blackTimeRemaining);
17962     StartClockTimer(intendedTickLength);
17963 }
17964
17965
17966 /* Stop both clocks */
17967 void
17968 StopClocks ()
17969 {
17970     long lastTickLength;
17971     TimeMark now;
17972
17973     if (!StopClockTimer()) return;
17974     if (!appData.clockMode) return;
17975
17976     GetTimeMark(&now);
17977
17978     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17979     if (WhiteOnMove(forwardMostMove)) {
17980         if(whiteNPS >= 0) lastTickLength = 0;
17981         whiteTimeRemaining -= lastTickLength;
17982         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17983     } else {
17984         if(blackNPS >= 0) lastTickLength = 0;
17985         blackTimeRemaining -= lastTickLength;
17986         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17987     }
17988     CheckFlags();
17989 }
17990
17991 /* Start clock of player on move.  Time may have been reset, so
17992    if clock is already running, stop and restart it. */
17993 void
17994 StartClocks ()
17995 {
17996     (void) StopClockTimer(); /* in case it was running already */
17997     DisplayBothClocks();
17998     if (CheckFlags()) return;
17999
18000     if (!appData.clockMode) return;
18001     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18002
18003     GetTimeMark(&tickStartTM);
18004     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18005       whiteTimeRemaining : blackTimeRemaining);
18006
18007    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18008     whiteNPS = blackNPS = -1;
18009     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18010        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18011         whiteNPS = first.nps;
18012     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18013        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18014         blackNPS = first.nps;
18015     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18016         whiteNPS = second.nps;
18017     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18018         blackNPS = second.nps;
18019     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18020
18021     StartClockTimer(intendedTickLength);
18022 }
18023
18024 char *
18025 TimeString (long ms)
18026 {
18027     long second, minute, hour, day;
18028     char *sign = "";
18029     static char buf[32];
18030
18031     if (ms > 0 && ms <= 9900) {
18032       /* convert milliseconds to tenths, rounding up */
18033       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18034
18035       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18036       return buf;
18037     }
18038
18039     /* convert milliseconds to seconds, rounding up */
18040     /* use floating point to avoid strangeness of integer division
18041        with negative dividends on many machines */
18042     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18043
18044     if (second < 0) {
18045         sign = "-";
18046         second = -second;
18047     }
18048
18049     day = second / (60 * 60 * 24);
18050     second = second % (60 * 60 * 24);
18051     hour = second / (60 * 60);
18052     second = second % (60 * 60);
18053     minute = second / 60;
18054     second = second % 60;
18055
18056     if (day > 0)
18057       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
18058               sign, day, hour, minute, second);
18059     else if (hour > 0)
18060       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
18061     else
18062       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
18063
18064     return buf;
18065 }
18066
18067
18068 /*
18069  * This is necessary because some C libraries aren't ANSI C compliant yet.
18070  */
18071 char *
18072 StrStr (char *string, char *match)
18073 {
18074     int i, length;
18075
18076     length = strlen(match);
18077
18078     for (i = strlen(string) - length; i >= 0; i--, string++)
18079       if (!strncmp(match, string, length))
18080         return string;
18081
18082     return NULL;
18083 }
18084
18085 char *
18086 StrCaseStr (char *string, char *match)
18087 {
18088     int i, j, length;
18089
18090     length = strlen(match);
18091
18092     for (i = strlen(string) - length; i >= 0; i--, string++) {
18093         for (j = 0; j < length; j++) {
18094             if (ToLower(match[j]) != ToLower(string[j]))
18095               break;
18096         }
18097         if (j == length) return string;
18098     }
18099
18100     return NULL;
18101 }
18102
18103 #ifndef _amigados
18104 int
18105 StrCaseCmp (char *s1, char *s2)
18106 {
18107     char c1, c2;
18108
18109     for (;;) {
18110         c1 = ToLower(*s1++);
18111         c2 = ToLower(*s2++);
18112         if (c1 > c2) return 1;
18113         if (c1 < c2) return -1;
18114         if (c1 == NULLCHAR) return 0;
18115     }
18116 }
18117
18118
18119 int
18120 ToLower (int c)
18121 {
18122     return isupper(c) ? tolower(c) : c;
18123 }
18124
18125
18126 int
18127 ToUpper (int c)
18128 {
18129     return islower(c) ? toupper(c) : c;
18130 }
18131 #endif /* !_amigados    */
18132
18133 char *
18134 StrSave (char *s)
18135 {
18136   char *ret;
18137
18138   if ((ret = (char *) malloc(strlen(s) + 1)))
18139     {
18140       safeStrCpy(ret, s, strlen(s)+1);
18141     }
18142   return ret;
18143 }
18144
18145 char *
18146 StrSavePtr (char *s, char **savePtr)
18147 {
18148     if (*savePtr) {
18149         free(*savePtr);
18150     }
18151     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18152       safeStrCpy(*savePtr, s, strlen(s)+1);
18153     }
18154     return(*savePtr);
18155 }
18156
18157 char *
18158 PGNDate ()
18159 {
18160     time_t clock;
18161     struct tm *tm;
18162     char buf[MSG_SIZ];
18163
18164     clock = time((time_t *)NULL);
18165     tm = localtime(&clock);
18166     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18167             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18168     return StrSave(buf);
18169 }
18170
18171
18172 char *
18173 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18174 {
18175     int i, j, fromX, fromY, toX, toY;
18176     int whiteToPlay, haveRights = nrCastlingRights;
18177     char buf[MSG_SIZ];
18178     char *p, *q;
18179     int emptycount;
18180     ChessSquare piece;
18181
18182     whiteToPlay = (gameMode == EditPosition) ?
18183       !blackPlaysFirst : (move % 2 == 0);
18184     p = buf;
18185
18186     /* Piece placement data */
18187     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18188         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18189         emptycount = 0;
18190         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18191             if (boards[move][i][j] == EmptySquare) {
18192                 emptycount++;
18193             } else { ChessSquare piece = boards[move][i][j];
18194                 if (emptycount > 0) {
18195                     if(emptycount<10) /* [HGM] can be >= 10 */
18196                         *p++ = '0' + emptycount;
18197                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18198                     emptycount = 0;
18199                 }
18200                 if(PieceToChar(piece) == '+') {
18201                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18202                     *p++ = '+';
18203                     piece = (ChessSquare)(CHUDEMOTED(piece));
18204                 }
18205                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18206                 if(*p = PieceSuffix(piece)) p++;
18207                 if(p[-1] == '~') {
18208                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18209                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18210                     *p++ = '~';
18211                 }
18212             }
18213         }
18214         if (emptycount > 0) {
18215             if(emptycount<10) /* [HGM] can be >= 10 */
18216                 *p++ = '0' + emptycount;
18217             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18218             emptycount = 0;
18219         }
18220         *p++ = '/';
18221     }
18222     *(p - 1) = ' ';
18223
18224     /* [HGM] print Crazyhouse or Shogi holdings */
18225     if( gameInfo.holdingsWidth ) {
18226         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18227         q = p;
18228         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18229             piece = boards[move][i][BOARD_WIDTH-1];
18230             if( piece != EmptySquare )
18231               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18232                   *p++ = PieceToChar(piece);
18233         }
18234         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18235             piece = boards[move][BOARD_HEIGHT-i-1][0];
18236             if( piece != EmptySquare )
18237               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18238                   *p++ = PieceToChar(piece);
18239         }
18240
18241         if( q == p ) *p++ = '-';
18242         *p++ = ']';
18243         *p++ = ' ';
18244     }
18245
18246     /* Active color */
18247     *p++ = whiteToPlay ? 'w' : 'b';
18248     *p++ = ' ';
18249
18250   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18251     haveRights = 0; q = p;
18252     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18253       piece = boards[move][0][i];
18254       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18255         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18256       }
18257     }
18258     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18259       piece = boards[move][BOARD_HEIGHT-1][i];
18260       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18261         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18262       }
18263     }
18264     if(p == q) *p++ = '-';
18265     *p++ = ' ';
18266   }
18267
18268   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18269     while(*p++ = *q++)
18270                       ;
18271     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18272   } else {
18273   if(haveRights) {
18274      int handW=0, handB=0;
18275      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18276         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18277         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18278      }
18279      q = p;
18280      if(appData.fischerCastling) {
18281         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18282            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18283                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18284         } else {
18285        /* [HGM] write directly from rights */
18286            if(boards[move][CASTLING][2] != NoRights &&
18287               boards[move][CASTLING][0] != NoRights   )
18288                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18289            if(boards[move][CASTLING][2] != NoRights &&
18290               boards[move][CASTLING][1] != NoRights   )
18291                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18292         }
18293         if(handB) {
18294            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18295                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18296         } else {
18297            if(boards[move][CASTLING][5] != NoRights &&
18298               boards[move][CASTLING][3] != NoRights   )
18299                 *p++ = boards[move][CASTLING][3] + AAA;
18300            if(boards[move][CASTLING][5] != NoRights &&
18301               boards[move][CASTLING][4] != NoRights   )
18302                 *p++ = boards[move][CASTLING][4] + AAA;
18303         }
18304      } else {
18305
18306         /* [HGM] write true castling rights */
18307         if( nrCastlingRights == 6 ) {
18308             int q, k=0;
18309             if(boards[move][CASTLING][0] != NoRights &&
18310                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18311             q = (boards[move][CASTLING][1] != NoRights &&
18312                  boards[move][CASTLING][2] != NoRights  );
18313             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18314                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18315                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18316                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18317             }
18318             if(q) *p++ = 'Q';
18319             k = 0;
18320             if(boards[move][CASTLING][3] != NoRights &&
18321                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18322             q = (boards[move][CASTLING][4] != NoRights &&
18323                  boards[move][CASTLING][5] != NoRights  );
18324             if(handB) {
18325                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18326                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18327                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18328             }
18329             if(q) *p++ = 'q';
18330         }
18331      }
18332      if (q == p) *p++ = '-'; /* No castling rights */
18333      *p++ = ' ';
18334   }
18335
18336   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18337      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18338      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18339     /* En passant target square */
18340     if (move > backwardMostMove) {
18341         fromX = moveList[move - 1][0] - AAA;
18342         fromY = moveList[move - 1][1] - ONE;
18343         toX = moveList[move - 1][2] - AAA;
18344         toY = moveList[move - 1][3] - ONE;
18345         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18346             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18347             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18348             fromX == toX) {
18349             /* 2-square pawn move just happened */
18350             *p++ = toX + AAA;
18351             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18352         } else {
18353             *p++ = '-';
18354         }
18355     } else if(move == backwardMostMove) {
18356         // [HGM] perhaps we should always do it like this, and forget the above?
18357         if((signed char)boards[move][EP_STATUS] >= 0) {
18358             *p++ = boards[move][EP_STATUS] + AAA;
18359             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18360         } else {
18361             *p++ = '-';
18362         }
18363     } else {
18364         *p++ = '-';
18365     }
18366     *p++ = ' ';
18367   }
18368   }
18369
18370     if(moveCounts)
18371     {   int i = 0, j=move;
18372
18373         /* [HGM] find reversible plies */
18374         if (appData.debugMode) { int k;
18375             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18376             for(k=backwardMostMove; k<=forwardMostMove; k++)
18377                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18378
18379         }
18380
18381         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18382         if( j == backwardMostMove ) i += initialRulePlies;
18383         sprintf(p, "%d ", i);
18384         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18385
18386         /* Fullmove number */
18387         sprintf(p, "%d", (move / 2) + 1);
18388     } else *--p = NULLCHAR;
18389
18390     return StrSave(buf);
18391 }
18392
18393 Boolean
18394 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18395 {
18396     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18397     char *p, c;
18398     int emptycount, virgin[BOARD_FILES];
18399     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18400
18401     p = fen;
18402
18403     /* Piece placement data */
18404     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18405         j = 0;
18406         for (;;) {
18407             if (*p == '/' || *p == ' ' || *p == '[' ) {
18408                 if(j > w) w = j;
18409                 emptycount = gameInfo.boardWidth - j;
18410                 while (emptycount--)
18411                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18412                 if (*p == '/') p++;
18413                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18414                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18415                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18416                     }
18417                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18418                 }
18419                 break;
18420 #if(BOARD_FILES >= 10)*0
18421             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18422                 p++; emptycount=10;
18423                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18424                 while (emptycount--)
18425                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18426 #endif
18427             } else if (*p == '*') {
18428                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18429             } else if (isdigit(*p)) {
18430                 emptycount = *p++ - '0';
18431                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18432                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18433                 while (emptycount--)
18434                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18435             } else if (*p == '<') {
18436                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18437                 else if (i != 0 || !shuffle) return FALSE;
18438                 p++;
18439             } else if (shuffle && *p == '>') {
18440                 p++; // for now ignore closing shuffle range, and assume rank-end
18441             } else if (*p == '?') {
18442                 if (j >= gameInfo.boardWidth) return FALSE;
18443                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18444                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18445             } else if (*p == '+' || isalpha(*p)) {
18446                 char *q, *s = SUFFIXES;
18447                 if (j >= gameInfo.boardWidth) return FALSE;
18448                 if(*p=='+') {
18449                     char c = *++p;
18450                     if(q = strchr(s, p[1])) p++;
18451                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18452                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18453                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18454                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18455                 } else {
18456                     char c = *p++;
18457                     if(q = strchr(s, *p)) p++;
18458                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18459                 }
18460
18461                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18462                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18463                     piece = (ChessSquare) (PROMOTED(piece));
18464                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18465                     p++;
18466                 }
18467                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18468                 if(piece == king) wKingRank = i;
18469                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18470             } else {
18471                 return FALSE;
18472             }
18473         }
18474     }
18475     while (*p == '/' || *p == ' ') p++;
18476
18477     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18478
18479     /* [HGM] by default clear Crazyhouse holdings, if present */
18480     if(gameInfo.holdingsWidth) {
18481        for(i=0; i<BOARD_HEIGHT; i++) {
18482            board[i][0]             = EmptySquare; /* black holdings */
18483            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18484            board[i][1]             = (ChessSquare) 0; /* black counts */
18485            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18486        }
18487     }
18488
18489     /* [HGM] look for Crazyhouse holdings here */
18490     while(*p==' ') p++;
18491     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18492         int swap=0, wcnt=0, bcnt=0;
18493         if(*p == '[') p++;
18494         if(*p == '<') swap++, p++;
18495         if(*p == '-' ) p++; /* empty holdings */ else {
18496             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18497             /* if we would allow FEN reading to set board size, we would   */
18498             /* have to add holdings and shift the board read so far here   */
18499             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18500                 p++;
18501                 if((int) piece >= (int) BlackPawn ) {
18502                     i = (int)piece - (int)BlackPawn;
18503                     i = PieceToNumber((ChessSquare)i);
18504                     if( i >= gameInfo.holdingsSize ) return FALSE;
18505                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18506                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18507                     bcnt++;
18508                 } else {
18509                     i = (int)piece - (int)WhitePawn;
18510                     i = PieceToNumber((ChessSquare)i);
18511                     if( i >= gameInfo.holdingsSize ) return FALSE;
18512                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18513                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18514                     wcnt++;
18515                 }
18516             }
18517             if(subst) { // substitute back-rank question marks by holdings pieces
18518                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18519                     int k, m, n = bcnt + 1;
18520                     if(board[0][j] == ClearBoard) {
18521                         if(!wcnt) return FALSE;
18522                         n = rand() % wcnt;
18523                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18524                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18525                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18526                             break;
18527                         }
18528                     }
18529                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18530                         if(!bcnt) return FALSE;
18531                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18532                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18533                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18534                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18535                             break;
18536                         }
18537                     }
18538                 }
18539                 subst = 0;
18540             }
18541         }
18542         if(*p == ']') p++;
18543     }
18544
18545     if(subst) return FALSE; // substitution requested, but no holdings
18546
18547     while(*p == ' ') p++;
18548
18549     /* Active color */
18550     c = *p++;
18551     if(appData.colorNickNames) {
18552       if( c == appData.colorNickNames[0] ) c = 'w'; else
18553       if( c == appData.colorNickNames[1] ) c = 'b';
18554     }
18555     switch (c) {
18556       case 'w':
18557         *blackPlaysFirst = FALSE;
18558         break;
18559       case 'b':
18560         *blackPlaysFirst = TRUE;
18561         break;
18562       default:
18563         return FALSE;
18564     }
18565
18566     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18567     /* return the extra info in global variiables             */
18568
18569     while(*p==' ') p++;
18570
18571     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18572         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18573         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18574     }
18575
18576     /* set defaults in case FEN is incomplete */
18577     board[EP_STATUS] = EP_UNKNOWN;
18578     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18579     for(i=0; i<nrCastlingRights; i++ ) {
18580         board[CASTLING][i] =
18581             appData.fischerCastling ? NoRights : initialRights[i];
18582     }   /* assume possible unless obviously impossible */
18583     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18584     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18585     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18586                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18587     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18588     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18589     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18590                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18591     FENrulePlies = 0;
18592
18593     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18594       char *q = p;
18595       int w=0, b=0;
18596       while(isalpha(*p)) {
18597         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18598         if(islower(*p)) b |= 1 << (*p++ - 'a');
18599       }
18600       if(*p == '-') p++;
18601       if(p != q) {
18602         board[TOUCHED_W] = ~w;
18603         board[TOUCHED_B] = ~b;
18604         while(*p == ' ') p++;
18605       }
18606     } else
18607
18608     if(nrCastlingRights) {
18609       int fischer = 0;
18610       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18611       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18612           /* castling indicator present, so default becomes no castlings */
18613           for(i=0; i<nrCastlingRights; i++ ) {
18614                  board[CASTLING][i] = NoRights;
18615           }
18616       }
18617       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18618              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18619              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18620              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18621         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18622
18623         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18624             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18625             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18626         }
18627         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18628             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18629         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18630                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18631         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18632                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18633         switch(c) {
18634           case'K':
18635               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18636               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18637               board[CASTLING][2] = whiteKingFile;
18638               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18639               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18640               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18641               break;
18642           case'Q':
18643               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18644               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18645               board[CASTLING][2] = whiteKingFile;
18646               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18647               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18648               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18649               break;
18650           case'k':
18651               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18652               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18653               board[CASTLING][5] = blackKingFile;
18654               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18655               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18656               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18657               break;
18658           case'q':
18659               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18660               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18661               board[CASTLING][5] = blackKingFile;
18662               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18663               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18664               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18665           case '-':
18666               break;
18667           default: /* FRC castlings */
18668               if(c >= 'a') { /* black rights */
18669                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18670                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18671                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18672                   if(i == BOARD_RGHT) break;
18673                   board[CASTLING][5] = i;
18674                   c -= AAA;
18675                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18676                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18677                   if(c > i)
18678                       board[CASTLING][3] = c;
18679                   else
18680                       board[CASTLING][4] = c;
18681               } else { /* white rights */
18682                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18683                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18684                     if(board[0][i] == WhiteKing) break;
18685                   if(i == BOARD_RGHT) break;
18686                   board[CASTLING][2] = i;
18687                   c -= AAA - 'a' + 'A';
18688                   if(board[0][c] >= WhiteKing) break;
18689                   if(c > i)
18690                       board[CASTLING][0] = c;
18691                   else
18692                       board[CASTLING][1] = c;
18693               }
18694         }
18695       }
18696       for(i=0; i<nrCastlingRights; i++)
18697         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18698       if(gameInfo.variant == VariantSChess)
18699         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18700       if(fischer && shuffle) appData.fischerCastling = TRUE;
18701     if (appData.debugMode) {
18702         fprintf(debugFP, "FEN castling rights:");
18703         for(i=0; i<nrCastlingRights; i++)
18704         fprintf(debugFP, " %d", board[CASTLING][i]);
18705         fprintf(debugFP, "\n");
18706     }
18707
18708       while(*p==' ') p++;
18709     }
18710
18711     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18712
18713     /* read e.p. field in games that know e.p. capture */
18714     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18715        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18716        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18717       if(*p=='-') {
18718         p++; board[EP_STATUS] = EP_NONE;
18719       } else {
18720          char c = *p++ - AAA;
18721
18722          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18723          if(*p >= '0' && *p <='9') p++;
18724          board[EP_STATUS] = c;
18725       }
18726     }
18727
18728
18729     if(sscanf(p, "%d", &i) == 1) {
18730         FENrulePlies = i; /* 50-move ply counter */
18731         /* (The move number is still ignored)    */
18732     }
18733
18734     return TRUE;
18735 }
18736
18737 void
18738 EditPositionPasteFEN (char *fen)
18739 {
18740   if (fen != NULL) {
18741     Board initial_position;
18742
18743     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18744       DisplayError(_("Bad FEN position in clipboard"), 0);
18745       return ;
18746     } else {
18747       int savedBlackPlaysFirst = blackPlaysFirst;
18748       EditPositionEvent();
18749       blackPlaysFirst = savedBlackPlaysFirst;
18750       CopyBoard(boards[0], initial_position);
18751       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18752       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18753       DisplayBothClocks();
18754       DrawPosition(FALSE, boards[currentMove]);
18755     }
18756   }
18757 }
18758
18759 static char cseq[12] = "\\   ";
18760
18761 Boolean
18762 set_cont_sequence (char *new_seq)
18763 {
18764     int len;
18765     Boolean ret;
18766
18767     // handle bad attempts to set the sequence
18768         if (!new_seq)
18769                 return 0; // acceptable error - no debug
18770
18771     len = strlen(new_seq);
18772     ret = (len > 0) && (len < sizeof(cseq));
18773     if (ret)
18774       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18775     else if (appData.debugMode)
18776       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18777     return ret;
18778 }
18779
18780 /*
18781     reformat a source message so words don't cross the width boundary.  internal
18782     newlines are not removed.  returns the wrapped size (no null character unless
18783     included in source message).  If dest is NULL, only calculate the size required
18784     for the dest buffer.  lp argument indicats line position upon entry, and it's
18785     passed back upon exit.
18786 */
18787 int
18788 wrap (char *dest, char *src, int count, int width, int *lp)
18789 {
18790     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18791
18792     cseq_len = strlen(cseq);
18793     old_line = line = *lp;
18794     ansi = len = clen = 0;
18795
18796     for (i=0; i < count; i++)
18797     {
18798         if (src[i] == '\033')
18799             ansi = 1;
18800
18801         // if we hit the width, back up
18802         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18803         {
18804             // store i & len in case the word is too long
18805             old_i = i, old_len = len;
18806
18807             // find the end of the last word
18808             while (i && src[i] != ' ' && src[i] != '\n')
18809             {
18810                 i--;
18811                 len--;
18812             }
18813
18814             // word too long?  restore i & len before splitting it
18815             if ((old_i-i+clen) >= width)
18816             {
18817                 i = old_i;
18818                 len = old_len;
18819             }
18820
18821             // extra space?
18822             if (i && src[i-1] == ' ')
18823                 len--;
18824
18825             if (src[i] != ' ' && src[i] != '\n')
18826             {
18827                 i--;
18828                 if (len)
18829                     len--;
18830             }
18831
18832             // now append the newline and continuation sequence
18833             if (dest)
18834                 dest[len] = '\n';
18835             len++;
18836             if (dest)
18837                 strncpy(dest+len, cseq, cseq_len);
18838             len += cseq_len;
18839             line = cseq_len;
18840             clen = cseq_len;
18841             continue;
18842         }
18843
18844         if (dest)
18845             dest[len] = src[i];
18846         len++;
18847         if (!ansi)
18848             line++;
18849         if (src[i] == '\n')
18850             line = 0;
18851         if (src[i] == 'm')
18852             ansi = 0;
18853     }
18854     if (dest && appData.debugMode)
18855     {
18856         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18857             count, width, line, len, *lp);
18858         show_bytes(debugFP, src, count);
18859         fprintf(debugFP, "\ndest: ");
18860         show_bytes(debugFP, dest, len);
18861         fprintf(debugFP, "\n");
18862     }
18863     *lp = dest ? line : old_line;
18864
18865     return len;
18866 }
18867
18868 // [HGM] vari: routines for shelving variations
18869 Boolean modeRestore = FALSE;
18870
18871 void
18872 PushInner (int firstMove, int lastMove)
18873 {
18874         int i, j, nrMoves = lastMove - firstMove;
18875
18876         // push current tail of game on stack
18877         savedResult[storedGames] = gameInfo.result;
18878         savedDetails[storedGames] = gameInfo.resultDetails;
18879         gameInfo.resultDetails = NULL;
18880         savedFirst[storedGames] = firstMove;
18881         savedLast [storedGames] = lastMove;
18882         savedFramePtr[storedGames] = framePtr;
18883         framePtr -= nrMoves; // reserve space for the boards
18884         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18885             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18886             for(j=0; j<MOVE_LEN; j++)
18887                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18888             for(j=0; j<2*MOVE_LEN; j++)
18889                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18890             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18891             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18892             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18893             pvInfoList[firstMove+i-1].depth = 0;
18894             commentList[framePtr+i] = commentList[firstMove+i];
18895             commentList[firstMove+i] = NULL;
18896         }
18897
18898         storedGames++;
18899         forwardMostMove = firstMove; // truncate game so we can start variation
18900 }
18901
18902 void
18903 PushTail (int firstMove, int lastMove)
18904 {
18905         if(appData.icsActive) { // only in local mode
18906                 forwardMostMove = currentMove; // mimic old ICS behavior
18907                 return;
18908         }
18909         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18910
18911         PushInner(firstMove, lastMove);
18912         if(storedGames == 1) GreyRevert(FALSE);
18913         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18914 }
18915
18916 void
18917 PopInner (Boolean annotate)
18918 {
18919         int i, j, nrMoves;
18920         char buf[8000], moveBuf[20];
18921
18922         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18923         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18924         nrMoves = savedLast[storedGames] - currentMove;
18925         if(annotate) {
18926                 int cnt = 10;
18927                 if(!WhiteOnMove(currentMove))
18928                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18929                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18930                 for(i=currentMove; i<forwardMostMove; i++) {
18931                         if(WhiteOnMove(i))
18932                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18933                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18934                         strcat(buf, moveBuf);
18935                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18936                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18937                 }
18938                 strcat(buf, ")");
18939         }
18940         for(i=1; i<=nrMoves; i++) { // copy last variation back
18941             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18942             for(j=0; j<MOVE_LEN; j++)
18943                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18944             for(j=0; j<2*MOVE_LEN; j++)
18945                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18946             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18947             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18948             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18949             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18950             commentList[currentMove+i] = commentList[framePtr+i];
18951             commentList[framePtr+i] = NULL;
18952         }
18953         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18954         framePtr = savedFramePtr[storedGames];
18955         gameInfo.result = savedResult[storedGames];
18956         if(gameInfo.resultDetails != NULL) {
18957             free(gameInfo.resultDetails);
18958       }
18959         gameInfo.resultDetails = savedDetails[storedGames];
18960         forwardMostMove = currentMove + nrMoves;
18961 }
18962
18963 Boolean
18964 PopTail (Boolean annotate)
18965 {
18966         if(appData.icsActive) return FALSE; // only in local mode
18967         if(!storedGames) return FALSE; // sanity
18968         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18969
18970         PopInner(annotate);
18971         if(currentMove < forwardMostMove) ForwardEvent(); else
18972         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18973
18974         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18975         return TRUE;
18976 }
18977
18978 void
18979 CleanupTail ()
18980 {       // remove all shelved variations
18981         int i;
18982         for(i=0; i<storedGames; i++) {
18983             if(savedDetails[i])
18984                 free(savedDetails[i]);
18985             savedDetails[i] = NULL;
18986         }
18987         for(i=framePtr; i<MAX_MOVES; i++) {
18988                 if(commentList[i]) free(commentList[i]);
18989                 commentList[i] = NULL;
18990         }
18991         framePtr = MAX_MOVES-1;
18992         storedGames = 0;
18993 }
18994
18995 void
18996 LoadVariation (int index, char *text)
18997 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18998         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18999         int level = 0, move;
19000
19001         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19002         // first find outermost bracketing variation
19003         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19004             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19005                 if(*p == '{') wait = '}'; else
19006                 if(*p == '[') wait = ']'; else
19007                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19008                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19009             }
19010             if(*p == wait) wait = NULLCHAR; // closing ]} found
19011             p++;
19012         }
19013         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19014         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19015         end[1] = NULLCHAR; // clip off comment beyond variation
19016         ToNrEvent(currentMove-1);
19017         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19018         // kludge: use ParsePV() to append variation to game
19019         move = currentMove;
19020         ParsePV(start, TRUE, TRUE);
19021         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19022         ClearPremoveHighlights();
19023         CommentPopDown();
19024         ToNrEvent(currentMove+1);
19025 }
19026
19027 void
19028 LoadTheme ()
19029 {
19030     char *p, *q, buf[MSG_SIZ];
19031     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19032         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19033         ParseArgsFromString(buf);
19034         ActivateTheme(TRUE); // also redo colors
19035         return;
19036     }
19037     p = nickName;
19038     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19039     {
19040         int len;
19041         q = appData.themeNames;
19042         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19043       if(appData.useBitmaps) {
19044         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19045                 appData.liteBackTextureFile, appData.darkBackTextureFile,
19046                 appData.liteBackTextureMode,
19047                 appData.darkBackTextureMode );
19048       } else {
19049         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
19050                 Col2Text(2),   // lightSquareColor
19051                 Col2Text(3) ); // darkSquareColor
19052       }
19053       if(appData.useBorder) {
19054         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19055                 appData.border);
19056       } else {
19057         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19058       }
19059       if(appData.useFont) {
19060         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19061                 appData.renderPiecesWithFont,
19062                 appData.fontToPieceTable,
19063                 Col2Text(9),    // appData.fontBackColorWhite
19064                 Col2Text(10) ); // appData.fontForeColorBlack
19065       } else {
19066         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
19067                 appData.pieceDirectory);
19068         if(!appData.pieceDirectory[0])
19069           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19070                 Col2Text(0),   // whitePieceColor
19071                 Col2Text(1) ); // blackPieceColor
19072       }
19073       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19074                 Col2Text(4),   // highlightSquareColor
19075                 Col2Text(5) ); // premoveHighlightColor
19076         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19077         if(insert != q) insert[-1] = NULLCHAR;
19078         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19079         if(q)   free(q);
19080     }
19081     ActivateTheme(FALSE);
19082 }